diff --git a/.github/workflows/pull-requests.yml b/.github/workflows/pull-requests.yml index 22ade34..44cae3c 100644 --- a/.github/workflows/pull-requests.yml +++ b/.github/workflows/pull-requests.yml @@ -25,3 +25,8 @@ jobs: with: command: test args: --features sqlite + - uses: actions-rs/cargo@v1 + name: Test MySQL syntax + with: + command: test + args: --features mysql diff --git a/Cargo.toml b/Cargo.toml index caf7d24..adc05ed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,20 +4,28 @@ description = "Write SQL queries in a simple and composable way" documentation = "https://docs.rs/sql_query_builder" repository = "https://github.com/belchior/sql_query_builder" authors = ["Belchior Oliveira "] -version = "2.4.2" +version = "2.5.2" edition = "2021" rust-version = "1.62" license = "MIT" -keywords = ["sql", "query", "database", "postgres", "sqlite"] +keywords = ["sql", "query", "postgres", "sqlite", "mysql"] [features] +#! SQL Query Builder comes with the following optional features: + +## enable Postgres syntax postgresql = [] + +## enable SQLite syntax sqlite = [] +## enable MySQL syntax +mysql = [] + [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs"] -features = ["postgresql", "sqlite"] +features = ["postgresql", "sqlite", "mysql"] [dev-dependencies] pretty_assertions = "=1.4.0" diff --git a/README.md b/README.md index bc51c9c..210098c 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,7 @@ SELECT id, login FROM users WHERE login = $1 AND is_admin = true SQL Query Builder comes with the following optional features: - `postgresql` enable Postgres syntax - `sqlite` enable SQLite syntax +- `mysql` enable MySQL syntax You can enable features like diff --git a/scripts/coverage_test.sh b/scripts/coverage_test.sh index 085860d..5bd23ad 100755 --- a/scripts/coverage_test.sh +++ b/scripts/coverage_test.sh @@ -1,8 +1,10 @@ #!/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 PKG_NAME="$(grep 'name\s*=\s*"' Cargo.toml | sed -E 's/.*"(.*)"/\1/')" COVERAGE_OUTPUT="coverage" @@ -11,12 +13,27 @@ COVERAGE_TARGET="target/coverage" rm -fr "$COVERAGE_TARGET" mkdir -p "$COVERAGE_OUTPUT" mkdir -p "$COVERAGE_TARGET" -clear +echo "\n-- ------------------------------------------------------------------------------" +echo "-- Testing SQL Standard" +echo "-- ------------------------------------------------------------------------------\n" RUSTFLAGS="-C instrument-coverage" LLVM_PROFILE_FILE="$COVERAGE_TARGET/$PKG_NAME-%m.profraw" cargo test --target-dir $COVERAGE_TARGET; + +echo "\n-- ------------------------------------------------------------------------------" +echo "-- Testing PostgreSQL syntax" +echo "-- ------------------------------------------------------------------------------\n" RUSTFLAGS="-C instrument-coverage" LLVM_PROFILE_FILE="$COVERAGE_TARGET/$PKG_NAME-%m.profraw" cargo test --target-dir $COVERAGE_TARGET --features postgresql; + +echo "\n-- ------------------------------------------------------------------------------" +echo "-- Testing SQLite syntax" +echo "-- ------------------------------------------------------------------------------\n" RUSTFLAGS="-C instrument-coverage" LLVM_PROFILE_FILE="$COVERAGE_TARGET/$PKG_NAME-%m.profraw" cargo test --target-dir $COVERAGE_TARGET --features sqlite; +echo "\n-- ------------------------------------------------------------------------------" +echo "-- Testing MySQL syntax" +echo "-- ------------------------------------------------------------------------------\n" +RUSTFLAGS="-C instrument-coverage" LLVM_PROFILE_FILE="$COVERAGE_TARGET/$PKG_NAME-%m.profraw" cargo test --target-dir $COVERAGE_TARGET --features mysql; + cargo profdata -- merge -sparse $COVERAGE_TARGET/$PKG_NAME-*.profraw -o $COVERAGE_TARGET/$PKG_NAME.profdata; OBJECT_PATH_LIB="$(ls $COVERAGE_TARGET/debug/deps/$PKG_NAME-???????????????? | xargs -I {} echo '--object {}')" diff --git a/scripts/test.sh b/scripts/test.sh index 578a3bf..0013181 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -1,11 +1,28 @@ #!/bin/sh -test_names=$(git status -s | grep 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-- ------------------------------------------------------------------------------" +echo "-- Testing SQL Standard" +echo "-- ------------------------------------------------------------------------------\n" cargo test $test_names + +echo "\n-- ------------------------------------------------------------------------------" +echo "-- Testing PostgreSQL syntax" +echo "-- ------------------------------------------------------------------------------\n" cargo test $test_names --features postgresql + +echo "\n-- ------------------------------------------------------------------------------" +echo "-- Testing SQLite syntax" +echo "-- ------------------------------------------------------------------------------\n" cargo test $test_names --features sqlite +cargo test $test_names --features mysql + +echo "\n-- ------------------------------------------------------------------------------" +echo "-- Testing MySQL syntax" +echo "-- ------------------------------------------------------------------------------\n" +cargo test $test_names --features mysql # run only one test # cargo test --features sqlite --test name_of_the_test_file name_of_the_test -- --nocapture --color always diff --git a/scripts/watch_doc.sh b/scripts/watch_doc.sh index 7204c42..4a19400 100755 --- a/scripts/watch_doc.sh +++ b/scripts/watch_doc.sh @@ -1,6 +1,6 @@ #!/bin/sh -doc_path=$(realpath ./target/doc/sql_query_builder/index.html) +doc_path=$(pwd)/target/doc/sql_query_builder/index.html c_blue='\033[34;1m' c_no='\033[0m' diff --git a/scripts/watch_test.sh b/scripts/watch_test.sh index c1bc033..75fd612 100755 --- a/scripts/watch_test.sh +++ b/scripts/watch_test.sh @@ -8,9 +8,10 @@ # ./scripts/watch_test.sh all # will enable all feature # ./scripts/watch_test.sh postgresql # will enable only the postgresql feature -all_features='postgresql sqlite' +clear +all_features='postgresql sqlite mysql' features='' -test_names=$(git status -s | grep 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/alter_table/alter_table.rs b/src/alter_table/alter_table.rs index f740598..c08bdd8 100644 --- a/src/alter_table/alter_table.rs +++ b/src/alter_table/alter_table.rs @@ -29,14 +29,13 @@ impl AlterTable { /// ADD COLUMN age int not null /// ``` /// - /// - /// Available on crate feature `postgresql` only. + /// ### Available on crate feature `postgresql` and `mysql` only. /// Multiples call of this method will build the SQL respecting the order of the calls /// /// ### Example /// /// ``` - /// # #[cfg(any(feature = "postgresql"))] + /// # #[cfg(any(feature = "postgresql", feature = "mysql"))] /// # { /// # use sql_query_builder as sql; /// let query = sql::AlterTable::new() @@ -161,13 +160,13 @@ impl AlterTable { /// DROP column login /// ``` /// - /// Available on crate feature `postgresql` only. + /// ### Available on crate feature `postgresql` and `mysql` only. /// Multiples call of this method will build the SQL respecting the order of the calls /// /// ### Example /// /// ``` - /// # #[cfg(any(feature = "postgresql"))] + /// # #[cfg(any(feature = "postgresql", feature = "mysql"))] /// # { /// # use sql_query_builder as sql; /// let query = sql::AlterTable::new() @@ -284,16 +283,17 @@ impl AlterTable { } } -#[cfg(any(doc, feature = "postgresql", feature = "sqlite"))] +#[cfg(any(doc, feature = "postgresql", feature = "sqlite", feature = "mysql"))] #[cfg_attr(docsrs, doc(cfg(feature = "postgresql")))] #[cfg_attr(docsrs, doc(cfg(feature = "sqlite")))] +#[cfg_attr(docsrs, doc(cfg(feature = "mysql")))] impl AlterTable { /// Changes the column name or table constraints, this method overrides the previous value /// /// ### Example /// ///``` - /// # #[cfg(any(feature = "postgresql", feature = "sqlite"))] + /// # #[cfg(any(feature = "postgresql", feature = "sqlite", feature = "mysql"))] /// # { /// # use sql_query_builder as sql; /// let query = sql::AlterTable::new() @@ -311,11 +311,54 @@ impl AlterTable { /// ```sql /// ALTER TABLE users RENAME COLUMN address TO city /// ``` + /// + /// ### Available on crate feature `mysql` only. + /// Changes the table name, column name or table constraints, + /// multiples call of this method will build the SQL respecting the order of the calls + /// + /// ### Example + /// + ///``` + /// # #[cfg(feature = "mysql")] + /// # { + /// # use sql_query_builder as sql; + /// let query = sql::AlterTable::new() + /// .alter_table("users") + /// .rename("TO users_old") + /// .rename("COLUMN name TO full_name") + /// .to_string(); + /// + /// # let expected = "ALTER TABLE users RENAME TO users_old, RENAME COLUMN name TO full_name"; + /// # assert_eq!(expected, query); + /// # } + /// ``` + /// + /// Outputs + /// + /// ```sql + /// ALTER TABLE users + /// RENAME TO users_old, + /// RENAME COLUMN name TO full_name + /// ``` pub fn rename(mut self, action: &str) -> Self { - self._rename = action.trim().to_string(); + #[cfg(feature = "mysql")] + { + let action = AlterTableActionItem(AlterTableOrderedAction::Rename, action.trim().to_string()); + push_unique(&mut self._ordered_actions, action); + } + #[cfg(not(feature = "mysql"))] + { + self._rename = action.trim().to_string(); + } + self } +} +#[cfg(any(doc, feature = "postgresql", feature = "sqlite"))] +#[cfg_attr(docsrs, doc(cfg(feature = "postgresql")))] +#[cfg_attr(docsrs, doc(cfg(feature = "sqlite")))] +impl AlterTable { /// Changes the name of the table, this method overrides the previous value /// /// ### Example @@ -345,8 +388,9 @@ impl AlterTable { } } -#[cfg(any(doc, feature = "postgresql"))] +#[cfg(any(doc, feature = "postgresql", feature = "mysql"))] #[cfg_attr(docsrs, doc(cfg(feature = "postgresql")))] +#[cfg_attr(docsrs, doc(cfg(feature = "mysql")))] impl AlterTable { /// Alter columns or table constraints. /// Multiples call of this method will build the SQL respecting the order of the calls @@ -354,7 +398,7 @@ impl AlterTable { /// ### Example /// ///``` - /// # #[cfg(any(feature = "postgresql"))] + /// # #[cfg(any(feature = "postgresql", feature = "mysql"))] /// # { /// # use sql_query_builder as sql; /// let query = sql::AlterTable::new() diff --git a/src/alter_table/alter_table_internal.rs b/src/alter_table/alter_table_internal.rs index d1cbaa6..c3bbc0e 100644 --- a/src/alter_table/alter_table_internal.rs +++ b/src/alter_table/alter_table_internal.rs @@ -45,7 +45,7 @@ impl AlterTable { fn concat_ordered_actions(&self, query: String, fmts: &fmt::Formatter) -> String { let actions = self._ordered_actions.iter().filter(|item| item.1.is_empty() == false); - #[cfg(any(feature = "postgresql"))] + #[cfg(any(feature = "postgresql", feature = "mysql"))] { use crate::structure::AlterTableActionItem; @@ -63,8 +63,9 @@ impl AlterTable { match action { AlterTableOrderedAction::Add => format!("{lb}{indent}ADD{space}{content}"), AlterTableOrderedAction::Drop => format!("{lb}{indent}DROP{space}{content}"), - #[cfg(any(feature = "postgresql"))] AlterTableOrderedAction::Alter => format!("{lb}{indent}ALTER{space}{content}"), + #[cfg(feature = "mysql")] + AlterTableOrderedAction::Rename => format!("{lb}{indent}RENAME{space}{content}"), } }) .collect::>() @@ -74,7 +75,7 @@ impl AlterTable { format!("{query}{sql}{space}") } - #[cfg(not(any(feature = "postgresql")))] + #[cfg(not(any(feature = "postgresql", feature = "mysql")))] { let fmt::Formatter { lb, space, .. } = fmts; diff --git a/src/behavior.rs b/src/behavior.rs index 659c6c2..a6ad9c8 100644 --- a/src/behavior.rs +++ b/src/behavior.rs @@ -4,5 +4,5 @@ use crate::concat::Concat; pub trait TransactionQuery: Concat {} /// Represents all commands that can be used inside the with method -#[cfg(any(feature = "postgresql", feature = "sqlite"))] +#[cfg(any(feature = "postgresql", feature = "sqlite", feature = "mysql"))] pub trait WithQuery: Concat {} diff --git a/src/concat/mod.rs b/src/concat/mod.rs index acd9247..5fb430d 100644 --- a/src/concat/mod.rs +++ b/src/concat/mod.rs @@ -1,5 +1,6 @@ use crate::fmt; +pub(crate) mod mysql; pub(crate) mod non_standard; pub(crate) mod sql_standard; pub(crate) mod sqlite; diff --git a/src/concat/mysql.rs b/src/concat/mysql.rs new file mode 100644 index 0000000..f1e0ad7 --- /dev/null +++ b/src/concat/mysql.rs @@ -0,0 +1,31 @@ +#[cfg(feature = "mysql")] +use crate::{concat::concat_raw_before_after, fmt, utils}; + +#[cfg(feature = "mysql")] +pub(crate) trait ConcatPartition { + fn concat_partition( + &self, + items_raw_before: &Vec<(Clause, String)>, + items_raw_after: &Vec<(Clause, String)>, + query: String, + fmts: &fmt::Formatter, + clause: Clause, + items: &Vec, + ) -> String { + let fmt::Formatter { comma, lb, space, .. } = fmts; + + let sql = if items.is_empty() == false { + let column_names = utils::join(items, comma); + + if column_names.is_empty() == false { + format!("PARTITION{space}({column_names}){space}{lb}") + } else { + "".to_string() + } + } else { + "".to_string() + }; + + concat_raw_before_after(items_raw_before, items_raw_after, query, fmts, clause, sql) + } +} diff --git a/src/concat/non_standard.rs b/src/concat/non_standard.rs index 828b58d..0dc86e2 100644 --- a/src/concat/non_standard.rs +++ b/src/concat/non_standard.rs @@ -1,7 +1,7 @@ -#[cfg(any(feature = "postgresql", feature = "sqlite"))] -use crate::{behavior::WithQuery, concat::concat_raw_before_after, fmt}; +#[cfg(any(feature = "postgresql", feature = "sqlite", feature = "mysql"))] +use crate::{behavior::WithQuery, concat::concat_raw_before_after, fmt, utils}; -#[cfg(any(feature = "postgresql", feature = "sqlite"))] +#[cfg(any(feature = "postgresql", feature = "sqlite", feature = "mysql"))] pub(crate) trait ConcatLimit { fn concat_limit( &self, @@ -36,12 +36,7 @@ pub(crate) trait ConcatReturning { ) -> String { let fmt::Formatter { lb, space, comma, .. } = fmts; let sql = if items.is_empty() == false { - let output_names = items - .iter() - .filter(|item| item.is_empty() == false) - .map(|item| item.as_str()) - .collect::>() - .join(comma); + let output_names = utils::join(items, comma); format!("RETURNING{space}{output_names}{space}{lb}") } else { "".to_string() @@ -51,7 +46,7 @@ pub(crate) trait ConcatReturning { } } -#[cfg(any(feature = "postgresql", feature = "sqlite"))] +#[cfg(any(feature = "postgresql", feature = "sqlite", feature = "mysql"))] pub(crate) trait ConcatWith { fn concat_with( &self, @@ -98,3 +93,31 @@ pub(crate) trait ConcatWith { concat_raw_before_after(items_raw_before, items_raw_after, query, fmts, clause, sql) } } + +#[cfg(feature = "mysql")] +pub(crate) trait ConcatColumn { + fn concat_column( + &self, + items_raw_before: &Vec<(Clause, String)>, + items_raw_after: &Vec<(Clause, String)>, + query: String, + fmts: &fmt::Formatter, + clause: Clause, + items: &Vec, + ) -> String { + let fmt::Formatter { lb, comma, space, .. } = fmts; + + let sql = if items.is_empty() == false { + let column_names = utils::join(&items, comma); + if column_names.is_empty() == false { + format!("({column_names}){space}{lb}") + } else { + "".to_string() + } + } else { + "".to_string() + }; + + concat_raw_before_after(items_raw_before, items_raw_after, query, fmts, clause, sql) + } +} diff --git a/src/concat/sql_standard.rs b/src/concat/sql_standard.rs index 2a0ab79..58f9de4 100644 --- a/src/concat/sql_standard.rs +++ b/src/concat/sql_standard.rs @@ -1,4 +1,4 @@ -use crate::{concat::concat_raw_before_after, fmt, structure::LogicalOperator}; +use crate::{concat::concat_raw_before_after, fmt, structure::LogicalOperator, utils}; pub(crate) trait ConcatFrom { fn concat_from( @@ -12,12 +12,7 @@ pub(crate) trait ConcatFrom { ) -> String { let fmt::Formatter { comma, lb, space, .. } = fmts; let sql = if items.is_empty() == false { - let tables = items - .iter() - .filter(|item| item.is_empty() == false) - .map(|item| item.as_str()) - .collect::>() - .join(comma); + let tables = utils::join(items, comma); format!("FROM{space}{tables}{space}{lb}") } else { "".to_string() @@ -61,12 +56,7 @@ pub(crate) trait ConcatOrderBy { ) -> String { let fmt::Formatter { comma, lb, space, .. } = fmts; let sql = if items.is_empty() == false { - let columns = items - .iter() - .filter(|item| item.is_empty() == false) - .map(|item| item.as_str()) - .collect::>() - .join(comma); + let columns = utils::join(items, comma); format!("ORDER BY{space}{columns}{space}{lb}") } else { @@ -77,6 +67,28 @@ pub(crate) trait ConcatOrderBy { } } +pub(crate) trait ConcatSet { + fn concat_set( + &self, + items_raw_before: &Vec<(Clause, String)>, + items_raw_after: &Vec<(Clause, String)>, + query: String, + fmts: &fmt::Formatter, + clause: Clause, + items: &Vec, + ) -> String { + let fmt::Formatter { comma, lb, space, .. } = fmts; + let sql = if items.is_empty() == false { + let values = utils::join(items, comma); + format!("SET{space}{values}{space}{lb}") + } else { + "".to_string() + }; + + concat_raw_before_after(items_raw_before, items_raw_after, query, fmts, clause, sql) + } +} + pub(crate) trait ConcatWhere { fn concat_where( &self, diff --git a/src/concat/sqlite.rs b/src/concat/sqlite.rs index e939387..5080435 100644 --- a/src/concat/sqlite.rs +++ b/src/concat/sqlite.rs @@ -2,39 +2,9 @@ use crate::{ concat::concat_raw_before_after, fmt, - structure::{InsertClause, InsertVars, UpdateClause, UpdateVars}, + structure::{UpdateClause, UpdateVars}, }; -#[cfg(feature = "sqlite")] -pub(crate) trait ConcatInsert { - fn concat_insert( - &self, - items_raw_before: &Vec<(InsertClause, String)>, - items_raw_after: &Vec<(InsertClause, String)>, - query: String, - fmts: &fmt::Formatter, - insert: &(InsertVars, String), - ) -> String { - let fmt::Formatter { lb, space, .. } = fmts; - - let (clause, sql) = match insert { - (InsertVars::InsertInto, exp) if exp.is_empty() => (InsertClause::InsertInto, "".to_string()), - (InsertVars::InsertInto, exp) => (InsertClause::InsertInto, format!("INSERT INTO{space}{exp}{space}{lb}")), - - (InsertVars::InsertOr, exp) if exp.is_empty() => (InsertClause::InsertOr, "".to_string()), - (InsertVars::InsertOr, exp) => (InsertClause::InsertOr, format!("INSERT OR{space}{exp}{space}{lb}")), - - (InsertVars::ReplaceInto, exp) if exp.is_empty() => (InsertClause::ReplaceInto, "".to_string()), - (InsertVars::ReplaceInto, exp) => ( - InsertClause::ReplaceInto, - format!("REPLACE INTO{space}{exp}{space}{lb}"), - ), - }; - - concat_raw_before_after(items_raw_before, items_raw_after, query, fmts, clause, sql) - } -} - #[cfg(feature = "sqlite")] pub(crate) trait ConcatUpdate { fn concat_update( diff --git a/src/create_index/create_index.rs b/src/create_index/create_index.rs index 6d3d03e..276043c 100644 --- a/src/create_index/create_index.rs +++ b/src/create_index/create_index.rs @@ -2,7 +2,7 @@ use crate::{ behavior::TransactionQuery, concat::Concat, fmt, - structure::{CreateIndex, CreateIndexParams, LogicalOperator}, + structure::{CreateIndex, CreateIndexParams}, utils::push_unique, }; @@ -14,7 +14,7 @@ impl CreateIndex { /// ### Example /// /// ``` - /// # #[cfg(any(feature = "postgresql", feature = "sqlite"))] + /// # #[cfg(any(feature = "postgresql", feature = "sqlite", feature = "mysql"))] /// # { /// # use sql_query_builder as sql; /// let query = sql::CreateIndex::new() @@ -41,7 +41,7 @@ impl CreateIndex { /// ### Example /// ///``` - /// # #[cfg(any(feature = "postgresql", feature = "sqlite"))] + /// # #[cfg(any(feature = "postgresql", feature = "sqlite", feature = "mysql"))] /// # { /// # use sql_query_builder as sql; /// let query = sql::CreateIndex::new() @@ -70,7 +70,7 @@ impl CreateIndex { /// ### Example /// ///``` - /// # #[cfg(any(feature = "postgresql", feature = "sqlite"))] + /// # #[cfg(any(feature = "postgresql", feature = "sqlite", feature = "mysql"))] /// # { /// # use sql_query_builder as sql; /// let query = sql::CreateIndex::new() @@ -94,43 +94,13 @@ impl CreateIndex { self } - /// Defines a create index parameter with the `if not exists` modifier, this method overrides the previous value - /// - /// ### Example - /// - /// ``` - /// # #[cfg(any(feature = "postgresql", feature = "sqlite"))] - /// # { - /// # use sql_query_builder as sql; - /// let query = sql::CreateIndex::new() - /// .create_index("users_name_idx") - /// .create_index_if_not_exists("orders_product_name_idx") - /// .to_string(); - /// - /// # let expected = "CREATE INDEX IF NOT EXISTS orders_product_name_idx"; - /// # assert_eq!(expected, query); - /// # } - /// ``` - /// - /// Outputs - /// - /// ```sql - /// CREATE INDEX IF NOT EXISTS orders_product_name_idx - /// ``` - pub fn create_index_if_not_exists(mut self, index_name: &str) -> Self { - self._index_name = index_name.trim().to_string(); - self._create_index = true; - self._if_not_exists = true; - self - } - /// Prints the current state of the [CreateIndex] to the standard output in a more ease to read version. /// This method is useful to debug complex queries or just print the generated SQL while you type /// /// ### Example /// /// ``` - /// # #[cfg(any(feature = "postgresql", feature = "sqlite"))] + /// # #[cfg(any(feature = "postgresql", feature = "sqlite", feature = "mysql"))] /// # { /// # use sql_query_builder as sql; /// let query = sql::CreateIndex::new() @@ -172,7 +142,7 @@ impl CreateIndex { /// ### Example /// ///``` - /// # #[cfg(any(feature = "postgresql", feature = "sqlite"))] + /// # #[cfg(any(feature = "postgresql", feature = "sqlite", feature = "mysql"))] /// # { /// # use sql_query_builder as sql; /// let query = sql::CreateIndex::new() @@ -208,7 +178,7 @@ impl CreateIndex { /// ### Example /// /// ``` - /// # #[cfg(any(feature = "postgresql", feature = "sqlite"))] + /// # #[cfg(any(feature = "postgresql", feature = "sqlite", feature = "mysql"))] /// # { /// # use sql_query_builder as sql; /// let create_index_query = sql::CreateIndex::new() @@ -238,7 +208,7 @@ impl CreateIndex { /// ### Example /// /// ``` - /// # #[cfg(any(feature = "postgresql", feature = "sqlite"))] + /// # #[cfg(any(feature = "postgresql", feature = "sqlite", feature = "mysql"))] /// # { /// # use sql_query_builder as sql; /// let raw = "/* after create index */"; @@ -278,7 +248,7 @@ impl CreateIndex { /// ### Example /// /// ``` - /// # #[cfg(any(feature = "postgresql", feature = "sqlite"))] + /// # #[cfg(any(feature = "postgresql", feature = "sqlite", feature = "mysql"))] /// # { /// # use sql_query_builder as sql; /// let raw = "/* before create index */"; @@ -316,7 +286,7 @@ impl CreateIndex { /// ### Example /// /// ``` - /// # #[cfg(any(feature = "postgresql", feature = "sqlite"))] + /// # #[cfg(any(feature = "postgresql", feature = "sqlite", feature = "mysql"))] /// # { /// # use sql_query_builder as sql; /// let query = sql::CreateIndex::new() @@ -336,6 +306,50 @@ impl CreateIndex { /// ``` pub fn unique(mut self) -> Self { self._unique = true; + + #[cfg(feature = "mysql")] + { + self._fulltext = false; + self._spatial = false; + } + self + } +} + +#[cfg(any(feature = "postgresql", feature = "sqlite"))] +use crate::structure::LogicalOperator; + +#[cfg(any(doc, feature = "postgresql", feature = "sqlite"))] +#[cfg_attr(docsrs, doc(cfg(feature = "postgresql")))] +#[cfg_attr(docsrs, doc(cfg(feature = "sqlite")))] +impl CreateIndex { + /// Defines a create index parameter with the `if not exists` modifier, this method overrides the previous value + /// + /// ### Example + /// + /// ``` + /// # #[cfg(any(feature = "postgresql", feature = "sqlite"))] + /// # { + /// # use sql_query_builder as sql; + /// let query = sql::CreateIndex::new() + /// .create_index("users_name_idx") + /// .create_index_if_not_exists("orders_product_name_idx") + /// .to_string(); + /// + /// # let expected = "CREATE INDEX IF NOT EXISTS orders_product_name_idx"; + /// # assert_eq!(expected, query); + /// # } + /// ``` + /// + /// Outputs + /// + /// ```sql + /// CREATE INDEX IF NOT EXISTS orders_product_name_idx + /// ``` + pub fn create_index_if_not_exists(mut self, index_name: &str) -> Self { + self._index_name = index_name.trim().to_string(); + self._create_index = true; + self._if_not_exists = true; self } @@ -454,6 +468,38 @@ impl CreateIndex { } } +#[cfg(any(doc, feature = "postgresql", feature = "mysql"))] +#[cfg_attr(docsrs, doc(cfg(feature = "postgresql")))] +#[cfg_attr(docsrs, doc(cfg(feature = "mysql")))] +impl CreateIndex { + /// Defines the index algorithm to be used to create the index, this method overrides the previous value + /// + /// ### Example + /// + ///``` + /// # #[cfg(any(feature = "postgresql", feature = "mysql"))] + /// # { + /// # use sql_query_builder as sql; + /// let query = sql::CreateIndex::new() + /// .using("btree") + /// .as_string(); + /// + /// # let expected = "USING btree"; + /// # assert_eq!(expected, query); + /// # } + /// ``` + /// + /// Outputs + /// + /// ```sql + /// USING btree + /// ``` + pub fn using(mut self, index_method: &str) -> Self { + self._using = index_method.trim().to_string(); + self + } +} + #[cfg(any(doc, feature = "postgresql"))] #[cfg_attr(docsrs, doc(cfg(feature = "postgresql")))] impl CreateIndex { @@ -540,21 +586,25 @@ impl CreateIndex { self._only = true; self } +} - /// Defines the index method to be used to create the index, this method overrides the previous value +#[cfg(any(doc, feature = "mysql"))] +#[cfg_attr(docsrs, doc(cfg(feature = "mysql")))] +impl CreateIndex { + /// Defines the `fulltext` parameter of the create index /// /// ### Example /// - ///``` - /// # #[cfg(feature = "postgresql")] + /// ``` + /// # #[cfg(feature = "mysql")] /// # { /// # use sql_query_builder as sql; /// let query = sql::CreateIndex::new() - /// .using("btree") - /// .using("gist") - /// .as_string(); + /// .create_index("idx_users") + /// .fulltext() + /// .to_string(); /// - /// # let expected = "USING gist"; + /// # let expected = "CREATE FULLTEXT INDEX idx_users"; /// # assert_eq!(expected, query); /// # } /// ``` @@ -562,10 +612,70 @@ impl CreateIndex { /// Outputs /// /// ```sql - /// USING gist + /// CREATE FULLTEXT INDEX idx_users /// ``` - pub fn using(mut self, index_method: &str) -> Self { - self._using = index_method.trim().to_string(); + pub fn fulltext(mut self) -> Self { + self._fulltext = true; + self._unique = false; + self._spatial = false; + self + } + + /// Defines the `lock` option + /// + /// ### Example + /// + /// ``` + /// # #[cfg(feature = "mysql")] + /// # { + /// # use sql_query_builder as sql; + /// let query = sql::CreateIndex::new() + /// .create_index("idx_users") + /// .lock("exclusive") + /// .to_string(); + /// + /// # let expected = "CREATE INDEX idx_users LOCK exclusive"; + /// # assert_eq!(expected, query); + /// # } + /// ``` + /// + /// Outputs + /// + /// ```sql + /// CREATE INDEX idx_users LOCK exclusive + /// ``` + pub fn lock(mut self, lock_option: &str) -> Self { + self._lock = lock_option.trim().to_string(); + self + } + + /// Defines the `spatial` parameter of the create index + /// + /// ### Example + /// + /// ``` + /// # #[cfg(feature = "mysql")] + /// # { + /// # use sql_query_builder as sql; + /// let query = sql::CreateIndex::new() + /// .create_index("idx_users") + /// .spatial() + /// .to_string(); + /// + /// # let expected = "CREATE SPATIAL INDEX idx_users"; + /// # assert_eq!(expected, query); + /// # } + /// ``` + /// + /// Outputs + /// + /// ```sql + /// CREATE SPATIAL INDEX idx_users + /// ``` + pub fn spatial(mut self) -> Self { + self._spatial = true; + self._unique = false; + self._fulltext = false; self } } diff --git a/src/create_index/create_index_internal.rs b/src/create_index/create_index_internal.rs index fd22451..b48153d 100644 --- a/src/create_index/create_index_internal.rs +++ b/src/create_index/create_index_internal.rs @@ -1,11 +1,9 @@ use crate::{ - concat::{concat_raw_before_after, sql_standard::ConcatWhere, Concat}, + concat::{concat_raw_before_after, Concat}, fmt, structure::{CreateIndex, CreateIndexParams}, }; -impl ConcatWhere for CreateIndex {} - impl Concat for CreateIndex { fn concat(&self, fmts: &fmt::Formatter) -> String { let mut query = "".to_string(); @@ -17,30 +15,42 @@ impl Concat for CreateIndex { query = self.concat_create_index_postgres(query, &fmts); query = self.concat_on_postgres(query, &fmts); query = self.concat_using(query, &fmts); + query = self.concat_column(query, &fmts); + query = self.concat_include(query, &fmts); + query = self.concat_where( + &self._raw_before, + &self._raw_after, + query, + &fmts, + CreateIndexParams::Where, + &self._where, + ); } #[cfg(feature = "sqlite")] { query = self.concat_create_index_sqlite(query, &fmts); - query = self.concat_on_sqlite(query, &fmts); + query = self.concat_on(query, &fmts); + query = self.concat_column(query, &fmts); + query = self.concat_where( + &self._raw_before, + &self._raw_after, + query, + &fmts, + CreateIndexParams::Where, + &self._where, + ); } - query = self.concat_column(query, &fmts); - - #[cfg(feature = "postgresql")] + #[cfg(feature = "mysql")] { - query = self.concat_include(query, &fmts); + query = self.concat_create_index_mysql(query, &fmts); + query = self.concat_using(query, &fmts); + query = self.concat_on(query, &fmts); + query = self.concat_column(query, &fmts); + query = self.concat_lock(query, &fmts); } - query = self.concat_where( - &self._raw_before, - &self._raw_after, - query, - &fmts, - CreateIndexParams::Where, - &self._where, - ); - query.trim_end().to_string() } } @@ -78,6 +88,59 @@ impl CreateIndex { } } +#[cfg(any(feature = "postgresql", feature = "sqlite"))] +use crate::concat::sql_standard::ConcatWhere; + +#[cfg(any(feature = "postgresql", feature = "sqlite"))] +impl ConcatWhere for CreateIndex {} + +#[cfg(any(feature = "postgresql", feature = "mysql"))] +impl CreateIndex { + fn concat_using(&self, query: String, fmts: &fmt::Formatter) -> String { + let fmt::Formatter { space, .. } = fmts; + + let sql = if self._using.is_empty() == false { + let index_method = &self._using; + format!("USING{space}{index_method}{space}") + } else { + "".to_string() + }; + + concat_raw_before_after( + &self._raw_before, + &self._raw_after, + query, + fmts, + CreateIndexParams::Using, + sql, + ) + } +} + +#[cfg(any(feature = "sqlite", feature = "mysql"))] +impl CreateIndex { + fn concat_on(&self, query: String, fmts: &fmt::Formatter) -> String { + let fmt::Formatter { space, .. } = fmts; + + let sql = if self._on.is_empty() == false { + let table_name = &self._on; + + format!("ON{space}{table_name}{space}") + } else { + "".to_string() + }; + + concat_raw_before_after( + &self._raw_before, + &self._raw_after, + query, + fmts, + CreateIndexParams::On, + sql, + ) + } +} + #[cfg(feature = "postgresql")] impl CreateIndex { fn concat_create_index_postgres(&self, query: String, fmts: &fmt::Formatter) -> String { @@ -204,13 +267,40 @@ impl CreateIndex { sql, ) } +} - fn concat_using(&self, query: String, fmts: &fmt::Formatter) -> String { - let fmt::Formatter { space, .. } = fmts; +#[cfg(feature = "sqlite")] +impl CreateIndex { + fn concat_create_index_sqlite(&self, query: String, fmts: &fmt::Formatter) -> String { + let fmt::Formatter { lb, space, .. } = fmts; - let sql = if self._using.is_empty() == false { - let index_method = &self._using; - format!("USING{space}{index_method}{space}") + let unique = if self._unique { + concat_raw_before_after( + &self._raw_before, + &self._raw_after, + "".to_string(), + fmts, + CreateIndexParams::Unique, + format!("UNIQUE{space}"), + ) + } else { + "".to_string() + }; + + let if_not_exists = if self._if_not_exists { + format!("IF NOT EXISTS{space}") + } else { + "".to_string() + }; + + let index_name = if self._index_name.is_empty() == false { + format!("{}{space}", &self._index_name) + } else { + "".to_string() + }; + + let sql = if index_name.is_empty() == false { + format!("CREATE{space}{unique}INDEX{space}{if_not_exists}{index_name}{lb}") } else { "".to_string() }; @@ -220,15 +310,15 @@ impl CreateIndex { &self._raw_after, query, fmts, - CreateIndexParams::Using, + CreateIndexParams::CreateIndex, sql, ) } } -#[cfg(feature = "sqlite")] +#[cfg(feature = "mysql")] impl CreateIndex { - fn concat_create_index_sqlite(&self, query: String, fmts: &fmt::Formatter) -> String { + fn concat_create_index_mysql(&self, query: String, fmts: &fmt::Formatter) -> String { let fmt::Formatter { lb, space, .. } = fmts; let unique = if self._unique { @@ -244,8 +334,28 @@ impl CreateIndex { "".to_string() }; - let if_not_exists = if self._if_not_exists { - format!("IF NOT EXISTS{space}") + let fulltext = if self._fulltext { + concat_raw_before_after( + &self._raw_before, + &self._raw_after, + "".to_string(), + fmts, + CreateIndexParams::Fulltext, + format!("FULLTEXT{space}"), + ) + } else { + "".to_string() + }; + + let spatial = if self._spatial { + concat_raw_before_after( + &self._raw_before, + &self._raw_after, + "".to_string(), + fmts, + CreateIndexParams::Spatial, + format!("SPATIAL{space}"), + ) } else { "".to_string() }; @@ -257,7 +367,7 @@ impl CreateIndex { }; let sql = if index_name.is_empty() == false { - format!("CREATE{space}{unique}INDEX{space}{if_not_exists}{index_name}{lb}") + format!("CREATE{space}{unique}{fulltext}{spatial}INDEX{space}{index_name}{lb}") } else { "".to_string() }; @@ -272,13 +382,12 @@ impl CreateIndex { ) } - fn concat_on_sqlite(&self, query: String, fmts: &fmt::Formatter) -> String { - let fmt::Formatter { space, .. } = fmts; - - let sql = if self._on.is_empty() == false { - let table_name = &self._on; + fn concat_lock(&self, query: String, fmts: &fmt::Formatter) -> String { + let fmt::Formatter { lb, space, .. } = fmts; - format!("ON{space}{table_name}{space}") + let sql = if self._lock.is_empty() == false { + let lock_option = &self._lock; + format!("LOCK{space}{lock_option}{space}{lb}") } else { "".to_string() }; @@ -288,7 +397,7 @@ impl CreateIndex { &self._raw_after, query, fmts, - CreateIndexParams::On, + CreateIndexParams::Lock, sql, ) } diff --git a/src/delete/delete.rs b/src/delete/delete.rs index 7915e61..6e8cf72 100644 --- a/src/delete/delete.rs +++ b/src/delete/delete.rs @@ -21,7 +21,7 @@ impl Delete { /// .as_string(); /// /// # let expected = "DELETE FROM users WHERE id = $1"; - /// # assert_eq!(query, expected); + /// # assert_eq!(expected, query); /// ``` /// /// Output @@ -41,11 +41,11 @@ impl Delete { /// /// ``` /// # use sql_query_builder as sql; - /// let delete_query = sql::Delete::new() + /// let query = sql::Delete::new() /// .delete_from("users") /// .where_clause("login = 'foo'") - /// .debug() /// .where_clause("name = 'Foo'") + /// .debug() /// .as_string(); /// ``` /// @@ -54,7 +54,9 @@ impl Delete { /// ```sql /// -- ------------------------------------------------------------------------------ /// DELETE FROM users - /// WHERE login = 'foo' + /// WHERE + /// login = 'foo' + /// AND name = 'Foo' /// -- ------------------------------------------------------------------------------ /// ``` pub fn debug(self) -> Self { @@ -63,7 +65,7 @@ impl Delete { self } - /// The `delete` clause. This method overrides the previous value + /// The `delete` and `from` clauses. This method overrides the previous value /// /// # Example /// @@ -73,17 +75,23 @@ impl Delete { /// .delete_from("orders"); /// # /// # let expected = "DELETE FROM orders"; - /// # assert_eq!(delete.to_string(), expected); + /// # assert_eq!(expected, delete.to_string()); /// /// let delete = sql::Delete::new() /// .delete_from("addresses") /// .delete_from("orders"); /// /// # let expected = "DELETE FROM orders"; - /// # assert_eq!(delete.to_string(), expected); + /// # assert_eq!(expected, delete.to_string()); + /// ``` + /// + /// Output + /// + /// ```sql + /// DELETE FROM orders /// ``` - pub fn delete_from(mut self, table_name: &str) -> Self { - self._delete_from = table_name.trim().to_string(); + pub fn delete_from(mut self, table: &str) -> Self { + self._delete_from = table.trim().to_string(); self } @@ -108,13 +116,13 @@ impl Delete { /// # use sql_query_builder as sql; /// let raw_query = "delete from users"; /// - /// let delete_query = sql::Delete::new() + /// let query = sql::Delete::new() /// .raw(raw_query) /// .where_clause("login = 'foo'") /// .as_string(); /// /// # let expected = "delete from users WHERE login = 'foo'"; - /// # assert_eq!(delete_query, expected); + /// # assert_eq!(expected, query); /// ``` /// /// Output @@ -135,13 +143,13 @@ impl Delete { /// # use sql_query_builder as sql; /// let raw = "where name = 'Foo'"; /// - /// let delete_query = sql::Delete::new() + /// let query = sql::Delete::new() /// .delete_from("users") /// .raw_after(sql::DeleteClause::DeleteFrom, raw) /// .as_string(); /// /// # let expected = "DELETE FROM users where name = 'Foo'"; - /// # assert_eq!(delete_query, expected); + /// # assert_eq!(expected, query); /// ``` /// /// Output @@ -162,13 +170,13 @@ impl Delete { /// # use sql_query_builder as sql; /// let raw = "delete from users"; /// - /// let delete_query = sql::Delete::new() + /// let query = sql::Delete::new() /// .raw_before(sql::DeleteClause::Where, raw) /// .where_clause("name = 'Bar'") /// .as_string(); /// /// # let expected = "delete from users WHERE name = 'Bar'"; - /// # assert_eq!(delete_query, expected); + /// # assert_eq!(expected, query); /// ``` /// /// Output @@ -187,14 +195,14 @@ impl Delete { /// /// ``` /// # use sql_query_builder as sql; - /// let delete_query = sql::Delete::new() + /// let query = sql::Delete::new() /// .where_clause("login = $1") /// .where_and("product_id = $2") /// .where_and("created_at >= current_date") /// .as_string(); /// /// # let expected = "WHERE login = $1 AND product_id = $2 AND created_at >= current_date"; - /// # assert_eq!(delete_query, expected); + /// # assert_eq!(expected, query); /// ``` /// /// Outputs @@ -216,13 +224,13 @@ impl Delete { /// /// ``` /// # use sql_query_builder as sql; - /// let delete_query = sql::Delete::new() + /// let query = sql::Delete::new() /// .where_clause("login = $1") /// .where_clause("status = 'deactivated'") /// .as_string(); /// /// # let expected = "WHERE login = $1 AND status = 'deactivated'"; - /// # assert_eq!(delete_query, expected); + /// # assert_eq!(expected, query); /// ``` /// /// Outputs @@ -244,13 +252,13 @@ impl Delete { /// /// ``` /// # use sql_query_builder as sql; - /// let delete_query = sql::Delete::new() + /// let query = sql::Delete::new() /// .where_clause("login = 'foo'") /// .where_or("login = 'bar'") /// .as_string(); /// /// # let expected = "WHERE login = 'foo' OR login = 'bar'"; - /// # assert_eq!(delete_query, expected); + /// # assert_eq!(expected, query); /// ``` /// /// Output @@ -266,12 +274,67 @@ impl Delete { } } -#[cfg(any(feature = "postgresql", feature = "sqlite"))] +#[cfg(any(feature = "postgresql", feature = "sqlite", feature = "mysql"))] use crate::behavior::WithQuery; -#[cfg(any(feature = "postgresql", feature = "sqlite"))] +#[cfg(any(feature = "postgresql", feature = "sqlite", feature = "mysql"))] impl WithQuery for Delete {} +#[cfg(any(doc, feature = "postgresql", feature = "sqlite", feature = "mysql"))] +#[cfg_attr(docsrs, doc(cfg(feature = "postgresql")))] +#[cfg_attr(docsrs, doc(cfg(feature = "sqlite")))] +#[cfg_attr(docsrs, doc(cfg(feature = "mysql")))] +impl Delete { + /// The `with` clause + /// + /// # Example + /// + /// ``` + /// # #[cfg(any(feature = "postgresql", feature = "sqlite", feature = "mysql"))] + /// # { + /// # use sql_query_builder as sql; + /// let deactivated_users = sql::Select::new() + /// .select("id") + /// .from("users") + /// .where_clause("ative = false"); + /// + /// let delete = sql::Delete::new() + /// .with("deactivated_users", deactivated_users) + /// .delete_from("users") + /// .where_clause("id in (select * from deactivated_users)") + /// .debug(); + /// + /// # let expected = "\ + /// # WITH deactivated_users AS (\ + /// # SELECT id \ + /// # FROM users \ + /// # WHERE ative = false\ + /// # ) \ + /// # DELETE FROM users \ + /// # WHERE id in (select * from deactivated_users)\ + /// # "; + /// # assert_eq!(expected, delete.to_string()); + /// # } + /// ``` + /// + /// Output + /// + /// ```sql + /// WITH deactivated_users AS ( + /// SELECT id + /// FROM users + /// WHERE ative = false + /// ) + /// DELETE FROM users + /// WHERE id in (select * from deactivated_users) + /// ``` + #[cfg(any(feature = "postgresql", feature = "sqlite", feature = "mysql"))] + pub fn with(mut self, name: &str, query: impl WithQuery + 'static + Send + Sync) -> Self { + self._with.push((name.trim().to_string(), std::sync::Arc::new(query))); + self + } +} + #[cfg(any(doc, feature = "postgresql", feature = "sqlite"))] #[cfg_attr(docsrs, doc(cfg(feature = "postgresql")))] #[cfg_attr(docsrs, doc(cfg(feature = "sqlite")))] @@ -290,7 +353,7 @@ impl Delete { /// .returning("login"); /// /// # let expected = "DELETE FROM users RETURNING id, login"; - /// # assert_eq!(delete.to_string(), expected); + /// # assert_eq!(expected, delete.to_string()); /// # } /// ``` /// @@ -303,49 +366,332 @@ impl Delete { push_unique(&mut self._returning, output_name.trim().to_string()); self } +} - /// The `with` clause +#[cfg(any(doc, feature = "mysql"))] +#[cfg_attr(docsrs, doc(cfg(feature = "mysql")))] +impl Delete { + /// The `delete` clause. MySQL allow single and multi-table deletes /// - /// # Example + /// # Single-table delete /// /// ``` - /// # #[cfg(any(feature = "postgresql", feature = "sqlite"))] + /// # #[cfg(feature = "mysql")] /// # { /// # use sql_query_builder as sql; - /// let deactivated_users = sql::Select::new().select("id").from("users").where_clause("ative = false"); /// let delete = sql::Delete::new() - /// .with("deactivated_users", deactivated_users) - /// .delete_from("users") - /// .where_clause("id in (select * from deactivated_users)") - /// .debug(); + /// .delete("low_priority") + /// .from("t1") + /// .where_clause("t1.id = '123'"); + /// + /// # let expected = "DELETE low_priority FROM t1 WHERE t1.id = '123'"; + /// # assert_eq!(expected, delete.to_string()); + /// # } + /// ``` + /// + /// Output + /// + /// ```sql + /// DELETE low_priority FROM t1 WHERE t1.id = '123' + /// ``` + /// + /// If the delete clause has no argument you can use the [delete_from](Delete::delete_from) method. + /// + /// ``` + /// # #[cfg(feature = "mysql")] + /// # { + /// # use sql_query_builder as sql; + /// let delete = sql::Delete::new() + /// .delete_from("t1") + /// .where_clause("t1.id = '123'"); + /// + /// # let expected = "DELETE FROM t1 WHERE t1.id = '123'"; + /// # assert_eq!(expected, delete.to_string()); + /// # } + /// ``` + /// + /// Output + /// + /// ```sql + /// DELETE FROM t1 WHERE t1.id = '123' + /// ``` + /// + /// # Multi-table deletes + /// + /// ``` + /// # #[cfg(feature = "mysql")] + /// # { + /// # use sql_query_builder as sql; + /// let delete = sql::Delete::new() + /// .delete("t1") + /// .delete("t2") + /// .from("t1") + /// .inner_join("t2") + /// .inner_join("t3") + /// .where_clause("t1.id = t2.id") + /// .where_and("t2.id = t3.id"); /// /// # let expected = "\ - /// WITH deactivated_users AS (\ - /// SELECT id \ - /// FROM users \ - /// WHERE ative = false\ - /// ) \ - /// DELETE FROM users \ - /// WHERE id in (select * from deactivated_users)\ - /// "; - /// # assert_eq!(delete.to_string(), expected); + /// # DELETE t1, t2 \ + /// # FROM t1 \ + /// # INNER JOIN t2 \ + /// # INNER JOIN t3 \ + /// # WHERE \ + /// # t1.id = t2.id \ + /// # AND t2.id = t3.id\ + /// # "; + /// # assert_eq!(expected, delete.to_string()); /// # } /// ``` /// /// Output /// /// ```sql - /// WITH deactivated_users AS ( - /// SELECT id - /// FROM users - /// WHERE ative = false - /// ) - /// DELETE FROM users - /// WHERE id in (select * from deactivated_users) + /// DELETE t1, t2 + /// FROM t1 + /// INNER JOIN t2 + /// INNER JOIN t3 + /// WHERE + /// t1.id = t2.id + /// AND t2.id = t3.id /// ``` - #[cfg(any(feature = "postgresql", feature = "sqlite"))] - pub fn with(mut self, name: &str, query: impl WithQuery + 'static + Send + Sync) -> Self { - self._with.push((name.trim().to_string(), std::sync::Arc::new(query))); + pub fn delete(mut self, table: &str) -> Self { + push_unique(&mut self._delete, table.trim().to_string()); + self + } + + /// The `from` clause + /// + /// # Example + /// + /// ``` + /// # #[cfg(feature = "mysql")] + /// # { + /// # use sql_query_builder as sql; + /// let query = sql::Delete::new() + /// .from("users") + /// .as_string(); + /// + /// # let expected = "FROM users"; + /// # assert_eq!(expected, query); + /// # } + /// ``` + /// + /// Output + /// + /// ```sql + /// FROM users + /// ``` + pub fn from(mut self, table: &str) -> Self { + push_unique(&mut self._from, table.trim().to_string()); + self + } + + /// The `cross join` clause + /// + /// # Example + /// + /// ``` + /// # #[cfg(feature = "mysql")] + /// # { + /// # use sql_query_builder as sql; + /// let query = sql::Delete::new() + /// .cross_join("addresses") + /// .as_string(); + /// + /// # let expected = "CROSS JOIN addresses"; + /// # assert_eq!(expected, query); + /// # } + /// ``` + /// + /// Output + /// + /// ```sql + /// CROSS JOIN addresses + /// ``` + pub fn cross_join(mut self, table: &str) -> Self { + let table = table.trim(); + if table.is_empty() == false { + let join = format!("CROSS JOIN {table}"); + push_unique(&mut self._join, join); + } + self + } + + /// The `inner join` clause + /// + /// # Example + /// + /// ``` + /// # #[cfg(feature = "mysql")] + /// # { + /// # use sql_query_builder as sql; + /// let query = sql::Delete::new() + /// .inner_join("addresses on addresses.user_login = users.login") + /// .as_string(); + /// + /// # let expected = "INNER JOIN addresses on addresses.user_login = users.login"; + /// # assert_eq!(query, expected); + /// # } + /// ``` + /// + /// Output + /// + /// ```sql + /// INNER JOIN addresses on addresses.user_login = users.login + /// ``` + pub fn inner_join(mut self, table: &str) -> Self { + let table = table.trim(); + if table.is_empty() == false { + let join = format!("INNER JOIN {table}"); + push_unique(&mut self._join, join); + } + self + } + + /// The `left join` clause + /// + /// # Example + /// + /// ``` + /// # #[cfg(feature = "mysql")] + /// # { + /// # use sql_query_builder as sql; + /// let query = sql::Delete::new() + /// .left_join("addresses on addresses.user_login = users.login") + /// .as_string(); + /// + /// # let expected = "LEFT JOIN addresses on addresses.user_login = users.login"; + /// # assert_eq!(query, expected); + /// # } + /// ``` + /// + /// Output + /// + /// ```sql + /// LEFT JOIN addresses on addresses.user_login = users.login + /// ``` + pub fn left_join(mut self, table: &str) -> Self { + let table = table.trim(); + if table.is_empty() == false { + let join = format!("LEFT JOIN {table}"); + push_unique(&mut self._join, join); + } + self + } + + /// The `right join` clause + /// + /// # Example + /// + /// ``` + /// # #[cfg(feature = "mysql")] + /// # { + /// # use sql_query_builder as sql; + /// let query = sql::Delete::new() + /// .right_join("addresses on addresses.user_login = users.login") + /// .as_string(); + /// + /// # let expected = "RIGHT JOIN addresses on addresses.user_login = users.login"; + /// # assert_eq!(query, expected); + /// # } + /// ``` + /// + /// Output + /// + /// ```sql + /// RIGHT JOIN addresses on addresses.user_login = users.login + /// ``` + pub fn right_join(mut self, table: &str) -> Self { + let table = table.trim(); + if table.is_empty() == false { + let join = format!("RIGHT JOIN {table}"); + push_unique(&mut self._join, join); + } + self + } + + /// The `limit` clause, this method overrides the previous value + /// + /// # Example + /// + /// ``` + /// # #[cfg(feature = "mysql")] + /// # { + /// # use sql_query_builder as sql; + /// let delete = sql::Delete::new() + /// .limit("123"); + /// + /// let delete = sql::Delete::new() + /// .limit("1000") + /// .limit("123"); + /// + /// # let expected = "LIMIT 123"; + /// # assert_eq!(expected, delete.as_string()); + /// # } + /// ``` + /// + /// Output + /// + /// ```sql + /// LIMIT 123 + /// ``` + pub fn limit(mut self, num: &str) -> Self { + self._limit = num.trim().to_string(); + self + } + + /// The `order by` clause + /// + /// # Example + /// + /// ``` + /// # #[cfg(feature = "mysql")] + /// # { + /// # use sql_query_builder as sql; + /// let delete = sql::Delete::new() + /// .order_by("created_at asc"); + /// + /// # let expected = "ORDER BY created_at asc"; + /// # assert_eq!(expected, delete.as_string()); + /// # } + /// ``` + /// + /// Output + /// + /// ```sql + /// ORDER BY created_at asc + /// ``` + pub fn order_by(mut self, column: &str) -> Self { + push_unique(&mut self._order_by, column.trim().to_string()); + self + } + + /// The `partition` clause + /// + /// # Example + /// + /// ``` + /// # #[cfg(feature = "mysql")] + /// # { + /// # use sql_query_builder as sql; + /// let query = sql::Delete::new() + /// .delete_from("employees") + /// .partition("p1") + /// .to_string(); + /// + /// # let expected = "DELETE FROM employees PARTITION (p1)"; + /// # assert_eq!(expected, query); + /// # } + /// ``` + /// + /// Output + /// + /// ```sql + /// DELETE FROM employees PARTITION (p1) + /// ``` + pub fn partition(mut self, name: &str) -> Self { + push_unique(&mut self._partition, name.trim().to_string()); self } } diff --git a/src/delete/delete_internal.rs b/src/delete/delete_internal.rs index dfa745a..589e69b 100644 --- a/src/delete/delete_internal.rs +++ b/src/delete/delete_internal.rs @@ -10,9 +10,23 @@ impl Concat for Delete { fn concat(&self, fmts: &fmt::Formatter) -> String { let mut query = "".to_string(); - query = self.concat_raw(query, &fmts, &self._raw); - #[cfg(any(feature = "postgresql", feature = "sqlite"))] + #[cfg(not(any(feature = "postgresql", feature = "sqlite", feature = "mysql")))] { + query = self.concat_raw(query, &fmts, &self._raw); + query = self.concat_delete_from(query, &fmts); + query = self.concat_where( + &self._raw_before, + &self._raw_after, + query, + &fmts, + DeleteClause::Where, + &self._where, + ); + } + + #[cfg(feature = "postgresql")] + { + query = self.concat_raw(query, &fmts, &self._raw); query = self.concat_with( &self._raw_before, &self._raw_after, @@ -21,18 +35,45 @@ impl Concat for Delete { DeleteClause::With, &self._with, ); + query = self.concat_delete_from(query, &fmts); + query = self.concat_where( + &self._raw_before, + &self._raw_after, + query, + &fmts, + DeleteClause::Where, + &self._where, + ); + query = self.concat_returning( + &self._raw_before, + &self._raw_after, + query, + &fmts, + DeleteClause::Returning, + &self._returning, + ); } - query = self.concat_delete_from(query, &fmts); - query = self.concat_where( - &self._raw_before, - &self._raw_after, - query, - &fmts, - DeleteClause::Where, - &self._where, - ); - #[cfg(any(feature = "postgresql", feature = "sqlite"))] + + #[cfg(feature = "sqlite")] { + query = self.concat_raw(query, &fmts, &self._raw); + query = self.concat_with( + &self._raw_before, + &self._raw_after, + query, + &fmts, + DeleteClause::With, + &self._with, + ); + query = self.concat_delete_from(query, &fmts); + query = self.concat_where( + &self._raw_before, + &self._raw_after, + query, + &fmts, + DeleteClause::Where, + &self._where, + ); query = self.concat_returning( &self._raw_before, &self._raw_after, @@ -43,11 +84,66 @@ impl Concat for Delete { ); } + #[cfg(feature = "mysql")] + { + query = self.concat_raw(query, &fmts, &self._raw); + query = self.concat_with( + &self._raw_before, + &self._raw_after, + query, + &fmts, + DeleteClause::With, + &self._with, + ); + query = self.concat_delete_from_mysql(query, &fmts); + query = self.concat_join( + &self._raw_before, + &self._raw_after, + query, + &fmts, + DeleteClause::Join, + &self._join, + ); + query = self.concat_partition( + &self._raw_before, + &self._raw_after, + query, + &fmts, + DeleteClause::Partition, + &self._partition, + ); + query = self.concat_where( + &self._raw_before, + &self._raw_after, + query, + &fmts, + DeleteClause::Where, + &self._where, + ); + query = self.concat_order_by( + &self._raw_before, + &self._raw_after, + query, + &fmts, + DeleteClause::OrderBy, + &self._order_by, + ); + query = self.concat_limit( + &self._raw_before, + &self._raw_after, + query, + &fmts, + DeleteClause::Limit, + &self._limit, + ); + } + query.trim_end().to_string() } } impl Delete { + #[cfg(not(feature = "mysql"))] fn concat_delete_from(&self, query: String, fmts: &fmt::Formatter) -> String { let fmt::Formatter { lb, space, .. } = fmts; let sql = if self._delete_from.is_empty() == false { @@ -68,10 +164,169 @@ impl Delete { } } +#[cfg(any(feature = "postgresql", feature = "sqlite", feature = "mysql"))] +use crate::concat::non_standard::ConcatWith; + +#[cfg(any(feature = "postgresql", feature = "sqlite", feature = "mysql"))] +impl ConcatWith for Delete {} + #[cfg(any(feature = "postgresql", feature = "sqlite"))] -use crate::concat::non_standard::{ConcatReturning, ConcatWith}; +use crate::concat::non_standard::ConcatReturning; #[cfg(any(feature = "postgresql", feature = "sqlite"))] impl ConcatReturning for Delete {} -#[cfg(any(feature = "postgresql", feature = "sqlite"))] -impl ConcatWith for Delete {} + +#[cfg(feature = "mysql")] +use crate::{ + concat::{ + mysql::ConcatPartition, + non_standard::ConcatLimit, + sql_standard::{ConcatFrom, ConcatJoin, ConcatOrderBy}, + }, + utils, +}; + +#[cfg(feature = "mysql")] +impl ConcatFrom for Delete {} +#[cfg(feature = "mysql")] +impl ConcatJoin for Delete {} +#[cfg(feature = "mysql")] +impl ConcatLimit for Delete {} +#[cfg(feature = "mysql")] +impl ConcatOrderBy for Delete {} +#[cfg(feature = "mysql")] +impl ConcatPartition for Delete {} + +#[cfg(feature = "mysql")] +impl Delete { + fn concat_delete_from_mysql(&self, query: String, fmts: &fmt::Formatter) -> String { + let fmt::Formatter { comma, lb, space, .. } = fmts; + let delete_values = utils::join(&self._delete, comma); + let from_values = utils::join(&self._from, comma); + + match (&self._delete_from, delete_values, from_values) { + (del_from, del, from) if del_from.is_empty() == false && del.is_empty() == false && from.is_empty() == false => { + let delete_clause = concat_raw_before_after( + &self._raw_before, + &self._raw_after, + "".to_string(), + fmts, + DeleteClause::Delete, + format!("DELETE{space}{del}{space}"), + ); + + let from_clause = concat_raw_before_after( + &self._raw_before, + &self._raw_after, + "".to_string(), + fmts, + DeleteClause::From, + format!("FROM{space}{del_from}{comma}{from}{space}{lb}"), + ); + + concat_raw_before_after( + &self._raw_before, + &self._raw_after, + query, + fmts, + DeleteClause::DeleteFrom, + format!("{delete_clause}{from_clause}"), + ) + } + (del_from, del, from) if del_from.is_empty() == false && del.is_empty() == false && from.is_empty() => { + let delete_clause = concat_raw_before_after( + &self._raw_before, + &self._raw_after, + "".to_string(), + fmts, + DeleteClause::Delete, + format!("DELETE{space}{del}{space}"), + ); + + let sql = format!("{delete_clause}FROM{space}{del_from}{space}{lb}"); + + concat_raw_before_after( + &self._raw_before, + &self._raw_after, + query, + fmts, + DeleteClause::DeleteFrom, + sql, + ) + } + (del_from, del, from) if del_from.is_empty() == false && del.is_empty() && from.is_empty() == false => { + let from_clause = concat_raw_before_after( + &self._raw_before, + &self._raw_after, + "".to_string(), + fmts, + DeleteClause::From, + format!("FROM{space}{del_from}{comma}{from}{space}{lb}"), + ); + + concat_raw_before_after( + &self._raw_before, + &self._raw_after, + query, + fmts, + DeleteClause::DeleteFrom, + format!("DELETE{space}{from_clause}"), + ) + } + (del_from, del, from) if del_from.is_empty() == false && del.is_empty() && from.is_empty() => { + let sql = format!("DELETE{space}FROM{space}{del_from}{space}{lb}"); + + concat_raw_before_after( + &self._raw_before, + &self._raw_after, + query, + fmts, + DeleteClause::DeleteFrom, + sql, + ) + } + (del_from, del, from) if del_from.is_empty() && del.is_empty() == false && from.is_empty() == false => { + let delete_clause = concat_raw_before_after( + &self._raw_before, + &self._raw_after, + "".to_string(), + fmts, + DeleteClause::Delete, + format!("DELETE{space}{del}{space}"), + ); + + let from_clause = concat_raw_before_after( + &self._raw_before, + &self._raw_after, + "".to_string(), + fmts, + DeleteClause::From, + format!("FROM{space}{from}{space}{lb}"), + ); + + format!("{delete_clause}{from_clause}") + } + (del_from, del, from) if del_from.is_empty() && del.is_empty() == false && from.is_empty() => { + concat_raw_before_after( + &self._raw_before, + &self._raw_after, + query, + fmts, + DeleteClause::Delete, + format!("DELETE{space}{del}{space}"), + ) + } + (del_from, del, from) if del_from.is_empty() && del.is_empty() && from.is_empty() == false => { + concat_raw_before_after( + &self._raw_before, + &self._raw_after, + query, + fmts, + DeleteClause::From, + format!("FROM{space}{from}{space}{lb}"), + ) + } + (_, _, _) => query, + } + } +} diff --git a/src/drop_index/drop_index.rs b/src/drop_index/drop_index.rs index 5eabd68..2e47dac 100644 --- a/src/drop_index/drop_index.rs +++ b/src/drop_index/drop_index.rs @@ -38,7 +38,7 @@ impl DropIndex { /// ### Example 1 /// ///``` - /// # #[cfg(not(feature = "postgresql"))] + /// # #[cfg(any(feature = "sqlite", feature = "mysql"))] /// # { /// # use sql_query_builder as sql; /// let query = sql::DropIndex::new() @@ -85,59 +85,6 @@ impl DropIndex { self } - /// Defines a drop index parameter with the `if exists` modifier, this method overrides the previous value - /// - /// ### Example 1 - /// - /// ``` - /// # #[cfg(not(feature = "postgresql"))] - /// # { - /// # use sql_query_builder as sql; - /// let query = sql::DropIndex::new() - /// .drop_index("users_name_idx") - /// .drop_index_if_exists("orders_product_name_idx") - /// .to_string(); - /// - /// # let expected = "DROP INDEX IF EXISTS orders_product_name_idx"; - /// # assert_eq!(expected, query); - /// # } - /// ``` - /// - /// Outputs - /// - /// ```sql - /// DROP INDEX IF EXISTS orders_product_name_idx - /// ``` - /// - /// ### Example 2 `crate features postgresql only` - /// - /// Multiples call will concatenates all values - /// - /// ``` - /// # #[cfg(feature = "postgresql")] - /// # { - /// # use sql_query_builder as sql; - /// let query = sql::DropIndex::new() - /// .drop_index("users_name_idx") - /// .drop_index_if_exists("orders_product_name_idx") - /// .to_string(); - /// - /// # let expected = "DROP INDEX IF EXISTS users_name_idx, orders_product_name_idx"; - /// # assert_eq!(expected, query); - /// # } - /// ``` - /// - /// Outputs - /// - /// ```sql - /// DROP INDEX IF EXISTS users_name_idx, orders_product_name_idx - /// ``` - pub fn drop_index_if_exists(mut self, index_name: &str) -> Self { - push_unique(&mut self._drop_index, index_name.trim().to_string()); - self._if_exists = true; - self - } - /// Prints the current state of the [DropIndex] to the standard output in a more ease to read version. /// This method is useful to debug complex queries or just print the generated SQL while you type /// @@ -259,6 +206,64 @@ impl DropIndex { } } +#[cfg(any(feature = "postgresql", feature = "sqlite"))] +#[cfg_attr(docsrs, doc(cfg(feature = "postgresql")))] +#[cfg_attr(docsrs, doc(cfg(feature = "sqlite")))] +impl DropIndex { + /// Defines a drop index parameter with the `if exists` modifier, this method overrides the previous value + /// + /// ### Example 1 + /// + /// ``` + /// # #[cfg(feature = "sqlite")] + /// # { + /// # use sql_query_builder as sql; + /// let query = sql::DropIndex::new() + /// .drop_index("users_name_idx") + /// .drop_index_if_exists("orders_product_name_idx") + /// .to_string(); + /// + /// # let expected = "DROP INDEX IF EXISTS orders_product_name_idx"; + /// # assert_eq!(expected, query); + /// # } + /// ``` + /// + /// Outputs + /// + /// ```sql + /// DROP INDEX IF EXISTS orders_product_name_idx + /// ``` + /// + /// ### Example 2 `crate features postgresql only` + /// + /// Multiples call will concatenates all values + /// + /// ``` + /// # #[cfg(feature = "postgresql")] + /// # { + /// # use sql_query_builder as sql; + /// let query = sql::DropIndex::new() + /// .drop_index("users_name_idx") + /// .drop_index_if_exists("orders_product_name_idx") + /// .to_string(); + /// + /// # let expected = "DROP INDEX IF EXISTS users_name_idx, orders_product_name_idx"; + /// # assert_eq!(expected, query); + /// # } + /// ``` + /// + /// Outputs + /// + /// ```sql + /// DROP INDEX IF EXISTS users_name_idx, orders_product_name_idx + /// ``` + pub fn drop_index_if_exists(mut self, index_name: &str) -> Self { + push_unique(&mut self._drop_index, index_name.trim().to_string()); + self._if_exists = true; + self + } +} + impl std::fmt::Display for DropIndex { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "{}", self.as_string()) diff --git a/src/drop_table/drop_table.rs b/src/drop_table/drop_table.rs index fac354c..6c0e42a 100644 --- a/src/drop_table/drop_table.rs +++ b/src/drop_table/drop_table.rs @@ -38,7 +38,7 @@ impl DropTable { /// ### Example 1 /// ///``` - /// # #[cfg(not(feature = "postgresql"))] + /// # #[cfg(not(any(feature = "postgresql", feature = "mysql")))] /// # { /// # use sql_query_builder as sql; /// let query = sql::DropTable::new() @@ -57,12 +57,12 @@ impl DropTable { /// DROP TABLE orders /// ``` /// - /// ### Example 2 `crate features postgresql only` + /// ### Example 2 `crate features postgresql and mysql only` /// /// Multiples call will concatenates all values /// ///``` - /// # #[cfg(feature = "postgresql")] + /// # #[cfg(any(feature = "postgresql", feature = "mysql"))] /// # { /// # use sql_query_builder as sql; /// let query = sql::DropTable::new() @@ -90,7 +90,7 @@ impl DropTable { /// ### Example 1 /// /// ``` - /// # #[cfg(not(feature = "postgresql"))] + /// # #[cfg(not(any(feature = "postgresql", feature = "mysql")))] /// # { /// # use sql_query_builder as sql; /// let query = sql::DropTable::new() @@ -109,12 +109,12 @@ impl DropTable { /// DROP TABLE IF EXISTS orders /// ``` /// - /// ### Example 2 `crate features postgresql only` + /// ### Example 2 `crate features postgresql and mysql only` /// /// Multiples call will concatenates all values /// /// ``` - /// # #[cfg(feature = "postgresql")] + /// # #[cfg(any(feature = "postgresql", feature = "mysql"))] /// # { /// # use sql_query_builder as sql; /// let query = sql::DropTable::new() diff --git a/src/drop_table/drop_table_internal.rs b/src/drop_table/drop_table_internal.rs index 7f2bae1..f74eaa5 100644 --- a/src/drop_table/drop_table_internal.rs +++ b/src/drop_table/drop_table_internal.rs @@ -26,7 +26,7 @@ impl DropTable { "".to_string() }; - let table_names = if cfg!(any(feature = "postgresql")) { + let table_names = if cfg!(any(feature = "postgresql", feature = "mysql")) { self ._drop_table .iter() diff --git a/src/fmt.rs b/src/fmt.rs index 100364e..e8fef0e 100644 --- a/src/fmt.rs +++ b/src/fmt.rs @@ -62,6 +62,7 @@ pub fn colorize(query: String) -> String { (blue, "ONLY ", "only "), (blue, "ORDER BY", "order by"), (blue, "OVERRIDING", "overriding"), + (blue, "PARTITION", "partition"), (blue, "PRIMARY", "primary"), (blue, "READ ONLY", "read only"), (blue, "READ WRITE", "read write"), @@ -71,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 e009f0c..ca045b2 100644 --- a/src/insert/insert.rs +++ b/src/insert/insert.rs @@ -2,7 +2,7 @@ use crate::{ behavior::TransactionQuery, concat::Concat, fmt, - structure::{Insert, InsertClause, Select}, + structure::{Insert, InsertClause, InsertVariance, Select, ValuesVariance}, utils::push_unique, }; @@ -21,7 +21,7 @@ impl Insert { /// .as_string(); /// /// # let expected = "INSERT INTO users (login) VALUES ('foo')"; - /// # assert_eq!(query, expected); + /// # assert_eq!(expected, query); /// ``` /// /// Output @@ -41,7 +41,7 @@ impl Insert { /// /// ``` /// # use sql_query_builder as sql; - /// let insert_query = sql::Insert::new() + /// let query = sql::Insert::new() /// .insert_into("users (login, name)") /// .values("('foo', 'Foo')") /// .debug() @@ -68,14 +68,17 @@ impl Insert { /// # Example /// /// ``` + /// # #[cfg(not(feature = "mysql"))] + /// # { /// # use sql_query_builder as sql; - /// let insert_query = sql::Insert::new() + /// let query = sql::Insert::new() /// .insert_into("users") /// .default_values() /// .to_string(); /// /// # let expected = "INSERT INTO users DEFAULT VALUES"; - /// # assert_eq!(insert_query, expected); + /// # assert_eq!(expected, query); + /// # } /// ``` /// /// Output @@ -83,8 +86,9 @@ impl Insert { /// ```sql /// INSERT INTO users DEFAULT VALUES /// ``` + #[cfg(not(feature = "mysql"))] pub fn default_values(mut self) -> Self { - self._default_values = true; + self._values_variance = ValuesVariance::InsertDefaultValues; self } @@ -98,51 +102,74 @@ impl Insert { /// .insert_into("users (login, name)"); /// # /// # let expected = "INSERT INTO users (login, name)"; - /// # assert_eq!(insert.to_string(), expected); + /// # assert_eq!(expected, insert.to_string()); /// /// let insert = sql::Insert::new() /// .insert_into("addresses (state, country)") /// .insert_into("users (login, name)"); /// /// # let expected = "INSERT INTO users (login, name)"; - /// # assert_eq!(insert.to_string(), expected); + /// # assert_eq!(expected, insert.to_string()); /// ``` - #[cfg(not(feature = "sqlite"))] - pub fn insert_into(mut self, table_name: &str) -> Self { - self._insert_into = table_name.trim().to_string(); - self - } - - /// Creates instance of the Insert command - pub fn new() -> Self { - Self::default() - } - - /// The `on conflict` clause. This method overrides the previous value /// - /// # Example + /// # Mutually exclusive methods + /// + /// Avaliable on `sqlite` only + /// + /// Methods [Insert::insert_into], [Insert::insert_or] and [Insert::replace_into] are mutually exclusive, the last called will overrides the previous ones. /// /// ``` + /// # #[cfg(feature = "sqlite")] + /// # { /// # use sql_query_builder as sql; - /// let query = sql::Insert::new() - /// .insert_into("users (login)") - /// .on_conflict("do nothing") - /// .as_string(); + /// let insert = sql::Insert::new() + /// .insert_into("users (login, name)") + /// .replace_into("users (login, name)"); + /// # + /// # let expected = "REPLACE INTO users (login, name)"; + /// # assert_eq!(expected, insert.to_string()); + /// # } + /// ``` + /// Output /// - /// # let expected = "INSERT INTO users (login) ON CONFLICT do nothing"; - /// # assert_eq!(query, expected); + /// ```sql + /// REPLACE INTO users (login, name) /// ``` /// + /// Avaliable on `mysql` only + /// + /// Methods [Insert::insert_into] and the splitted version ([Insert::insert], [Insert::into], [Insert::column]) are mutually exclusive, the last called will overrides the previous ones. + /// + /// ``` + /// # #[cfg(feature = "mysql")] + /// # { + /// # use sql_query_builder as sql; + /// let insert = sql::Insert::new() + /// .insert_into("users (login, name)") + /// .insert("low_priority") + /// .into("users") + /// .column("login"); + /// # + /// # let expected = "INSERT low_priority INTO users (login)"; + /// # assert_eq!(expected, insert.to_string()); + /// # } + /// ``` /// Output /// /// ```sql - /// INSERT INTO users (login) ON CONFLICT do nothing + /// INSERT low_priority INTO users (login) /// ``` - pub fn on_conflict(mut self, conflict: &str) -> Self { - self._on_conflict = conflict.trim().to_string(); + pub fn insert_into(mut self, table_name: &str) -> Self { + self._insert_into = table_name.trim().to_string(); + self._insert_variance = InsertVariance::InsertInto; self } + /// Creates instance of the Insert command + pub fn new() -> Self { + Self::default() + } + /// The `overriding` clause. This method overrides the previous value /// /// # Example @@ -155,7 +182,7 @@ impl Insert { /// .as_string(); /// /// # let expected = "INSERT INTO users (login) OVERRIDING user value"; - /// # assert_eq!(query, expected); + /// # assert_eq!(expected, query); /// ``` /// /// Output @@ -163,7 +190,7 @@ impl Insert { /// ```sql /// INSERT INTO users (login) OVERRIDING user value /// ``` - #[cfg(not(feature = "sqlite"))] + #[cfg(not(any(feature = "sqlite", feature = "mysql")))] pub fn overriding(mut self, option: &str) -> Self { self._overriding = option.trim().to_string(); self @@ -183,7 +210,7 @@ impl Insert { /// /// ``` /// # use sql_query_builder as sql; - /// let insert_query = sql::Insert::new() + /// let query = sql::Insert::new() /// .insert_into("users (login, name)") /// .select( /// sql::Select::new() @@ -199,7 +226,7 @@ impl Insert { /// # FROM users_bk \ /// # WHERE active = true\ /// # "; - /// # assert_eq!(insert_query, expected); + /// # assert_eq!(expected, query); /// ``` /// /// Output @@ -212,6 +239,7 @@ impl Insert { /// ``` pub fn select(mut self, select: Select) -> Self { self._select = Some(select); + self._values_variance = ValuesVariance::InsertSelect; self } @@ -223,13 +251,13 @@ impl Insert { /// # use sql_query_builder as sql; /// let raw_query = "insert into users (login, name)"; /// - /// let insert_query = sql::Insert::new() + /// let query = sql::Insert::new() /// .raw(raw_query) /// .values("('foo', 'Foo')") /// .as_string(); /// /// # let expected = "insert into users (login, name) VALUES ('foo', 'Foo')"; - /// # assert_eq!(insert_query, expected); + /// # assert_eq!(expected, query); /// ``` /// /// Output @@ -250,13 +278,13 @@ impl Insert { /// # use sql_query_builder as sql; /// let raw = "values ('foo', 'Foo')"; /// - /// let insert_query = sql::Insert::new() + /// let query = sql::Insert::new() /// .insert_into("users (login, name)") /// .raw_after(sql::InsertClause::InsertInto, raw) /// .as_string(); /// /// # let expected = "INSERT INTO users (login, name) values ('foo', 'Foo')"; - /// # assert_eq!(insert_query, expected); + /// # assert_eq!(expected, query); /// ``` /// /// Output @@ -277,13 +305,13 @@ impl Insert { /// # use sql_query_builder as sql; /// let raw = "insert into users (login, name)"; /// - /// let insert_query = sql::Insert::new() + /// let query = sql::Insert::new() /// .raw_before(sql::InsertClause::Values, raw) /// .values("('bar', 'Bar')") /// .as_string(); /// /// # let expected = "insert into users (login, name) VALUES ('bar', 'Bar')"; - /// # assert_eq!(insert_query, expected); + /// # assert_eq!(expected, query); /// ``` /// /// Output @@ -309,7 +337,7 @@ impl Insert { /// .as_string(); /// /// # let expected = "INSERT INTO users (login, name) VALUES ('foo', 'Foo'), ('bar', 'Bar')"; - /// # assert_eq!(query, expected); + /// # assert_eq!(expected, query); /// ``` /// /// Output @@ -317,8 +345,59 @@ 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()); + /// + /// # Composable methods + /// The methods [Insert::values], [Insert::row] are composable, when both was used each line will receive the contructor clause row. + /// + /// # Example + /// ``` + /// # #[cfg(feature = "mysql")] + /// # { + /// # use sql_query_builder as sql; + /// let query = sql::Insert::new() + /// .values("('foo', 'Foo')") + /// .row("('bar', 'Bar')") + /// .as_string(); + /// # let expected = "VALUES ROW('foo', 'Foo'), ROW('bar', 'Bar')"; + /// # assert_eq!(expected, query); + /// # } + /// ``` + /// + /// Output + /// + /// ```sql + /// VALUES ROW('foo', 'Foo'), ROW('bar', 'Bar') + /// ``` + /// + /// # Mutually exclusive methods + /// The methods [Insert::values]/[Insert::row], [Insert::select] and [Insert::set] are mutually exclusive, the last called will overrides the previous ones. + /// [Insert::row] and [Insert::set] are available on crate feature `mysql` only. + /// + /// # Example + /// ``` + /// # #[cfg(feature = "mysql")] + /// # { + /// # use sql_query_builder as sql; + /// let query = sql::Insert::new() + /// .values("('foo', 'Foo')") + /// .row("('bar', 'Bar')") + /// .set("login = 'foo'") + /// .set("name = 'Foo'") + /// .as_string(); + /// # let expected = "SET login = 'foo', name = 'Foo'"; + /// # assert_eq!(expected, query); + /// # } + /// ``` + /// + /// Output + /// + /// ```sql + /// SET login = 'foo', name = 'Foo' + /// ``` + pub fn values(mut self, expression: &str) -> Self { + push_unique(&mut self._values, expression.trim().to_string()); + self._values_variance = ValuesVariance::InsertValues; + self } } @@ -333,6 +412,34 @@ impl WithQuery for Insert {} #[cfg_attr(docsrs, doc(cfg(feature = "postgresql")))] #[cfg_attr(docsrs, doc(cfg(feature = "sqlite")))] impl Insert { + /// The `on conflict` clause. This method overrides the previous value + /// + /// # Example + /// + /// ``` + /// # #[cfg(any(feature = "postgresql", feature = "sqlite"))] + /// # { + /// # use sql_query_builder as sql; + /// let query = sql::Insert::new() + /// .insert_into("users (login)") + /// .on_conflict("do nothing") + /// .as_string(); + /// + /// # let expected = "INSERT INTO users (login) ON CONFLICT do nothing"; + /// # assert_eq!(expected, query); + /// # } + /// ``` + /// + /// Output + /// + /// ```sql + /// INSERT INTO users (login) ON CONFLICT do nothing + /// ``` + pub fn on_conflict(mut self, conflict: &str) -> Self { + self._on_conflict = conflict.trim().to_string(); + self + } + /// The `returning` clause /// /// # Example @@ -341,14 +448,14 @@ impl Insert { /// # #[cfg(any(feature = "postgresql", feature = "sqlite"))] /// # { /// # use sql_query_builder as sql; - /// let insert_query = sql::Insert::new() + /// let query = sql::Insert::new() /// .insert_into("users") /// .returning("id") /// .returning("login") /// .to_string(); /// /// # let expected = "INSERT INTO users RETURNING id, login"; - /// # assert_eq!(insert_query, expected); + /// # assert_eq!(expected, query); /// # } /// ``` /// @@ -375,7 +482,7 @@ impl Insert { /// .from("users_bk") /// .where_clause("ative = true"); /// - /// let insert_query = sql::Insert::new() + /// let query = sql::Insert::new() /// .with("active_users", active_users) /// .insert_into("users") /// .select(sql::Select::new().select("*").from("active_users")) @@ -391,7 +498,7 @@ impl Insert { /// # SELECT * \ /// # FROM active_users\ /// # "; - /// # assert_eq!(insert_query, expected); + /// # assert_eq!(expected, query); /// # } /// ``` /// @@ -414,19 +521,9 @@ impl Insert { } } -#[cfg(feature = "sqlite")] -use crate::structure::InsertVars; - #[cfg(any(doc, feature = "sqlite"))] #[cfg_attr(docsrs, doc(cfg(feature = "sqlite")))] impl Insert { - /// The `insert into` clause, this method overrides the previous value - #[cfg(not(doc))] - pub fn insert_into(mut self, expression: &str) -> Self { - self._insert = (InsertVars::InsertInto, expression.trim().to_string()); - self - } - /// The `insert or into` clause /// /// # Example @@ -439,14 +536,14 @@ impl Insert { /// .insert_or("abort into users (login, name)"); /// # /// # let expected = "INSERT OR abort into users (login, name)"; - /// # assert_eq!(insert.to_string(), expected); + /// # assert_eq!(expected, insert.to_string()); /// /// let insert = sql::Insert::new() /// .insert_or("fail into addresses (state, country)") /// .insert_or("abort into users (login, name)"); /// /// # let expected = "INSERT OR abort into users (login, name)"; - /// # assert_eq!(insert.to_string(), expected); + /// # assert_eq!(expected, insert.to_string()); /// # } /// ``` /// @@ -456,7 +553,8 @@ impl Insert { /// INSERT OR abort into users (login, name) /// ``` pub fn insert_or(mut self, expression: &str) -> Self { - self._insert = (InsertVars::InsertOr, expression.trim().to_string()); + self._insert_or = expression.trim().to_string(); + self._insert_variance = InsertVariance::InsertOr; self } @@ -472,7 +570,7 @@ impl Insert { /// .replace_into("users (login, name)"); /// # /// # let expected = "REPLACE INTO users (login, name)"; - /// # assert_eq!(insert.to_string(), expected); + /// # assert_eq!(expected, insert.to_string()); /// # } /// ``` /// @@ -481,8 +579,224 @@ impl Insert { /// ```sql /// REPLACE INTO users (login, name) /// ``` - pub fn replace_into(mut self, expression: &str) -> Self { - self._insert = (InsertVars::ReplaceInto, expression.trim().to_string()); + pub fn replace_into(mut self, table_name: &str) -> Self { + self._replace_into = table_name.trim().to_string(); + self._insert_variance = InsertVariance::ReplaceInto; + self + } +} + +#[cfg(any(doc, feature = "mysql"))] +#[cfg_attr(docsrs, doc(cfg(feature = "mysql")))] +impl Insert { + /// Defines the columns of the table used to insert values. + /// + /// ### Example + /// + ///``` + /// # #[cfg(feature = "mysql")] + /// # { + /// # use sql_query_builder as sql; + /// let query = sql::Insert::new() + /// .into("users") + /// .column("login") + /// .column("name") + /// .as_string(); + /// + /// # let expected = "INTO users (login, name)"; + /// # assert_eq!(expected, query); + /// # } + /// ``` + /// + /// Outputs + /// + /// ```sql + /// INTO users (login, name) + /// ``` + pub fn column(mut self, column_name: &str) -> Self { + push_unique(&mut self._column, column_name.trim().to_string()); + self._insert_variance = InsertVariance::InsertSplitted; + self + } + + /// The `insert` clause, used to defined modifiers to change de insert execution + /// + /// ### Example + /// + ///``` + /// # #[cfg(feature = "mysql")] + /// # { + /// # use sql_query_builder as sql; + /// let query = sql::Insert::new() + /// .insert("LOW_PRIORITY") + /// .into("users") + /// .column("login") + /// .as_string(); + /// + /// # let expected = "INSERT LOW_PRIORITY INTO users (login)"; + /// # assert_eq!(expected, query); + /// # } + /// ``` + /// + /// Outputs + /// + /// ```sql + /// INSERT LOW_PRIORITY INTO users (login) + /// ``` + pub fn insert(mut self, modifier: &str) -> Self { + self._insert = modifier.trim().to_string(); + self._insert_variance = InsertVariance::InsertSplitted; + self + } + + /// The `into` clause, defines the name of the table to be used + /// + /// ### Example + /// + ///``` + /// # #[cfg(feature = "mysql")] + /// # { + /// # use sql_query_builder as sql; + /// let query = sql::Insert::new() + /// .into("users") + /// .column("login") + /// .as_string(); + /// + /// # let expected = "INTO users (login)"; + /// # assert_eq!(expected, query); + /// # } + /// ``` + /// + /// Outputs + /// + /// ```sql + /// INTO users (login) + /// ``` + pub fn into(mut self, table: &str) -> Self { + self._into = table.trim().to_string(); + self._insert_variance = InsertVariance::InsertSplitted; + self + } + + /// The `partition` clause + /// + /// # Example + /// + /// ``` + /// # #[cfg(feature = "mysql")] + /// # { + /// # use sql_query_builder as sql; + /// let query = sql::Insert::new() + /// .into("employees") + /// .partition("p1") + /// .to_string(); + /// + /// # let expected = "INTO employees PARTITION (p1)"; + /// # assert_eq!(expected, query); + /// # } + /// ``` + /// + /// Output + /// + /// ```sql + /// INTO employees PARTITION (p1) + /// ``` + pub fn partition(mut self, name: &str) -> Self { + push_unique(&mut self._partition, name.trim().to_string()); + self._insert_into = "".to_string(); + self + } + + /// The `ON DUPLICATE KEY UPDATE` clause + /// + /// # Example + /// + /// ``` + /// # #[cfg(feature = "mysql")] + /// # { + /// # use sql_query_builder as sql; + /// let query = sql::Insert::new() + /// .insert_into("t1 (a, b, c)") + /// .values("(1, 2, 3)") + /// .on_duplicate_key_update("c = c+1") + /// .as_string(); + /// + /// # let expected = "\ + /// # INSERT INTO t1 (a, b, c) \ + /// # VALUES (1, 2, 3) \ + /// # ON DUPLICATE KEY UPDATE c = c+1\ + /// # "; + /// # assert_eq!(expected, query); + /// # } + /// ``` + /// + /// Output + /// + /// ```sql + /// INSERT INTO t1 (a, b, c) + /// VALUES (1, 2, 3) + /// ON DUPLICATE KEY UPDATE c = c+1 + /// ``` + pub fn on_duplicate_key_update(mut self, assignment: &str) -> Self { + push_unique(&mut self._on_duplicate_key_update, assignment.trim().to_string()); + 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._values_variance = ValuesVariance::InsertValuesRow; + self + } + + /// The `set` clause + /// + /// # Example + /// + /// ``` + /// # #[cfg(feature = "mysql")] + /// # { + /// # use sql_query_builder as sql; + /// let update_query = sql::Insert::new() + /// .set("name = 'Bar'") + /// .as_string(); + /// + /// # let expected = "SET name = 'Bar'"; + /// # assert_eq!(expected, update_query); + /// # } + /// ``` + /// + /// Output + /// + /// ```sql + /// SET name = 'Bar' + /// ``` + pub fn set(mut self, assignment: &str) -> Self { + push_unique(&mut self._set, assignment.trim().to_string()); + self._values_variance = ValuesVariance::InsertSet; self } } diff --git a/src/insert/insert_internal.rs b/src/insert/insert_internal.rs index 5ab978b..ff3ef51 100644 --- a/src/insert/insert_internal.rs +++ b/src/insert/insert_internal.rs @@ -1,17 +1,37 @@ use crate::{ concat::{concat_raw_before_after, Concat}, fmt, - structure::{Insert, InsertClause}, + structure::{Insert, InsertClause, ValuesVariance}, }; +#[cfg(any(feature = "sqlite", feature = "mysql"))] +use crate::structure::InsertVariance; + impl Concat for Insert { fn concat(&self, fmts: &fmt::Formatter) -> String { let mut query = "".to_string(); - query = self.concat_raw(query, &fmts, &self._raw); + #[cfg(not(any(feature = "postgresql", feature = "sqlite", feature = "mysql")))] + { + query = self.concat_raw(query, &fmts, &self._raw); + query = self.concat_insert_into(query, &fmts); + query = self.concat_overriding(query, &fmts); + match self._values_variance { + ValuesVariance::InsertDefaultValues => { + query = self.concat_default_values(query, &fmts); + } + ValuesVariance::InsertSelect => { + query = self.concat_select(query, &fmts); + } + ValuesVariance::InsertValues => { + query = self.concat_values(query, &fmts); + } + } + } - #[cfg(any(feature = "postgresql", feature = "sqlite"))] + #[cfg(feature = "postgresql")] { + query = self.concat_raw(query, &fmts, &self._raw); query = self.concat_with( &self._raw_before, &self._raw_after, @@ -20,27 +40,64 @@ impl Concat for Insert { InsertClause::With, &self._with, ); - } - - #[cfg(not(feature = "sqlite"))] - { query = self.concat_insert_into(query, &fmts); + query = self.concat_overriding(query, &fmts); + match self._values_variance { + ValuesVariance::InsertDefaultValues => { + query = self.concat_default_values(query, &fmts); + } + ValuesVariance::InsertSelect => { + query = self.concat_select(query, &fmts); + } + ValuesVariance::InsertValues => { + query = self.concat_values(query, &fmts); + } + } + query = self.concat_on_conflict(query, &fmts); + query = self.concat_returning( + &self._raw_before, + &self._raw_after, + query, + &fmts, + InsertClause::Returning, + &self._returning, + ); } - #[cfg(feature = "sqlite")] - { - query = ConcatInsert::concat_insert(self, &self._raw_before, &self._raw_after, query, &fmts, &self._insert); - } - - query = self.concat_overriding(query, &fmts); - - query = self.concat_values(query, &fmts); - - query = self.concat_select(query, &fmts); - - query = self.concat_on_conflict(query, &fmts); - #[cfg(any(feature = "postgresql", feature = "sqlite"))] + #[cfg(feature = "sqlite")] { + query = self.concat_raw(query, &fmts, &self._raw); + query = self.concat_with( + &self._raw_before, + &self._raw_after, + query, + &fmts, + InsertClause::With, + &self._with, + ); + match self._insert_variance { + InsertVariance::InsertInto => { + query = self.concat_insert_into(query, &fmts); + } + InsertVariance::InsertOr => { + query = self.concat_insert_or(query, &fmts); + } + InsertVariance::ReplaceInto => { + query = self.concat_replace_into(query, &fmts); + } + } + match self._values_variance { + ValuesVariance::InsertDefaultValues => { + query = self.concat_default_values(query, &fmts); + } + ValuesVariance::InsertSelect => { + query = self.concat_select(query, &fmts); + } + ValuesVariance::InsertValues => { + query = self.concat_values(query, &fmts); + } + } + query = self.concat_on_conflict(query, &fmts); query = self.concat_returning( &self._raw_before, &self._raw_after, @@ -51,17 +108,80 @@ impl Concat for Insert { ); } + #[cfg(feature = "mysql")] + { + query = self.concat_raw(query, &fmts, &self._raw); + match self._insert_variance { + InsertVariance::InsertInto => { + query = self.concat_insert_into(query, &fmts); + } + InsertVariance::InsertSplitted => { + query = self.concat_insert(query, &fmts); + query = self.concat_into(query, &fmts); + } + } + + query = self.concat_partition( + &self._raw_before, + &self._raw_after, + query, + &fmts, + InsertClause::Partition, + &self._partition, + ); + + match self._values_variance { + ValuesVariance::InsertSelect => { + if self._insert_variance == InsertVariance::InsertSplitted { + query = self.concat_column( + &self._raw_before, + &self._raw_after, + query, + &fmts, + InsertClause::Column, + &self._column, + ); + } + query = self.concat_select(query, &fmts); + } + ValuesVariance::InsertSet => { + query = self.concat_set( + &self._raw_before, + &self._raw_after, + query, + &fmts, + InsertClause::Set, + &self._set, + ); + } + ValuesVariance::InsertValues | ValuesVariance::InsertValuesRow => { + if self._insert_variance == InsertVariance::InsertSplitted { + query = self.concat_column( + &self._raw_before, + &self._raw_after, + query, + &fmts, + InsertClause::Column, + &self._column, + ); + } + query = self.concat_values(query, &fmts); + } + } + + query = self.concat_on_duplicate_key_update(query, &fmts); + } + query.trim_end().to_string() } } impl Insert { - #[cfg(not(feature = "sqlite"))] fn concat_insert_into(&self, query: String, fmts: &fmt::Formatter) -> String { let fmt::Formatter { lb, space, .. } = fmts; let sql = if self._insert_into.is_empty() == false { - let insert_into = &self._insert_into; - format!("INSERT INTO{space}{insert_into}{space}{lb}") + let expression = &self._insert_into; + format!("INSERT INTO{space}{expression}{space}{lb}") } else { "".to_string() }; @@ -76,6 +196,7 @@ impl Insert { ) } + #[cfg(not(any(feature = "sqlite", feature = "mysql")))] fn concat_overriding(&self, query: String, fmts: &fmt::Formatter) -> String { let fmt::Formatter { lb, space, .. } = fmts; let sql = if self._overriding.is_empty() == false { @@ -95,64 +216,81 @@ impl Insert { ) } - fn concat_on_conflict(&self, query: String, fmts: &fmt::Formatter) -> String { + fn concat_select(&self, query: String, fmts: &fmt::Formatter) -> String { let fmt::Formatter { lb, space, .. } = fmts; - let sql = if self._on_conflict.is_empty() == false { - let overriding = &self._on_conflict; - format!("ON CONFLICT{space}{overriding}{space}{lb}") - } else { - "".to_string() - }; + let sql = self._select.as_ref().map_or("".to_string(), |select| { + let select_string = select.concat(fmts); + format!("{select_string}{space}{lb}") + }); concat_raw_before_after( &self._raw_before, &self._raw_after, query, fmts, - InsertClause::OnConflict, + InsertClause::Select, sql, ) } - fn concat_select(&self, query: String, fmts: &fmt::Formatter) -> String { + #[cfg(not(feature = "mysql"))] + fn concat_default_values(&self, query: String, fmts: &fmt::Formatter) -> String { let fmt::Formatter { lb, space, .. } = fmts; - let sql = if let Some(select) = &self._select { - let select_string = select.concat(fmts); - format!("{select_string}{space}{lb}") - } else { - "".to_string() - }; + let sql = format!("DEFAULT VALUES{space}{lb}"); concat_raw_before_after( &self._raw_before, &self._raw_after, query, fmts, - InsertClause::Select, + InsertClause::DefaultValues, sql, ) } fn concat_values(&self, query: String, fmts: &fmt::Formatter) -> String { let fmt::Formatter { comma, lb, space, .. } = fmts; - - let (clause, sql) = if self._default_values { - (InsertClause::DefaultValues, format!("DEFAULT VALUES{space}{lb}")) - } else if self._values.is_empty() == false { + let sql = if self._values.is_empty() == false { let sep = format!("{comma}{lb}"); - let values = self + let rows = self ._values .iter() .filter(|item| item.is_empty() == false) - .map(|item| item.as_str()) + .map(|item| { + #[cfg(not(feature = "mysql"))] + { + item.clone() + } + + #[cfg(feature = "mysql")] + { + if self._values_variance == ValuesVariance::InsertValuesRow { + format!("ROW{item}") + } else { + item.clone() + } + } + }) .collect::>() .join(&sep); - (InsertClause::Values, format!("VALUES{space}{lb}{values}{space}{lb}")) + + if rows.is_empty() == true { + return "".to_string(); + } + + format!("VALUES{space}{lb}{rows}{space}{lb}") } else { - (InsertClause::Values, "".to_string()) + "".to_string() }; - concat_raw_before_after(&self._raw_before, &self._raw_after, query, fmts, clause, sql) + concat_raw_before_after( + &self._raw_before, + &self._raw_after, + query, + fmts, + InsertClause::Values, + sql, + ) } } @@ -164,8 +302,138 @@ impl ConcatReturning for Insert {} #[cfg(any(feature = "postgresql", feature = "sqlite"))] impl ConcatWith for Insert {} -#[cfg(feature = "sqlite")] -use crate::concat::sqlite::ConcatInsert; +#[cfg(any(feature = "postgresql", feature = "sqlite"))] +impl Insert { + fn concat_on_conflict(&self, query: String, fmts: &fmt::Formatter) -> String { + let fmt::Formatter { lb, space, .. } = fmts; + let sql = if self._on_conflict.is_empty() == false { + let overriding = &self._on_conflict; + format!("ON CONFLICT{space}{overriding}{space}{lb}") + } else { + "".to_string() + }; + + concat_raw_before_after( + &self._raw_before, + &self._raw_after, + query, + fmts, + InsertClause::OnConflict, + sql, + ) + } +} #[cfg(feature = "sqlite")] -impl ConcatInsert for Insert {} +impl Insert { + fn concat_insert_or(&self, query: String, fmts: &fmt::Formatter) -> String { + let fmt::Formatter { lb, space, .. } = fmts; + let sql = if self._insert_or.is_empty() == false { + let expression = &self._insert_or; + format!("INSERT OR{space}{expression}{space}{lb}") + } else { + "".to_string() + }; + + concat_raw_before_after( + &self._raw_before, + &self._raw_after, + query, + fmts, + InsertClause::InsertOr, + sql, + ) + } + + fn concat_replace_into(&self, query: String, fmts: &fmt::Formatter) -> String { + let fmt::Formatter { lb, space, .. } = fmts; + let sql = if self._replace_into.is_empty() == false { + let table_name = &self._replace_into; + format!("REPLACE INTO{space}{table_name}{space}{lb}") + } else { + "".to_string() + }; + + concat_raw_before_after( + &self._raw_before, + &self._raw_after, + query, + fmts, + InsertClause::ReplaceInto, + sql, + ) + } +} + +#[cfg(feature = "mysql")] +use crate::{ + concat::{mysql::ConcatPartition, non_standard::ConcatColumn, sql_standard::ConcatSet}, + utils, +}; + +#[cfg(feature = "mysql")] +impl ConcatColumn for Insert {} +#[cfg(feature = "mysql")] +impl ConcatPartition for Insert {} +#[cfg(feature = "mysql")] +impl ConcatSet for Insert {} + +#[cfg(feature = "mysql")] +impl Insert { + fn concat_insert(&self, query: String, fmts: &fmt::Formatter) -> String { + let fmt::Formatter { space, .. } = fmts; + let sql = if self._insert.is_empty() == false { + let modifiers = &self._insert; + format!("INSERT{space}{modifiers}{space}") + } else { + "".to_string() + }; + + concat_raw_before_after( + &self._raw_before, + &self._raw_after, + query, + fmts, + InsertClause::Insert, + sql, + ) + } + + fn concat_into(&self, query: String, fmts: &fmt::Formatter) -> String { + let fmt::Formatter { space, .. } = fmts; + let sql = if self._into.is_empty() == false { + let table_name = &self._into; + format!("INTO{space}{table_name}{space}") + } else { + "".to_string() + }; + + concat_raw_before_after( + &self._raw_before, + &self._raw_after, + query, + fmts, + InsertClause::Into, + sql, + ) + } + + fn concat_on_duplicate_key_update(&self, query: String, fmts: &fmt::Formatter) -> String { + let fmt::Formatter { comma, lb, space, .. } = fmts; + let sql = if self._on_duplicate_key_update.is_empty() == false { + let values = utils::join(&self._on_duplicate_key_update, comma); + format!("ON DUPLICATE KEY UPDATE{space}{values}{space}{lb}") + } else { + "".to_string() + }; + + concat_raw_before_after( + &self._raw_before, + &self._raw_after, + query, + fmts, + InsertClause::OnDuplicateKeyUpdate, + sql, + ) + } +} diff --git a/src/lib.rs b/src/lib.rs index a8e2f54..60c6357 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,10 +21,10 @@ pub use crate::structure::{ Insert, InsertClause, Select, SelectClause, Transaction, Update, UpdateClause, Values, ValuesClause, }; -#[cfg(any(feature = "postgresql", feature = "sqlite"))] +#[cfg(any(feature = "postgresql", feature = "sqlite", feature = "mysql"))] mod create_index; -#[cfg(any(feature = "postgresql", feature = "sqlite"))] +#[cfg(any(feature = "postgresql", feature = "sqlite", feature = "mysql"))] mod drop_index; -#[cfg(any(feature = "postgresql", feature = "sqlite"))] +#[cfg(any(feature = "postgresql", feature = "sqlite", feature = "mysql"))] pub use crate::structure::{CreateIndex, CreateIndexParams, DropIndex, DropIndexParams}; diff --git a/src/select/select.rs b/src/select/select.rs index 7127608..f436baf 100644 --- a/src/select/select.rs +++ b/src/select/select.rs @@ -99,10 +99,10 @@ impl Select { /// .from("users"); /// /// # let expected = "FROM users"; - /// # assert_eq!(select.as_string(), expected); + /// # assert_eq!(expected, select.as_string()); /// ``` - pub fn from(mut self, tables: &str) -> Self { - push_unique(&mut self._from, tables.trim().to_string()); + pub fn from(mut self, table: &str) -> Self { + push_unique(&mut self._from, table.trim().to_string()); self } @@ -116,7 +116,7 @@ impl Select { /// .group_by("id"); /// /// # let expected = "GROUP BY id"; - /// # assert_eq!(select.as_string(), expected); + /// # assert_eq!(expected, select.as_string()); /// ``` pub fn group_by(mut self, column: &str) -> Self { push_unique(&mut self._group_by, column.trim().to_string()); @@ -280,7 +280,7 @@ impl Select { /// .order_by("login asc"); /// /// # let expected = "SELECT name, login ORDER BY login asc"; - /// # assert_eq!(select.as_string(), expected); + /// # assert_eq!(expected, select.as_string()); /// ``` pub fn order_by(mut self, column: &str) -> Self { push_unique(&mut self._order_by, column.trim().to_string()); @@ -397,7 +397,7 @@ impl Select { /// .select("count(id)"); /// /// # let expected = "SELECT count(id)"; - /// # assert_eq!(select.as_string(), expected); + /// # assert_eq!(expected, select.as_string()); /// ``` pub fn select(mut self, column: &str) -> Self { push_unique(&mut self._select, column.trim().to_string()); @@ -565,22 +565,23 @@ impl Select { } } -#[cfg(any(feature = "postgresql", feature = "sqlite"))] +#[cfg(any(feature = "postgresql", feature = "sqlite", feature = "mysql"))] use crate::behavior::WithQuery; -#[cfg(any(feature = "postgresql", feature = "sqlite"))] +#[cfg(any(feature = "postgresql", feature = "sqlite", feature = "mysql"))] impl WithQuery for Select {} -#[cfg(any(doc, feature = "postgresql", feature = "sqlite"))] +#[cfg(any(doc, feature = "postgresql", feature = "sqlite", feature = "mysql"))] #[cfg_attr(docsrs, doc(cfg(feature = "postgresql")))] #[cfg_attr(docsrs, doc(cfg(feature = "sqlite")))] +#[cfg_attr(docsrs, doc(cfg(feature = "mysql")))] impl Select { /// The `except` clause /// /// # Example /// /// ``` - /// # #[cfg(any(feature = "postgresql", feature = "sqlite"))] + /// # #[cfg(any(feature = "postgresql", feature = "sqlite", feature = "mysql"))] /// # { /// # use sql_query_builder as sql; /// let select_users = sql::Select::new() @@ -620,7 +621,7 @@ impl Select { /// # Example /// /// ``` - /// # #[cfg(any(feature = "postgresql", feature = "sqlite"))] + /// # #[cfg(any(feature = "postgresql", feature = "sqlite", feature = "mysql"))] /// # { /// # use sql_query_builder as sql; /// let select_users = sql::Select::new() @@ -660,7 +661,7 @@ impl Select { /// # Example /// /// ``` - /// # #[cfg(any(feature = "postgresql", feature = "sqlite"))] + /// # #[cfg(any(feature = "postgresql", feature = "sqlite", feature = "mysql"))] /// # { /// # use sql_query_builder as sql; /// let select = sql::Select::new() @@ -671,7 +672,7 @@ impl Select { /// .limit("123"); /// /// # let expected = "LIMIT 123"; - /// # assert_eq!(select.as_string(), expected); + /// # assert_eq!(expected, select.as_string()); /// # } /// ``` pub fn limit(mut self, num: &str) -> Self { @@ -684,7 +685,7 @@ impl Select { /// # Example /// /// ``` - /// # #[cfg(any(feature = "postgresql", feature = "sqlite"))] + /// # #[cfg(any(feature = "postgresql", feature = "sqlite", feature = "mysql"))] /// # { /// # use sql_query_builder as sql; /// let select = sql::Select::new() @@ -695,7 +696,7 @@ impl Select { /// .offset("1500"); /// /// # let expected = "OFFSET 1500"; - /// # assert_eq!(select.as_string(), expected); + /// # assert_eq!(expected, select.as_string()); /// # } /// ``` pub fn offset(mut self, num: &str) -> Self { @@ -708,7 +709,7 @@ impl Select { /// # Example /// /// ``` - /// # #[cfg(any(feature = "postgresql", feature = "sqlite"))] + /// # #[cfg(any(feature = "postgresql", feature = "sqlite", feature = "mysql"))] /// # { /// # use sql_query_builder as sql; /// let select_users = sql::Select::new() @@ -748,7 +749,7 @@ impl Select { /// # Example /// /// ``` - /// # #[cfg(any(feature = "postgresql", feature = "sqlite"))] + /// # #[cfg(any(feature = "postgresql", feature = "sqlite", feature = "mysql"))] /// # { /// # use sql_query_builder as sql; /// let logins = sql::Select::new() @@ -773,7 +774,7 @@ impl Select { /// # FROM orders \ /// # WHERE owner_login in (select * from logins)\ /// # "; - /// # assert_eq!(select.as_string(), expected); + /// # assert_eq!(expected, select.as_string()); /// # } /// ``` /// @@ -792,13 +793,46 @@ impl Select { /// WHERE owner_login in (select * from logins) /// -- ------------------------------------------------------------------------------ /// ``` - #[cfg(any(feature = "postgresql", feature = "sqlite"))] + #[cfg(any(feature = "postgresql", feature = "sqlite", feature = "mysql"))] pub fn with(mut self, name: &str, query: impl WithQuery + Send + Sync + 'static) -> Self { self._with.push((name.trim().to_string(), std::sync::Arc::new(query))); self } } +#[cfg(any(doc, feature = "mysql"))] +#[cfg_attr(docsrs, doc(cfg(feature = "mysql")))] +impl Select { + /// The `partition` clause + /// + /// # Example + /// + /// ``` + /// # #[cfg(feature = "mysql")] + /// # { + /// # use sql_query_builder as sql; + /// let query = sql::Select::new() + /// .select("*") + /// .from("employees") + /// .partition("p1") + /// .to_string(); + /// + /// # let expected_query = "SELECT * FROM employees PARTITION (p1)"; + /// # assert_eq!(expected_query, query); + /// # } + /// ``` + /// + /// Output + /// + /// ```sql + /// SELECT * FROM employees PARTITION (p1) + /// ``` + pub fn partition(mut self, name: &str) -> Self { + push_unique(&mut self._partition, name.trim().to_string()); + self + } +} + impl std::fmt::Display for Select { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "{}", self.as_string()) diff --git a/src/select/select_internal.rs b/src/select/select_internal.rs index d039d1c..7d82fb4 100644 --- a/src/select/select_internal.rs +++ b/src/select/select_internal.rs @@ -6,6 +6,7 @@ use crate::{ }, fmt, structure::{Select, SelectClause}, + utils, }; impl ConcatFrom for Select {} @@ -17,10 +18,52 @@ impl Concat for Select { fn concat(&self, fmts: &fmt::Formatter) -> String { let mut query = "".to_string(); - query = self.concat_raw(query, &fmts, &self._raw); + #[cfg(not(any(feature = "postgresql", feature = "sqlite", feature = "mysql")))] + { + query = self.concat_raw(query, &fmts, &self._raw); + query = self.concat_select(query, &fmts); + query = self.concat_from( + &self._raw_before, + &self._raw_after, + query, + &fmts, + SelectClause::From, + &self._from, + ); + query = self.concat_join( + &self._raw_before, + &self._raw_after, + query, + &fmts, + SelectClause::Join, + &self._join, + ); + query = self.concat_where( + &self._raw_before, + &self._raw_after, + query, + &fmts, + SelectClause::Where, + &self._where, + ); + query = self.concat_group_by(query, &fmts); + query = self.concat_having(query, &fmts); + query = self.concat_window(query, &fmts); + query = self.concat_order_by( + &self._raw_before, + &self._raw_after, + query, + &fmts, + SelectClause::OrderBy, + &self._order_by, + ); + } - #[cfg(any(feature = "postgresql", feature = "sqlite"))] + #[cfg(feature = "postgresql")] { + use crate::structure::Combinator; + + query = self.concat_raw(query, &fmts, &self._raw); query = self.concat_with( &self._raw_before, &self._raw_after, @@ -29,48 +72,105 @@ impl Concat for Select { SelectClause::With, &self._with, ); + query = self.concat_select(query, &fmts); + query = self.concat_from( + &self._raw_before, + &self._raw_after, + query, + &fmts, + SelectClause::From, + &self._from, + ); + query = self.concat_join( + &self._raw_before, + &self._raw_after, + query, + &fmts, + SelectClause::Join, + &self._join, + ); + query = self.concat_where( + &self._raw_before, + &self._raw_after, + query, + &fmts, + SelectClause::Where, + &self._where, + ); + query = self.concat_group_by(query, &fmts); + query = self.concat_having(query, &fmts); + query = self.concat_window(query, &fmts); + query = self.concat_order_by( + &self._raw_before, + &self._raw_after, + query, + &fmts, + SelectClause::OrderBy, + &self._order_by, + ); + query = self.concat_limit( + &self._raw_before, + &self._raw_after, + query, + &fmts, + SelectClause::Limit, + &self._limit, + ); + query = self.concat_offset(query, &fmts); + query = self.concat_combinator(query, &fmts, Combinator::Except); + query = self.concat_combinator(query, &fmts, Combinator::Intersect); + query = self.concat_combinator(query, &fmts, Combinator::Union); } - query = self.concat_select(query, &fmts); - query = self.concat_from( - &self._raw_before, - &self._raw_after, - query, - &fmts, - SelectClause::From, - &self._from, - ); - query = self.concat_join( - &self._raw_before, - &self._raw_after, - query, - &fmts, - SelectClause::Join, - &self._join, - ); - - query = self.concat_where( - &self._raw_before, - &self._raw_after, - query, - &fmts, - SelectClause::Where, - &self._where, - ); - query = self.concat_group_by(query, &fmts); - query = self.concat_having(query, &fmts); - query = self.concat_window(query, &fmts); - query = self.concat_order_by( - &self._raw_before, - &self._raw_after, - query, - &fmts, - SelectClause::OrderBy, - &self._order_by, - ); - - #[cfg(any(feature = "postgresql", feature = "sqlite"))] + #[cfg(feature = "sqlite")] { + use crate::structure::Combinator; + + query = self.concat_raw(query, &fmts, &self._raw); + query = self.concat_with( + &self._raw_before, + &self._raw_after, + query, + &fmts, + SelectClause::With, + &self._with, + ); + query = self.concat_select(query, &fmts); + query = self.concat_from( + &self._raw_before, + &self._raw_after, + query, + &fmts, + SelectClause::From, + &self._from, + ); + query = self.concat_join( + &self._raw_before, + &self._raw_after, + query, + &fmts, + SelectClause::Join, + &self._join, + ); + query = self.concat_where( + &self._raw_before, + &self._raw_after, + query, + &fmts, + SelectClause::Where, + &self._where, + ); + query = self.concat_group_by(query, &fmts); + query = self.concat_having(query, &fmts); + query = self.concat_window(query, &fmts); + query = self.concat_order_by( + &self._raw_before, + &self._raw_after, + query, + &fmts, + SelectClause::OrderBy, + &self._order_by, + ); query = self.concat_limit( &self._raw_before, &self._raw_after, @@ -80,11 +180,77 @@ impl Concat for Select { &self._limit, ); query = self.concat_offset(query, &fmts); + query = self.concat_combinator(query, &fmts, Combinator::Except); + query = self.concat_combinator(query, &fmts, Combinator::Intersect); + query = self.concat_combinator(query, &fmts, Combinator::Union); } - #[cfg(any(feature = "postgresql", feature = "sqlite"))] + #[cfg(feature = "mysql")] { use crate::structure::Combinator; + + query = self.concat_raw(query, &fmts, &self._raw); + query = self.concat_with( + &self._raw_before, + &self._raw_after, + query, + &fmts, + SelectClause::With, + &self._with, + ); + query = self.concat_select(query, &fmts); + query = self.concat_from( + &self._raw_before, + &self._raw_after, + query, + &fmts, + SelectClause::From, + &self._from, + ); + query = self.concat_join( + &self._raw_before, + &self._raw_after, + query, + &fmts, + SelectClause::Join, + &self._join, + ); + query = self.concat_partition( + &self._raw_before, + &self._raw_after, + query, + &fmts, + SelectClause::Partition, + &self._partition, + ); + query = self.concat_where( + &self._raw_before, + &self._raw_after, + query, + &fmts, + SelectClause::Where, + &self._where, + ); + query = self.concat_group_by(query, &fmts); + query = self.concat_having(query, &fmts); + query = self.concat_window(query, &fmts); + query = self.concat_order_by( + &self._raw_before, + &self._raw_after, + query, + &fmts, + SelectClause::OrderBy, + &self._order_by, + ); + query = self.concat_limit( + &self._raw_before, + &self._raw_after, + query, + &fmts, + SelectClause::Limit, + &self._limit, + ); + query = self.concat_offset(query, &fmts); query = self.concat_combinator(query, &fmts, Combinator::Except); query = self.concat_combinator(query, &fmts, Combinator::Intersect); query = self.concat_combinator(query, &fmts, Combinator::Union); @@ -98,13 +264,7 @@ impl Select { fn concat_group_by(&self, query: String, fmts: &fmt::Formatter) -> String { let fmt::Formatter { comma, lb, space, .. } = fmts; let sql = if self._group_by.is_empty() == false { - let columns = self - ._group_by - .iter() - .filter(|column| column.is_empty() == false) - .map(|column| column.as_str()) - .collect::>() - .join(comma); + let columns = utils::join(&self._group_by, comma); format!("GROUP BY{space}{columns}{space}{lb}") } else { "".to_string() @@ -123,13 +283,7 @@ impl Select { fn concat_having(&self, query: String, fmts: &fmt::Formatter) -> String { let fmt::Formatter { lb, space, .. } = fmts; let sql = if self._having.is_empty() == false { - let conditions = self - ._having - .iter() - .filter(|item| item.is_empty() == false) - .map(|item| item.as_str()) - .collect::>() - .join(" AND "); + let conditions = utils::join(&self._having, " AND "); format!("HAVING{space}{conditions}{space}{lb}") } else { "".to_string() @@ -148,13 +302,7 @@ impl Select { fn concat_select(&self, query: String, fmts: &fmt::Formatter) -> String { let fmt::Formatter { comma, lb, space, .. } = fmts; let sql = if self._select.is_empty() == false { - let columns = self - ._select - .iter() - .filter(|item| item.is_empty() == false) - .map(|item| item.as_str()) - .collect::>() - .join(comma); + let columns = utils::join(&self._select, comma); format!("SELECT{space}{columns}{space}{lb}") } else { "".to_string() @@ -173,13 +321,7 @@ impl Select { fn concat_window(&self, query: String, fmts: &fmt::Formatter) -> String { let fmt::Formatter { comma, lb, space, .. } = fmts; let sql = if self._window.is_empty() == false { - let columns = self - ._window - .iter() - .filter(|item| item.is_empty() == false) - .map(|item| item.as_str()) - .collect::>() - .join(comma); + let columns = utils::join(&self._window, comma); format!("WINDOW{space}{columns}{space}{lb}") } else { "".to_string() @@ -196,15 +338,15 @@ impl Select { } } -#[cfg(any(feature = "postgresql", feature = "sqlite"))] +#[cfg(any(feature = "postgresql", feature = "sqlite", feature = "mysql"))] use crate::concat::non_standard::{ConcatLimit, ConcatWith}; -#[cfg(any(feature = "postgresql", feature = "sqlite"))] +#[cfg(any(feature = "postgresql", feature = "sqlite", feature = "mysql"))] impl ConcatWith for Select {} -#[cfg(any(feature = "postgresql", feature = "sqlite"))] +#[cfg(any(feature = "postgresql", feature = "sqlite", feature = "mysql"))] impl ConcatLimit for Select {} -#[cfg(any(feature = "postgresql", feature = "sqlite"))] +#[cfg(any(feature = "postgresql", feature = "sqlite", feature = "mysql"))] impl Select { fn concat_combinator( &self, @@ -253,7 +395,7 @@ impl Select { } } -#[cfg(any(feature = "postgresql", feature = "sqlite"))] +#[cfg(any(feature = "postgresql", feature = "sqlite", feature = "mysql"))] impl Select { fn concat_offset(&self, query: String, fmts: &fmt::Formatter) -> String { let fmt::Formatter { lb, space, .. } = fmts; @@ -274,3 +416,9 @@ impl Select { ) } } + +#[cfg(feature = "mysql")] +use crate::concat::mysql::ConcatPartition; + +#[cfg(feature = "mysql")] +impl ConcatPartition for Select {} diff --git a/src/structure.rs b/src/structure.rs index 2b0d0ce..256021d 100644 --- a/src/structure.rs +++ b/src/structure.rs @@ -1,12 +1,12 @@ use crate::behavior::TransactionQuery; -#[cfg(any(feature = "postgresql", feature = "sqlite"))] +#[cfg(any(feature = "postgresql", feature = "sqlite", feature = "mysql"))] use crate::behavior::WithQuery; -#[cfg(any(feature = "postgresql", feature = "sqlite"))] +#[cfg(any(feature = "postgresql", feature = "sqlite", feature = "mysql"))] use std::sync::Arc; -/// Builder to contruct a [AlterTable] command. +/// Builder of [AlterTable] command. /// /// Basic API /// @@ -51,8 +51,11 @@ pub(crate) enum AlterTableOrderedAction { Add, Drop, - #[cfg(any(feature = "postgresql"))] + #[cfg(any(feature = "postgresql", feature = "mysql"))] Alter, + + #[cfg(feature = "mysql")] + Rename, } /// All available params to be used in [AlterTable::raw_before] and [AlterTable::raw_after] methods on [AlterTable] builder @@ -77,14 +80,14 @@ pub enum AlterTableAction { Drop, } -#[cfg(any(feature = "postgresql", feature = "sqlite"))] +#[cfg(any(feature = "postgresql", feature = "sqlite", feature = "mysql"))] pub(crate) enum Combinator { Except, Intersect, Union, } -/// Builder to contruct a [CreateIndex] command. Available only for the crate features `postgresql` and `sqlite`. +/// Builder of [CreateIndex] command. Available only for the crate features `postgresql` and `sqlite`. /// /// Basic API /// @@ -109,34 +112,43 @@ pub(crate) enum Combinator { /// ```sql /// CREATE INDEX users_name_idx ON users (name) /// ``` -#[cfg(any(feature = "postgresql", feature = "sqlite"))] +#[cfg(any(feature = "postgresql", feature = "sqlite", feature = "mysql"))] #[derive(Default, Clone)] pub struct CreateIndex { pub(crate) _column: Vec, pub(crate) _index_name: String, pub(crate) _create_index: bool, - pub(crate) _if_not_exists: bool, pub(crate) _on: String, pub(crate) _raw_after: Vec<(CreateIndexParams, String)>, pub(crate) _raw_before: Vec<(CreateIndexParams, String)>, pub(crate) _raw: Vec, pub(crate) _unique: bool, + #[cfg(any(feature = "postgresql", feature = "sqlite"))] + pub(crate) _if_not_exists: bool, #[cfg(any(feature = "postgresql", feature = "sqlite"))] pub(crate) _where: Vec<(LogicalOperator, String)>, + #[cfg(any(feature = "postgresql", feature = "mysql"))] + pub(crate) _using: String, + #[cfg(feature = "postgresql")] pub(crate) _concurrently: bool, #[cfg(feature = "postgresql")] pub(crate) _include: Vec, #[cfg(feature = "postgresql")] pub(crate) _only: bool, - #[cfg(feature = "postgresql")] - pub(crate) _using: String, + + #[cfg(feature = "mysql")] + pub(crate) _fulltext: bool, + #[cfg(feature = "mysql")] + pub(crate) _lock: String, + #[cfg(feature = "mysql")] + pub(crate) _spatial: bool, } /// All available params to be used in [CreateIndex::raw_before] and [CreateIndex::raw_after] methods on [CreateIndex] builder -#[cfg(any(feature = "postgresql", feature = "sqlite"))] +#[cfg(any(feature = "postgresql", feature = "sqlite", feature = "mysql"))] #[derive(PartialEq, Clone)] pub enum CreateIndexParams { Column, @@ -147,17 +159,25 @@ pub enum CreateIndexParams { #[cfg(any(feature = "postgresql", feature = "sqlite"))] Where, + #[cfg(any(feature = "postgresql", feature = "mysql"))] + Using, + #[cfg(feature = "postgresql")] Concurrently, #[cfg(feature = "postgresql")] Only, #[cfg(feature = "postgresql")] - Using, - #[cfg(feature = "postgresql")] Include, + + #[cfg(feature = "mysql")] + Fulltext, + #[cfg(feature = "mysql")] + Lock, + #[cfg(feature = "mysql")] + Spatial, } -/// Builder to contruct a [CreateTable] command. +/// Builder of [CreateTable] command. /// /// Basic API /// @@ -214,12 +234,108 @@ pub enum CreateTableParams { PrimaryKey, } -/// Builder to contruct a [DropIndex] command. Available only for the crate features `postgresql` and `sqlite`. +/// Builder of [Delete] command. /// /// Basic API /// /// ``` -/// # #[cfg(any(feature = "postgresql", feature = "sqlite"))] +/// use sql_query_builder as sql; +/// +/// let query = sql::Delete::new() +/// .delete_from("users") +/// .where_clause("id = $1") +/// .as_string(); +/// +/// # let expected = "DELETE FROM users WHERE id = $1"; +/// # assert_eq!(expected, query); +/// ``` +/// +/// Output +/// +/// ```sql +/// DELETE FROM users WHERE id = $1 +/// ``` +#[derive(Default, Clone)] +pub struct Delete { + pub(crate) _delete_from: String, + pub(crate) _raw_after: Vec<(DeleteClause, String)>, + pub(crate) _raw_before: Vec<(DeleteClause, String)>, + pub(crate) _raw: Vec, + pub(crate) _where: Vec<(LogicalOperator, String)>, + + #[cfg(any(feature = "postgresql", feature = "sqlite", feature = "mysql"))] + pub(crate) _with: Vec<(String, std::sync::Arc)>, + + #[cfg(any(feature = "postgresql", feature = "sqlite"))] + pub(crate) _returning: Vec, + + #[cfg(feature = "mysql")] + pub(crate) _delete: Vec, + + #[cfg(feature = "mysql")] + pub(crate) _from: Vec, + + #[cfg(feature = "mysql")] + pub(crate) _join: Vec, + + #[cfg(feature = "mysql")] + pub(crate) _limit: String, + + #[cfg(feature = "mysql")] + pub(crate) _order_by: Vec, + + #[cfg(feature = "mysql")] + pub(crate) _partition: Vec, +} + +/// All available clauses to be used in [Delete::raw_before] and [Delete::raw_after] methods on [Delete] builder +#[derive(PartialEq, Clone)] +pub enum DeleteClause { + DeleteFrom, + Where, + + #[cfg(any(feature = "postgresql", feature = "sqlite", feature = "mysql"))] + #[cfg_attr(docsrs, doc(cfg(feature = "postgresql")))] + #[cfg_attr(docsrs, doc(cfg(feature = "sqlite")))] + #[cfg_attr(docsrs, doc(cfg(feature = "mysql")))] + With, + + #[cfg(any(feature = "postgresql", feature = "sqlite"))] + #[cfg_attr(docsrs, doc(cfg(feature = "postgresql")))] + #[cfg_attr(docsrs, doc(cfg(feature = "sqlite")))] + Returning, + + #[cfg(feature = "mysql")] + #[cfg_attr(docsrs, doc(cfg(feature = "mysql")))] + Limit, + + #[cfg(feature = "mysql")] + #[cfg_attr(docsrs, doc(cfg(feature = "mysql")))] + Delete, + + #[cfg(feature = "mysql")] + #[cfg_attr(docsrs, doc(cfg(feature = "mysql")))] + From, + + #[cfg(feature = "mysql")] + #[cfg_attr(docsrs, doc(cfg(feature = "mysql")))] + Join, + + #[cfg(feature = "mysql")] + #[cfg_attr(docsrs, doc(cfg(feature = "mysql")))] + OrderBy, + + #[cfg(feature = "mysql")] + #[cfg_attr(docsrs, doc(cfg(feature = "mysql")))] + Partition, +} + +/// Builder of [DropIndex] command. Available only for the crate features `postgresql` and `sqlite` and `mysql`. +/// +/// Basic API +/// +/// ``` +/// # #[cfg(any(feature = "postgresql", feature = "sqlite", feature = "mysql"))] /// # { /// use sql_query_builder as sql; /// @@ -238,7 +354,7 @@ pub enum CreateTableParams { /// ```sql /// DROP INDEX users_name_idx /// ``` -#[cfg(any(feature = "postgresql", feature = "sqlite"))] +#[cfg(any(feature = "postgresql", feature = "sqlite", feature = "mysql"))] #[derive(Default, Clone)] pub struct DropIndex { pub(crate) _drop_index: Vec, @@ -249,13 +365,13 @@ pub struct DropIndex { } /// All available params to be used in [DropIndex::raw_before] and [DropIndex::raw_after] methods on [DropIndex] builder -#[cfg(any(feature = "postgresql", feature = "sqlite"))] +#[cfg(any(feature = "postgresql", feature = "sqlite", feature = "mysql"))] #[derive(PartialEq, Clone)] pub enum DropIndexParams { DropIndex, } -/// Builder to contruct a [DropTable] command. +/// Builder of [DropTable] command. /// /// Basic API /// @@ -291,60 +407,7 @@ pub enum DropTableParams { DropTable, } -/// Builder to contruct a [Delete] command. -/// -/// Basic API -/// -/// ``` -/// use sql_query_builder as sql; -/// -/// let query = sql::Delete::new() -/// .delete_from("users") -/// .where_clause("id = $1") -/// .as_string(); -/// -/// # let expected = "DELETE FROM users WHERE id = $1"; -/// # assert_eq!(expected, query); -/// ``` -/// -/// Output -/// -/// ```sql -/// DELETE FROM users WHERE id = $1 -/// ``` -#[derive(Default, Clone)] -pub struct Delete { - pub(crate) _delete_from: String, - pub(crate) _raw_after: Vec<(DeleteClause, String)>, - pub(crate) _raw_before: Vec<(DeleteClause, String)>, - pub(crate) _raw: Vec, - pub(crate) _where: Vec<(LogicalOperator, String)>, - - #[cfg(any(feature = "postgresql", feature = "sqlite"))] - pub(crate) _returning: Vec, - - #[cfg(any(feature = "postgresql", feature = "sqlite"))] - pub(crate) _with: Vec<(String, std::sync::Arc)>, -} - -/// All available clauses to be used in [Delete::raw_before] and [Delete::raw_after] methods on [Delete] builder -#[derive(PartialEq, Clone)] -pub enum DeleteClause { - DeleteFrom, - Where, - - #[cfg(any(feature = "postgresql", feature = "sqlite"))] - #[cfg_attr(docsrs, doc(cfg(feature = "postgresql")))] - #[cfg_attr(docsrs, doc(cfg(feature = "sqlite")))] - Returning, - - #[cfg(any(feature = "postgresql", feature = "sqlite"))] - #[cfg_attr(docsrs, doc(cfg(feature = "postgresql")))] - #[cfg_attr(docsrs, doc(cfg(feature = "sqlite")))] - With, -} - -/// Builder to contruct a [Insert] command. +/// Builder of [Insert] command. /// /// Basic API /// @@ -368,14 +431,17 @@ pub enum DeleteClause { /// ``` #[derive(Default, Clone)] pub struct Insert { - pub(crate) _default_values: bool, - pub(crate) _on_conflict: String, - pub(crate) _overriding: String, + pub(crate) _insert_into: String, + pub(crate) _insert_variance: InsertVariance, + pub(crate) _raw: Vec, pub(crate) _raw_after: Vec<(InsertClause, String)>, pub(crate) _raw_before: Vec<(InsertClause, String)>, - pub(crate) _raw: Vec, pub(crate) _select: Option