From 3bfd93c90e692c0ad086b7c7954d4eb4770377c6 Mon Sep 17 00:00:00 2001 From: Belchior Oliveira Date: Mon, 21 Jul 2025 16:12:59 -0300 Subject: [PATCH] Adds MySQL syntax to Values builder --- scripts/coverage_test.sh | 3 +- scripts/test.sh | 2 +- scripts/watch_test.sh | 2 +- src/fmt.rs | 1 + src/insert/insert.rs | 34 ++- src/insert/insert_internal.rs | 27 ++- src/structure.rs | 4 + src/values/values.rs | 60 +++++- src/values/values_internal.rs | 20 +- tests/clause_row_spec.rs | 179 ++++++++++++++++ tests/clause_values_spec.rs | 49 +++-- tests/command_insert_spec.rs | 145 ++++++++++++- tests/command_values_spec.rs | 383 ++++++++++++++++++++++++++-------- 13 files changed, 781 insertions(+), 128 deletions(-) create mode 100644 tests/clause_row_spec.rs diff --git a/scripts/coverage_test.sh b/scripts/coverage_test.sh index f30a3a7..5bd23ad 100755 --- a/scripts/coverage_test.sh +++ b/scripts/coverage_test.sh @@ -1,7 +1,8 @@ #!/bin/sh # Prerequisites -# cargo install rustfilt cargo-binutils +# rustup override set 1.82.0 +# cargo install rustfilt@0.2.1 cargo-binutils@0.3.6 # rustup component add llvm-tools-preview clear diff --git a/scripts/test.sh b/scripts/test.sh index 0cc7a01..0013181 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -1,6 +1,6 @@ #!/bin/sh -test_names=$(git status -s | grep 'A tests/\|M tests/' | sed -e 's/.* //' -e 's/tests\//--test /' -e 's/\.rs//' | tr '\n' ' ') +test_names=$(git status -s | grep 'A[[:space:]]*tests/\|M[[:space:]]*tests/' | sed -e 's/.* //' -e 's/tests\//--test /' -e 's/\.rs//' | tr '\n' ' ') clear echo "\n-- ------------------------------------------------------------------------------" diff --git a/scripts/watch_test.sh b/scripts/watch_test.sh index ddb7839..7ea24ff 100755 --- a/scripts/watch_test.sh +++ b/scripts/watch_test.sh @@ -10,7 +10,7 @@ all_features='postgresql sqlite mysql' features='' -test_names=$(git status -s | grep 'A tests/\|M tests/' | sed -e 's/.* //' -e 's/tests\//--test /' -e 's/\.rs//' | tr '\n' ' ') +test_names=$(git status -s | grep 'A[[:space:]]*tests/\|M[[:space:]]*tests/' | sed -e 's/.* //' -e 's/tests\//--test /' -e 's/\.rs//' | tr '\n' ' ') case "$@" in "") features="";; diff --git a/src/fmt.rs b/src/fmt.rs index 60fdaf7..e8fef0e 100644 --- a/src/fmt.rs +++ b/src/fmt.rs @@ -72,6 +72,7 @@ pub fn colorize(query: String) -> String { (blue, "RETURNING", "returning"), (blue, "RIGHT", "right"), (blue, "ROLLBACK", "rollback"), + (blue, "ROW", "row"), (blue, "SAVEPOINT", "savepoint"), (blue, "SELECT ", "select "), (blue, "SERIALIZABLE", "serializable"), diff --git a/src/insert/insert.rs b/src/insert/insert.rs index 35294d6..adb7e35 100644 --- a/src/insert/insert.rs +++ b/src/insert/insert.rs @@ -311,8 +311,8 @@ impl Insert { /// ```sql /// INSERT INTO users (login, name) VALUES ('foo', 'Foo'), ('bar', 'Bar') /// ``` - pub fn values(mut self, value: &str) -> Self { - push_unique(&mut self._values, value.trim().to_string()); + pub fn values(mut self, expression: &str) -> Self { + push_unique(&mut self._values, expression.trim().to_string()); #[cfg(not(feature = "mysql"))] { self._default_values = false @@ -663,6 +663,36 @@ impl Insert { self } + /// The `values` clause with the `row` constructor clause + /// + /// # Example + /// + /// ``` + /// # #[cfg(feature = "mysql")] + /// # { + /// # use sql_query_builder as sql; + /// let query = sql::Insert::new() + /// .insert_into("users (login, name)") + /// .row("('foo', 'Foo')") + /// .row("('bar', 'Bar')") + /// .as_string(); + /// + /// # let expected = "INSERT INTO users (login, name) VALUES ROW('foo', 'Foo'), ROW('bar', 'Bar')"; + /// # assert_eq!(expected, query); + /// # } + /// ``` + /// + /// Output + /// + /// ```sql + /// INSERT INTO users (login, name) VALUES ROW('foo', 'Foo'), ROW('bar', 'Bar') + /// ``` + pub fn row(mut self, expression: &str) -> Self { + push_unique(&mut self._values, expression.trim().to_string()); + self._use_row = true; + self + } + /// The `set` clause /// /// # Example diff --git a/src/insert/insert_internal.rs b/src/insert/insert_internal.rs index 68c8246..20bab9f 100644 --- a/src/insert/insert_internal.rs +++ b/src/insert/insert_internal.rs @@ -2,7 +2,6 @@ use crate::{ concat::{concat_raw_before_after, Concat}, fmt, structure::{Insert, InsertClause}, - utils, }; impl Concat for Insert { @@ -180,8 +179,25 @@ impl Insert { let fmt::Formatter { comma, lb, space, .. } = fmts; let sql = if self._values.is_empty() == false { let sep = format!("{comma}{lb}"); - let values = utils::join(&self._values, &sep); - format!("VALUES{space}{lb}{values}{space}{lb}") + let rows = self + ._values + .iter() + .filter(|item| item.is_empty() == false) + .map(|item| { + if self._use_row { + format!("ROW{item}") + } else { + item.clone() + } + }) + .collect::>() + .join(&sep); + + if rows.is_empty() == true { + return "".to_string(); + } + + format!("VALUES{space}{lb}{rows}{space}{lb}") } else { "".to_string() }; @@ -269,7 +285,10 @@ impl Insert { } #[cfg(feature = "mysql")] -use crate::concat::{mysql::ConcatPartition, non_standard::ConcatColumn, sql_standard::ConcatSet}; +use crate::{ + concat::{mysql::ConcatPartition, non_standard::ConcatColumn, sql_standard::ConcatSet}, + utils, +}; #[cfg(feature = "mysql")] impl ConcatColumn for Insert {} diff --git a/src/structure.rs b/src/structure.rs index eb453bc..9537bd8 100644 --- a/src/structure.rs +++ b/src/structure.rs @@ -437,6 +437,7 @@ pub struct Insert { pub(crate) _raw: Vec, pub(crate) _select: Option