diff --git a/src/visitor.rs b/src/visitor.rs index c5a3de7d2..b37d5fe4a 100644 --- a/src/visitor.rs +++ b/src/visitor.rs @@ -646,6 +646,70 @@ pub trait Visitor<'a> { self.visit_expression(right) } + fn visit_like(&mut self, left: Expression<'a>, right: Cow) -> Result { + self.visit_expression(left)?; + + self.add_parameter(Value::text(format!( + "{}{}{}", + Self::C_WILDCARD, + right, + Self::C_WILDCARD + ))); + + self.write(" LIKE ")?; + self.parameter_substitution() + } + + fn visit_not_like(&mut self, left: Expression<'a>, right: Cow) -> Result { + self.visit_expression(left)?; + + self.add_parameter(Value::text(format!( + "{}{}{}", + Self::C_WILDCARD, + right, + Self::C_WILDCARD + ))); + + self.write(" NOT LIKE ")?; + self.parameter_substitution() + } + + fn visit_begins_with(&mut self, left: Expression<'a>, right: Cow) -> Result { + self.visit_expression(left)?; + + self.add_parameter(Value::text(format!("{}{}", right, Self::C_WILDCARD))); + + self.write(" LIKE ")?; + self.parameter_substitution() + } + + fn visit_not_begins_with(&mut self, left: Expression<'a>, right: Cow) -> Result { + self.visit_expression(left)?; + + self.add_parameter(Value::text(format!("{}{}", right, Self::C_WILDCARD))); + + self.write(" NOT LIKE ")?; + self.parameter_substitution() + } + + fn visit_ends_with(&mut self, left: Expression<'a>, right: Cow) -> Result { + self.visit_expression(left)?; + + self.add_parameter(Value::text(format!("{}{}", Self::C_WILDCARD, right,))); + + self.write(" LIKE ")?; + self.parameter_substitution() + } + + fn visit_not_ends_with(&mut self, left: Expression<'a>, right: Cow) -> Result { + self.visit_expression(left)?; + + self.add_parameter(Value::text(format!("{}{}", Self::C_WILDCARD, right,))); + + self.write(" NOT LIKE ")?; + self.parameter_substitution() + } + /// A comparison expression fn visit_compare(&mut self, compare: Compare<'a>) -> Result { match compare { @@ -799,64 +863,12 @@ pub trait Visitor<'a> { self.visit_expression(right) } }, - Compare::Like(left, right) => { - self.visit_expression(*left)?; - - self.add_parameter(Value::text(format!( - "{}{}{}", - Self::C_WILDCARD, - right, - Self::C_WILDCARD - ))); - - self.write(" LIKE ")?; - self.parameter_substitution() - } - Compare::NotLike(left, right) => { - self.visit_expression(*left)?; - - self.add_parameter(Value::text(format!( - "{}{}{}", - Self::C_WILDCARD, - right, - Self::C_WILDCARD - ))); - - self.write(" NOT LIKE ")?; - self.parameter_substitution() - } - Compare::BeginsWith(left, right) => { - self.visit_expression(*left)?; - - self.add_parameter(Value::text(format!("{}{}", right, Self::C_WILDCARD))); - - self.write(" LIKE ")?; - self.parameter_substitution() - } - Compare::NotBeginsWith(left, right) => { - self.visit_expression(*left)?; - - self.add_parameter(Value::text(format!("{}{}", right, Self::C_WILDCARD))); - - self.write(" NOT LIKE ")?; - self.parameter_substitution() - } - Compare::EndsInto(left, right) => { - self.visit_expression(*left)?; - - self.add_parameter(Value::text(format!("{}{}", Self::C_WILDCARD, right,))); - - self.write(" LIKE ")?; - self.parameter_substitution() - } - Compare::NotEndsInto(left, right) => { - self.visit_expression(*left)?; - - self.add_parameter(Value::text(format!("{}{}", Self::C_WILDCARD, right,))); - - self.write(" NOT LIKE ")?; - self.parameter_substitution() - } + Compare::Like(left, right) => self.visit_like(*left, right), + Compare::NotLike(left, right) => self.visit_not_like(*left, right), + Compare::BeginsWith(left, right) => self.visit_begins_with(*left, right), + Compare::NotBeginsWith(left, right) => self.visit_not_begins_with(*left, right), + Compare::EndsInto(left, right) => self.visit_ends_with(*left, right), + Compare::NotEndsInto(left, right) => self.visit_not_ends_with(*left, right), Compare::Null(column) => { self.visit_expression(*column)?; self.write(" IS NULL") diff --git a/src/visitor/postgres.rs b/src/visitor/postgres.rs index 3deac0050..641e3ed33 100644 --- a/src/visitor/postgres.rs +++ b/src/visitor/postgres.rs @@ -1,3 +1,4 @@ +use std::borrow::Cow; use crate::{ ast::*, visitor::{self, Visitor}, @@ -416,6 +417,102 @@ impl<'a> Visitor<'a> for Postgres<'a> { Ok(()) } + + fn visit_like(&mut self, left: Expression<'a>, right: Cow) -> visitor::Result { + let need_cast = matches!(&left.kind, ExpressionKind::Column(_)); + self.visit_expression(left)?; + + // NOTE: Pg is strongly typed, LIKE comparisons are only between strings. + // to avoid problems with types without implicit casting we explicitly cast to text + if need_cast { + self.write("::text")?; + } + + self.add_parameter(Value::text(format!( + "{}{}{}", + Self::C_WILDCARD, + right, + Self::C_WILDCARD + ))); + + self.write(" LIKE ")?; + self.parameter_substitution() + } + + fn visit_not_like(&mut self, left: Expression<'a>, right: Cow) -> visitor::Result { + let need_cast = matches!(&left.kind, ExpressionKind::Column(_)); + self.visit_expression(left)?; + + if need_cast { + self.write("::text")?; + } + + self.add_parameter(Value::text(format!( + "{}{}{}", + Self::C_WILDCARD, + right, + Self::C_WILDCARD + ))); + + self.write(" NOT LIKE ")?; + self.parameter_substitution() + } + + fn visit_begins_with(&mut self, left: Expression<'a>, right: Cow) -> visitor::Result { + let need_cast = matches!(&left.kind, ExpressionKind::Column(_)); + self.visit_expression(left)?; + + if need_cast { + self.write("::text")?; + } + + self.add_parameter(Value::text(format!("{}{}", right, Self::C_WILDCARD))); + + self.write(" LIKE ")?; + self.parameter_substitution() + } + + fn visit_not_begins_with(&mut self, left: Expression<'a>, right: Cow) -> visitor::Result { + let need_cast = matches!(&left.kind, ExpressionKind::Column(_)); + self.visit_expression(left)?; + + if need_cast { + self.write("::text")?; + } + + self.add_parameter(Value::text(format!("{}{}", right, Self::C_WILDCARD))); + + self.write(" NOT LIKE ")?; + self.parameter_substitution() + } + + fn visit_ends_with(&mut self, left: Expression<'a>, right: Cow) -> visitor::Result { + let need_cast = matches!(&left.kind, ExpressionKind::Column(_)); + self.visit_expression(left)?; + + if need_cast { + self.write("::text")?; + } + + self.add_parameter(Value::text(format!("{}{}", Self::C_WILDCARD, right,))); + + self.write(" LIKE ")?; + self.parameter_substitution() + } + + fn visit_not_ends_with(&mut self, left: Expression<'a>, right: Cow) -> visitor::Result { + let need_cast = matches!(&left.kind, ExpressionKind::Column(_)); + self.visit_expression(left)?; + + if need_cast { + self.write("::text")?; + } + + self.add_parameter(Value::text(format!("{}{}", Self::C_WILDCARD, right,))); + + self.write(" NOT LIKE ")?; + self.parameter_substitution() + } } #[cfg(test)] @@ -813,6 +910,102 @@ mod tests { assert_eq!(r#"SELECT "foo".* FROM "foo" WHERE "bar" ILIKE $1"#, sql); } + #[test] + fn test_like_cast_to_string() { + let expected = expected_values( + r#"SELECT "test".* FROM "test" WHERE "jsonField"::text LIKE $1"#, + vec!["%foo%"], + ); + + let query = + Select::from_table("test") + .so_that(Column::from("jsonField").like("foo")); + let (sql, params) = Postgres::build(query).unwrap(); + + assert_eq!(expected.0, sql); + assert_eq!(expected.1, params); + } + + #[test] + fn test_not_like_cast_to_string() { + let expected = expected_values( + r#"SELECT "test".* FROM "test" WHERE "jsonField"::text NOT LIKE $1"#, + vec!["%foo%"], + ); + + let query = + Select::from_table("test") + .so_that(Column::from("jsonField").not_like("foo")); + let (sql, params) = Postgres::build(query).unwrap(); + + assert_eq!(expected.0, sql); + assert_eq!(expected.1, params); + } + + #[test] + fn test_begins_with_cast_to_string() { + let expected = expected_values( + r#"SELECT "test".* FROM "test" WHERE "jsonField"::text LIKE $1"#, + vec!["foo%"], + ); + + let query = + Select::from_table("test") + .so_that(Column::from("jsonField").begins_with("foo")); + let (sql, params) = Postgres::build(query).unwrap(); + + assert_eq!(expected.0, sql); + assert_eq!(expected.1, params); + } + + #[test] + fn test_not_begins_with_cast_to_string() { + let expected = expected_values( + r#"SELECT "test".* FROM "test" WHERE "jsonField"::text NOT LIKE $1"#, + vec!["foo%"], + ); + + let query = + Select::from_table("test") + .so_that(Column::from("jsonField").not_begins_with("foo")); + let (sql, params) = Postgres::build(query).unwrap(); + + assert_eq!(expected.0, sql); + assert_eq!(expected.1, params); + } + + #[test] + fn test_ends_with_cast_to_string() { + let expected = expected_values( + r#"SELECT "test".* FROM "test" WHERE "jsonField"::text LIKE $1"#, + vec!["%foo"], + ); + + let query = + Select::from_table("test") + .so_that(Column::from("jsonField").ends_into("foo")); + let (sql, params) = Postgres::build(query).unwrap(); + + assert_eq!(expected.0, sql); + assert_eq!(expected.1, params); + } + + #[test] + fn test_not_ends_with_cast_to_string() { + let expected = expected_values( + r#"SELECT "test".* FROM "test" WHERE "jsonField"::text NOT LIKE $1"#, + vec!["%foo"], + ); + + let query = + Select::from_table("test") + .so_that(Column::from("jsonField").not_ends_into("foo")); + let (sql, params) = Postgres::build(query).unwrap(); + + assert_eq!(expected.0, sql); + assert_eq!(expected.1, params); + } + #[test] fn test_default_insert() { let insert = Insert::single_into("foo")