diff --git a/Cargo.toml b/Cargo.toml index 44499a2..d608620 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ 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.6.2" +version = "2.7.2" edition = "2021" rust-version = "1.62" license = "MIT" diff --git a/src/concat/non_standard.rs b/src/concat/non_standard.rs index 0dc86e2..29b9f66 100644 --- a/src/concat/non_standard.rs +++ b/src/concat/non_standard.rs @@ -23,6 +23,28 @@ pub(crate) trait ConcatLimit { } } +#[cfg(any(feature = "postgresql", feature = "sqlite", feature = "mysql"))] +pub(crate) trait ConcatOffset { + fn concat_offset( + &self, + items_raw_before: &Vec<(Clause, String)>, + items_raw_after: &Vec<(Clause, String)>, + query: String, + fmts: &fmt::Formatter, + clause: Clause, + offset: &str, + ) -> String { + let fmt::Formatter { lb, space, .. } = fmts; + let sql = if offset.is_empty() == false { + format!("OFFSET{space}{offset}{space}{lb}") + } else { + "".to_string() + }; + + concat_raw_before_after(items_raw_before, items_raw_after, query, fmts, clause, sql) + } +} + #[cfg(any(feature = "postgresql", feature = "sqlite"))] pub(crate) trait ConcatReturning { fn concat_returning( diff --git a/src/delete/delete.rs b/src/delete/delete.rs index 8f9bc46..3e2535d 100644 --- a/src/delete/delete.rs +++ b/src/delete/delete.rs @@ -372,6 +372,38 @@ impl Delete { #[cfg_attr(docsrs, doc(cfg(feature = "sqlite")))] #[cfg_attr(docsrs, doc(cfg(feature = "mysql")))] impl Delete { + /// The `limit` clause, this method overrides the previous value + /// + /// # Example + /// + /// ``` + /// # #[cfg(any(feature = "sqlite", 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 + /// ``` + /// + /// Note: For crate feature `sqlite` this clause is behind a flag at SQLite, [more info](https://sqlite.org/lang_delete.html#optional_limit_and_order_by_clauses). + pub fn limit(mut self, num: &str) -> Self { + self._limit = num.trim().to_string(); + self + } + /// The `order by` clause. /// /// # Example @@ -401,6 +433,36 @@ impl Delete { } } +#[cfg(any(doc, feature = "sqlite"))] +#[cfg_attr(docsrs, doc(cfg(feature = "sqlite")))] +impl Delete { + /// The `offset` clause, this method overrides the previous value + /// + /// # Example + /// + /// ``` + /// # #[cfg(feature = "sqlite")] + /// # { + /// # use sql_query_builder as sql; + /// let delete = sql::Delete::new() + /// .offset("1500"); + /// + /// let delete = sql::Delete::new() + /// .offset("1000") + /// .offset("1500"); + /// + /// # let expected = "OFFSET 1500"; + /// # assert_eq!(expected, delete.as_string()); + /// # } + /// ``` + /// + /// Note: For crate feature `sqlite` this clause is behind a flag at SQLite, [more info](https://sqlite.org/lang_delete.html#optional_limit_and_order_by_clauses). + pub fn offset(mut self, num: &str) -> Self { + self._offset = num.trim().to_string(); + self + } +} + #[cfg(any(doc, feature = "mysql"))] #[cfg_attr(docsrs, doc(cfg(feature = "mysql")))] impl Delete { @@ -644,36 +706,6 @@ impl Delete { 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 `partition` clause /// /// # Example diff --git a/src/delete/delete_internal.rs b/src/delete/delete_internal.rs index d6c13f2..09c8ed3 100644 --- a/src/delete/delete_internal.rs +++ b/src/delete/delete_internal.rs @@ -90,6 +90,22 @@ impl Concat for Delete { DeleteClause::OrderBy, &self._order_by, ); + query = self.concat_limit( + &self._raw_before, + &self._raw_after, + query, + &fmts, + DeleteClause::Limit, + &self._limit, + ); + query = self.concat_offset( + &self._raw_before, + &self._raw_after, + query, + &fmts, + DeleteClause::Offset, + &self._offset, + ); } #[cfg(feature = "mysql")] @@ -183,15 +199,21 @@ use crate::concat::non_standard::ConcatReturning; impl ConcatReturning for Delete {} #[cfg(any(feature = "sqlite", feature = "mysql"))] -use crate::concat::sql_standard::ConcatOrderBy; +use crate::concat::{non_standard::ConcatLimit, sql_standard::ConcatOrderBy}; +#[cfg(any(feature = "sqlite", feature = "mysql"))] +impl ConcatLimit for Delete {} #[cfg(any(feature = "sqlite", feature = "mysql"))] impl ConcatOrderBy for Delete {} +#[cfg(feature = "sqlite")] +use crate::concat::non_standard::ConcatOffset; +#[cfg(feature = "sqlite")] +impl ConcatOffset for Delete {} + #[cfg(feature = "mysql")] use crate::{ concat::{ mysql::ConcatPartition, - non_standard::ConcatLimit, sql_standard::{ConcatFrom, ConcatJoin}, }, utils, @@ -201,8 +223,6 @@ impl ConcatFrom for Delete {} #[cfg(feature = "mysql")] impl ConcatJoin for Delete {} #[cfg(feature = "mysql")] -impl ConcatLimit for Delete {} -#[cfg(feature = "mysql")] impl ConcatPartition for Delete {} #[cfg(feature = "mysql")] diff --git a/src/select/select.rs b/src/select/select.rs index f436baf..865bfad 100644 --- a/src/select/select.rs +++ b/src/select/select.rs @@ -567,7 +567,6 @@ impl Select { #[cfg(any(feature = "postgresql", feature = "sqlite", feature = "mysql"))] use crate::behavior::WithQuery; - #[cfg(any(feature = "postgresql", feature = "sqlite", feature = "mysql"))] impl WithQuery for Select {} diff --git a/src/select/select_internal.rs b/src/select/select_internal.rs index 7d82fb4..491a4fe 100644 --- a/src/select/select_internal.rs +++ b/src/select/select_internal.rs @@ -116,7 +116,14 @@ impl Concat for Select { SelectClause::Limit, &self._limit, ); - query = self.concat_offset(query, &fmts); + query = self.concat_offset( + &self._raw_before, + &self._raw_after, + query, + &fmts, + SelectClause::Offset, + &self._offset, + ); query = self.concat_combinator(query, &fmts, Combinator::Except); query = self.concat_combinator(query, &fmts, Combinator::Intersect); query = self.concat_combinator(query, &fmts, Combinator::Union); @@ -179,7 +186,14 @@ impl Concat for Select { SelectClause::Limit, &self._limit, ); - query = self.concat_offset(query, &fmts); + query = self.concat_offset( + &self._raw_before, + &self._raw_after, + query, + &fmts, + SelectClause::Offset, + &self._offset, + ); query = self.concat_combinator(query, &fmts, Combinator::Except); query = self.concat_combinator(query, &fmts, Combinator::Intersect); query = self.concat_combinator(query, &fmts, Combinator::Union); @@ -250,7 +264,14 @@ impl Concat for Select { SelectClause::Limit, &self._limit, ); - query = self.concat_offset(query, &fmts); + query = self.concat_offset( + &self._raw_before, + &self._raw_after, + query, + &fmts, + SelectClause::Offset, + &self._offset, + ); query = self.concat_combinator(query, &fmts, Combinator::Except); query = self.concat_combinator(query, &fmts, Combinator::Intersect); query = self.concat_combinator(query, &fmts, Combinator::Union); @@ -339,12 +360,13 @@ impl Select { } #[cfg(any(feature = "postgresql", feature = "sqlite", feature = "mysql"))] -use crate::concat::non_standard::{ConcatLimit, ConcatWith}; - -#[cfg(any(feature = "postgresql", feature = "sqlite", feature = "mysql"))] -impl ConcatWith for Select {} +use crate::concat::non_standard::{ConcatLimit, ConcatOffset, ConcatWith}; #[cfg(any(feature = "postgresql", feature = "sqlite", feature = "mysql"))] impl ConcatLimit for Select {} +#[cfg(any(feature = "postgresql", feature = "sqlite", feature = "mysql"))] +impl ConcatOffset for Select {} +#[cfg(any(feature = "postgresql", feature = "sqlite", feature = "mysql"))] +impl ConcatWith for Select {} #[cfg(any(feature = "postgresql", feature = "sqlite", feature = "mysql"))] impl Select { @@ -395,30 +417,7 @@ impl Select { } } -#[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; - let sql = if self._offset.is_empty() == false { - let start = &self._offset; - format!("OFFSET{space}{start}{space}{lb}") - } else { - "".to_string() - }; - - concat_raw_before_after( - &self._raw_before, - &self._raw_after, - query, - fmts, - SelectClause::Offset, - sql, - ) - } -} - #[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 c458820..5534cd3 100644 --- a/src/structure.rs +++ b/src/structure.rs @@ -269,9 +269,15 @@ pub struct Delete { #[cfg(any(feature = "postgresql", feature = "sqlite"))] pub(crate) _returning: Vec, + #[cfg(any(feature = "sqlite", feature = "mysql"))] + pub(crate) _limit: String, + #[cfg(any(feature = "sqlite", feature = "mysql"))] pub(crate) _order_by: Vec, + #[cfg(feature = "sqlite")] + pub(crate) _offset: String, + #[cfg(feature = "mysql")] pub(crate) _delete: Vec, @@ -281,9 +287,6 @@ pub struct Delete { #[cfg(feature = "mysql")] pub(crate) _join: Vec, - #[cfg(feature = "mysql")] - pub(crate) _limit: String, - #[cfg(feature = "mysql")] pub(crate) _partition: Vec, } @@ -308,11 +311,16 @@ pub enum DeleteClause { #[cfg(any(feature = "sqlite", feature = "mysql"))] #[cfg_attr(docsrs, doc(cfg(feature = "sqlite")))] #[cfg_attr(docsrs, doc(cfg(feature = "mysql")))] - OrderBy, + Limit, - #[cfg(feature = "mysql")] + #[cfg(any(feature = "sqlite", feature = "mysql"))] + #[cfg_attr(docsrs, doc(cfg(feature = "sqlite")))] #[cfg_attr(docsrs, doc(cfg(feature = "mysql")))] - Limit, + OrderBy, + + #[cfg(feature = "sqlite")] + #[cfg_attr(docsrs, doc(cfg(feature = "sqlite")))] + Offset, #[cfg(feature = "mysql")] #[cfg_attr(docsrs, doc(cfg(feature = "mysql")))] @@ -821,9 +829,15 @@ pub struct Update { #[cfg(any(feature = "postgresql", feature = "sqlite"))] pub(crate) _with: Vec<(String, std::sync::Arc)>, + #[cfg(any(feature = "sqlite", feature = "mysql"))] + pub(crate) _limit: String, + #[cfg(any(feature = "sqlite", feature = "mysql"))] pub(crate) _order_by: Vec, + #[cfg(feature = "sqlite")] + pub(crate) _offset: String, + #[cfg(not(feature = "sqlite"))] pub(crate) _update: String, @@ -832,9 +846,6 @@ pub struct Update { #[cfg(feature = "sqlite")] pub(crate) _join: Vec, - - #[cfg(feature = "mysql")] - pub(crate) _limit: String, } #[cfg(feature = "sqlite")] @@ -867,11 +878,20 @@ pub enum UpdateClause { #[cfg_attr(docsrs, doc(cfg(feature = "sqlite")))] With, + #[cfg(any(feature = "sqlite", feature = "mysql"))] + #[cfg_attr(docsrs, doc(cfg(feature = "sqlite")))] + #[cfg_attr(docsrs, doc(cfg(feature = "mysql")))] + Limit, + #[cfg(any(feature = "sqlite", feature = "mysql"))] #[cfg_attr(docsrs, doc(cfg(feature = "sqlite")))] #[cfg_attr(docsrs, doc(cfg(feature = "mysql")))] OrderBy, + #[cfg(feature = "sqlite")] + #[cfg_attr(docsrs, doc(cfg(feature = "sqlite")))] + Offset, + #[cfg(feature = "sqlite")] #[cfg_attr(docsrs, doc(cfg(feature = "sqlite")))] UpdateOr, @@ -879,10 +899,6 @@ pub enum UpdateClause { #[cfg(feature = "sqlite")] #[cfg_attr(docsrs, doc(cfg(feature = "sqlite")))] Join, - - #[cfg(feature = "mysql")] - #[cfg_attr(docsrs, doc(cfg(feature = "mysql")))] - Limit, } /// Builder of [Values] command. diff --git a/src/update/update.rs b/src/update/update.rs index 5c3b418..30bf8fb 100644 --- a/src/update/update.rs +++ b/src/update/update.rs @@ -428,6 +428,32 @@ impl Update { #[cfg_attr(docsrs, doc(cfg(feature = "sqlite")))] #[cfg_attr(docsrs, doc(cfg(feature = "mysql")))] impl Update { + /// The `limit` clause, this method overrides the previous value + /// + /// # Example + /// + /// ``` + /// # #[cfg(any(feature = "sqlite", feature = "mysql"))] + /// # { + /// # use sql_query_builder as sql; + /// let update = sql::Update::new() + /// .limit("123"); + /// + /// let update = sql::Update::new() + /// .limit("1000") + /// .limit("123"); + /// + /// # let expected = "LIMIT 123"; + /// # assert_eq!(expected, update.as_string()); + /// # } + /// ``` + /// + /// Note: For crate feature `sqlite` this clause is behind a flag at SQLite, [more info](https://sqlite.org/lang_update.html#optional_limit_and_order_by_clauses). + pub fn limit(mut self, num: &str) -> Self { + self._limit = num.trim().to_string(); + self + } + /// The `order by` clause /// /// # Example @@ -627,30 +653,32 @@ impl Update { } } -#[cfg(any(doc, feature = "mysql"))] -#[cfg_attr(docsrs, doc(cfg(feature = "mysql")))] +#[cfg(any(doc, feature = "sqlite"))] +#[cfg_attr(docsrs, doc(cfg(feature = "sqlite")))] impl Update { - /// The `limit` clause, this method overrides the previous value + /// The `offset` clause, this method overrides the previous value /// /// # Example /// /// ``` - /// # #[cfg(feature = "mysql")] + /// # #[cfg(feature = "sqlite")] /// # { /// # use sql_query_builder as sql; /// let update = sql::Update::new() - /// .limit("123"); + /// .offset("1500"); /// /// let update = sql::Update::new() - /// .limit("1000") - /// .limit("123"); + /// .offset("1000") + /// .offset("1500"); /// - /// # let expected = "LIMIT 123"; + /// # let expected = "OFFSET 1500"; /// # assert_eq!(expected, update.as_string()); /// # } /// ``` - pub fn limit(mut self, num: &str) -> Self { - self._limit = num.trim().to_string(); + /// + /// Note: For crate feature `sqlite` this clause is behind a flag at SQLite, [more info](https://sqlite.org/lang_delete.html#optional_limit_and_order_by_clauses). + pub fn offset(mut self, num: &str) -> Self { + self._offset = num.trim().to_string(); self } } diff --git a/src/update/update_internal.rs b/src/update/update_internal.rs index f617a71..e4944db 100644 --- a/src/update/update_internal.rs +++ b/src/update/update_internal.rs @@ -144,6 +144,22 @@ impl Concat for Update { UpdateClause::OrderBy, &self._order_by, ); + query = self.concat_limit( + &self._raw_before, + &self._raw_after, + query, + &fmts, + UpdateClause::Limit, + &self._limit, + ); + query = self.concat_offset( + &self._raw_before, + &self._raw_after, + query, + &fmts, + UpdateClause::Offset, + &self._offset, + ); } #[cfg(feature = "mysql")] @@ -196,10 +212,17 @@ impl ConcatReturning for Update {} impl ConcatWith for Update {} #[cfg(any(feature = "sqlite", feature = "mysql"))] -use crate::concat::sql_standard::ConcatOrderBy; +use crate::concat::{non_standard::ConcatLimit, sql_standard::ConcatOrderBy}; +#[cfg(any(feature = "sqlite", feature = "mysql"))] +impl ConcatLimit for Update {} #[cfg(any(feature = "sqlite", feature = "mysql"))] impl ConcatOrderBy for Update {} +#[cfg(feature = "sqlite")] +use crate::concat::non_standard::ConcatOffset; +#[cfg(feature = "sqlite")] +impl ConcatOffset for Update {} + #[cfg(feature = "sqlite")] use crate::concat::sqlite::ConcatUpdate; #[cfg(feature = "sqlite")] @@ -229,8 +252,3 @@ impl Update { ) } } - -#[cfg(feature = "mysql")] -use crate::concat::non_standard::ConcatLimit; -#[cfg(feature = "mysql")] -impl ConcatLimit for Update {} diff --git a/tests/clause_limit_spec.rs b/tests/clause_limit_spec.rs index 2c60e1c..f8e4470 100644 --- a/tests/clause_limit_spec.rs +++ b/tests/clause_limit_spec.rs @@ -58,7 +58,7 @@ mod select_command { } } -#[cfg(feature = "mysql")] +#[cfg(any(feature = "sqlite", feature = "mysql"))] mod delete_command { use pretty_assertions::assert_eq; use sql_query_builder as sql; @@ -118,7 +118,7 @@ mod delete_command { } } -#[cfg(feature = "mysql")] +#[cfg(any(feature = "sqlite", feature = "mysql"))] mod update_command { use pretty_assertions::assert_eq; use sql_query_builder as sql; @@ -177,3 +177,17 @@ mod update_command { assert_eq!(expected_query, query); } } + +#[cfg(feature = "sqlite")] +mod update_command_sqlite { + use pretty_assertions::assert_eq; + use sql_query_builder as sql; + + #[test] + fn clause_limit_should_be_after_returning_clause() { + let query = sql::Update::new().limit("42").returning("*").as_string(); + let expected_query = "RETURNING * LIMIT 42"; + + assert_eq!(expected_query, query); + } +} diff --git a/tests/clause_offset_spec.rs b/tests/clause_offset_spec.rs index 76646f7..0b71f10 100644 --- a/tests/clause_offset_spec.rs +++ b/tests/clause_offset_spec.rs @@ -38,7 +38,7 @@ mod select_command { #[test] fn method_raw_before_should_add_raw_sql_before_offset_clause() { let query = sql::Select::new() - .raw_before(sql::SelectClause::Limit, "limit 1000") + .raw_before(sql::SelectClause::Offset, "limit 1000") .offset("50") .as_string(); let expected_query = "limit 1000 OFFSET 50"; @@ -57,3 +57,123 @@ mod select_command { assert_eq!(query, expected_query); } } + +#[cfg(feature = "sqlite")] +mod delete_command { + use pretty_assertions::assert_eq; + use sql_query_builder as sql; + + #[test] + fn method_offset_should_add_the_offset_clause() { + let query = sql::Delete::new().offset("100").as_string(); + let expected_query = "OFFSET 100"; + + assert_eq!(query, expected_query); + } + + #[test] + fn method_offset_should_override_the_current_value() { + let query = sql::Delete::new().offset("100").offset("200").as_string(); + let expected_query = "OFFSET 200"; + + assert_eq!(query, expected_query); + } + + #[test] + fn method_offset_should_trim_space_of_the_argument() { + let query = sql::Delete::new().offset(" 2000 ").as_string(); + let expected_query = "OFFSET 2000"; + + assert_eq!(query, expected_query); + } + + #[test] + fn clause_offset_should_be_after_limit_clause() { + let query = sql::Delete::new().limit("500").offset("100").as_string(); + let expected_query = "LIMIT 500 OFFSET 100"; + + assert_eq!(query, expected_query); + } + + #[test] + fn method_raw_before_should_add_raw_sql_before_offset_clause() { + let query = sql::Delete::new() + .raw_before(sql::DeleteClause::Offset, "limit 1000") + .offset("50") + .as_string(); + let expected_query = "limit 1000 OFFSET 50"; + + assert_eq!(query, expected_query); + } + + #[test] + fn method_raw_after_should_add_raw_sql_after_offset_clause() { + let query = sql::Delete::new() + .offset("10") + .raw_after(sql::DeleteClause::Offset, "/* the end */") + .as_string(); + let expected_query = "OFFSET 10 /* the end */"; + + assert_eq!(query, expected_query); + } +} + +#[cfg(feature = "sqlite")] +mod update_command { + use pretty_assertions::assert_eq; + use sql_query_builder as sql; + + #[test] + fn method_offset_should_add_the_offset_clause() { + let query = sql::Update::new().offset("100").as_string(); + let expected_query = "OFFSET 100"; + + assert_eq!(query, expected_query); + } + + #[test] + fn method_offset_should_override_the_current_value() { + let query = sql::Update::new().offset("100").offset("200").as_string(); + let expected_query = "OFFSET 200"; + + assert_eq!(query, expected_query); + } + + #[test] + fn method_offset_should_trim_space_of_the_argument() { + let query = sql::Update::new().offset(" 2000 ").as_string(); + let expected_query = "OFFSET 2000"; + + assert_eq!(query, expected_query); + } + + #[test] + fn clause_offset_should_be_after_limit_clause() { + let query = sql::Update::new().limit("500").offset("100").as_string(); + let expected_query = "LIMIT 500 OFFSET 100"; + + assert_eq!(query, expected_query); + } + + #[test] + fn method_raw_before_should_add_raw_sql_before_offset_clause() { + let query = sql::Update::new() + .raw_before(sql::UpdateClause::Offset, "limit 1000") + .offset("50") + .as_string(); + let expected_query = "limit 1000 OFFSET 50"; + + assert_eq!(query, expected_query); + } + + #[test] + fn method_raw_after_should_add_raw_sql_after_offset_clause() { + let query = sql::Update::new() + .offset("10") + .raw_after(sql::UpdateClause::Offset, "/* the end */") + .as_string(); + let expected_query = "OFFSET 10 /* the end */"; + + assert_eq!(query, expected_query); + } +}