diff --git a/Cargo.toml b/Cargo.toml index 17322b31..402a49a7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,11 +33,11 @@ tokio-postgres = { workspace = true, optional = true } tiberius = { workspace = true, optional = true } [workspace.dependencies] -canyon_crud = { version = "0.4.0", path = "canyon_crud" } -canyon_connection = { version = "0.4.0", path = "canyon_connection" } -canyon_entities = { version = "0.4.0", path = "canyon_entities" } -canyon_migrations = { version = "0.4.0", path = "canyon_migrations"} -canyon_macros = { version = "0.4.0", path = "canyon_macros" } +canyon_crud = { version = "0.4.1", path = "canyon_crud" } +canyon_connection = { version = "0.4.1", path = "canyon_connection" } +canyon_entities = { version = "0.4.1", path = "canyon_entities" } +canyon_migrations = { version = "0.4.1", path = "canyon_migrations"} +canyon_macros = { version = "0.4.1", path = "canyon_macros" } tokio = { version = "1.27.0", features = ["full"] } tokio-util = { version = "0.7.4", features = ["compat"] } @@ -61,7 +61,7 @@ quote = "1.0.9" proc-macro2 = "1.0.27" [workspace.package] -version = "0.4.0" +version = "0.4.1" edition = "2021" authors = ["Alex Vergara, Gonzalo Busto Musi"] documentation = "https://zerodaycode.github.io/canyon-book/" diff --git a/canyon_crud/src/query_elements/operators.rs b/canyon_crud/src/query_elements/operators.rs index 7a91e7ef..30637bad 100644 --- a/canyon_crud/src/query_elements/operators.rs +++ b/canyon_crud/src/query_elements/operators.rs @@ -1,5 +1,5 @@ pub trait Operator { - fn as_str(&self) -> &'static str; + fn as_str(&self, placeholder_counter: usize) -> String; } /// Enumerated type for represent the comparison operations @@ -18,15 +18,37 @@ pub enum Comp { /// Operator "=<" less or equals than value LtEq, } + impl Operator for Comp { - fn as_str(&self) -> &'static str { + fn as_str(&self, placeholder_counter: usize) -> String { + match *self { + Self::Eq => format!(" = ${placeholder_counter}"), + Self::Neq => format!(" <> ${placeholder_counter}"), + Self::Gt => format!(" > ${placeholder_counter}"), + Self::GtEq => format!(" >= ${placeholder_counter}"), + Self::Lt => format!(" < ${placeholder_counter}"), + Self::LtEq => format!(" <= ${placeholder_counter}"), + } + } +} + +pub enum Like { + /// Operator "LIKE" as '%pattern%' + Full, + /// Operator "LIKE" as '%pattern' + Left, + /// Operator "LIKE" as 'pattern%' + Right, +} + +impl Operator for Like { + fn as_str(&self, placeholder_counter: usize) -> String { match *self { - Self::Eq => " = ", - Self::Neq => " <> ", - Self::Gt => " > ", - Self::GtEq => " >= ", - Self::Lt => " < ", - Self::LtEq => " <= ", + Like::Full => { + format!(" LIKE CONCAT('%', CAST(${placeholder_counter} AS VARCHAR) ,'%')") + } + Like::Left => format!(" LIKE CONCAT('%', CAST(${placeholder_counter} AS VARCHAR))"), + Like::Right => format!(" LIKE CONCAT(CAST(${placeholder_counter} AS VARCHAR) ,'%')"), } } } diff --git a/canyon_crud/src/query_elements/query_builder.rs b/canyon_crud/src/query_elements/query_builder.rs index 92146542..ddcde0fd 100644 --- a/canyon_crud/src/query_elements/query_builder.rs +++ b/canyon_crud/src/query_elements/query_builder.rs @@ -180,11 +180,8 @@ where pub fn r#where>(&mut self, r#where: Z, op: impl Operator) { let (column_name, value) = r#where.value(); - let where_ = String::from(" WHERE ") - + column_name - + op.as_str() - + "$" - + &(self.query.params.len() + 1).to_string(); + let where_ = + String::from(" WHERE ") + column_name + &op.as_str(self.query.params.len() + 1); self.query.sql.push_str(&where_); self.query.params.push(value); @@ -193,12 +190,7 @@ where pub fn and>(&mut self, r#and: Z, op: impl Operator) { let (column_name, value) = r#and.value(); - let and_ = String::from(" AND ") - + column_name - + op.as_str() - + "$" - + &(self.query.params.len() + 1).to_string() - + " "; + let and_ = String::from(" AND ") + column_name + &op.as_str(self.query.params.len() + 1); self.query.sql.push_str(&and_); self.query.params.push(value); @@ -207,12 +199,7 @@ where pub fn or>(&mut self, r#and: Z, op: impl Operator) { let (column_name, value) = r#and.value(); - let and_ = String::from(" OR ") - + column_name - + op.as_str() - + "$" - + &(self.query.params.len() + 1).to_string() - + " "; + let and_ = String::from(" OR ") + column_name + &op.as_str(self.query.params.len() + 1); self.query.sql.push_str(&and_); self.query.params.push(value); @@ -246,7 +233,7 @@ where self.query.params.push(qp) }); - self.query.sql.push_str(") "); + self.query.sql.push_str(")"); } fn or_values_in(&mut self, r#or: Z, values: &'a [Q]) @@ -277,7 +264,7 @@ where self.query.params.push(qp) }); - self.query.sql.push_str(") "); + self.query.sql.push_str(")"); } #[inline] diff --git a/tests/crud/querybuilder_operations.rs b/tests/crud/querybuilder_operations.rs index 1c853161..4bc205f6 100644 --- a/tests/crud/querybuilder_operations.rs +++ b/tests/crud/querybuilder_operations.rs @@ -6,7 +6,7 @@ /// use canyon_sql::{ crud::CrudOperations, - query::{operators::Comp, ops::QueryBuilder}, + query::{operators::Comp, operators::Like, ops::QueryBuilder}, }; #[cfg(feature = "mssql")] @@ -34,7 +34,7 @@ fn test_generated_sql_by_the_select_querybuilder() { // generated SQL by the SelectQueryBuilder is the spected assert_eq!( select_with_joins.read_sql(), - "SELECT * FROM league INNER JOIN tournament ON league.id = tournament.league_id LEFT JOIN team ON tournament.id = player.tournament_id WHERE id > $1 AND name = $2 AND name IN ($2, $3) " + "SELECT * FROM league INNER JOIN tournament ON league.id = tournament.league_id LEFT JOIN team ON tournament.id = player.tournament_id WHERE id > $1 AND name = $2 AND name IN ($2, $3)" ) } @@ -59,6 +59,96 @@ fn test_crud_find_with_querybuilder() { assert_eq!(league_idx_0.region, "KOREA"); } +/// Builds a new SQL statement for retrieves entities of the `T` type, filtered +/// with the parameters that modifies the base SQL to SELECT * FROM +#[cfg(feature = "postgres")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_find_with_querybuilder_and_fulllike() { + // Find all the leagues with "LC" in their name + let mut filtered_leagues_result = League::select_query(); + filtered_leagues_result.r#where(LeagueFieldValue::name(&"LC"), Like::Full); + + assert_eq!( + filtered_leagues_result.read_sql(), + "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR) ,'%')" + ) +} + +/// Builds a new SQL statement for retrieves entities of the `T` type, filtered +/// with the parameters that modifies the base SQL to SELECT * FROM +#[cfg(feature = "mssql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_find_with_querybuilder_and_fulllike_datasource() { + // Find all the leagues with "LC" in their name + let mut filtered_leagues_result = League::select_query_datasource(SQL_SERVER_DS); + filtered_leagues_result.r#where(LeagueFieldValue::name(&"LC"), Like::Full); + + assert_eq!( + filtered_leagues_result.read_sql(), + "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR) ,'%')" + ) +} + +/// Builds a new SQL statement for retrieves entities of the `T` type, filtered +/// with the parameters that modifies the base SQL to SELECT * FROM +#[cfg(feature = "postgres")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_find_with_querybuilder_and_leftlike() { + // Find all the leagues whose name ends with "CK" + let mut filtered_leagues_result = League::select_query(); + filtered_leagues_result.r#where(LeagueFieldValue::name(&"CK"), Like::Left); + + assert_eq!( + filtered_leagues_result.read_sql(), + "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR))" + ) +} + +/// Builds a new SQL statement for retrieves entities of the `T` type, filtered +/// with the parameters that modifies the base SQL to SELECT * FROM +#[cfg(feature = "mssql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_find_with_querybuilder_and_leftlike_datasource() { + // Find all the leagues whose name ends with "CK" + let mut filtered_leagues_result = League::select_query(); + filtered_leagues_result.r#where(LeagueFieldValue::name(&"CK"), Like::Left); + + assert_eq!( + filtered_leagues_result.read_sql(), + "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR))" + ) +} + +/// Builds a new SQL statement for retrieves entities of the `T` type, filtered +/// with the parameters that modifies the base SQL to SELECT * FROM +#[cfg(feature = "postgres")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_find_with_querybuilder_and_rightlike() { + // Find all the leagues whose name starts with "LC" + let mut filtered_leagues_result = League::select_query(); + filtered_leagues_result.r#where(LeagueFieldValue::name(&"LC"), Like::Right); + + assert_eq!( + filtered_leagues_result.read_sql(), + "SELECT * FROM league WHERE name LIKE CONCAT(CAST($1 AS VARCHAR) ,'%')" + ) +} + +/// Builds a new SQL statement for retrieves entities of the `T` type, filtered +/// with the parameters that modifies the base SQL to SELECT * FROM +#[cfg(feature = "mssql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_find_with_querybuilder_and_rightlike_datasource() { + // Find all the leagues whose name starts with "LC" + let mut filtered_leagues_result = League::select_query_datasource(SQL_SERVER_DS); + filtered_leagues_result.r#where(LeagueFieldValue::name(&"LC"), Like::Right); + + assert_eq!( + filtered_leagues_result.read_sql(), + "SELECT * FROM league WHERE name LIKE CONCAT(CAST($1 AS VARCHAR) ,'%')" + ) +} + /// Same than the above but with the specified datasource #[cfg(feature = "mssql")] #[canyon_sql::macros::canyon_tokio_test] @@ -239,7 +329,7 @@ fn test_or_clause_with_in_constraint() { assert_eq!( l.read_sql(), - "SELECT * FROM league WHERE name = $1 OR id IN ($1, $2, $3) " + "SELECT * FROM league WHERE name = $1 OR id IN ($1, $2, $3)" ) }