Skip to content
This repository has been archived by the owner on Apr 25, 2023. It is now read-only.

Commit

Permalink
Now LIKE operations explicitly cast to text in pgSQL
Browse files Browse the repository at this point in the history
 - This affects only comparisson between text and columns
 - This is required to solve prisma/prisma#10103
  • Loading branch information
cprieto committed Jun 2, 2022
1 parent 8be4fab commit 03bfdad
Show file tree
Hide file tree
Showing 2 changed files with 263 additions and 58 deletions.
128 changes: 70 additions & 58 deletions src/visitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -646,6 +646,70 @@ pub trait Visitor<'a> {
self.visit_expression(right)
}

fn visit_like(&mut self, left: Expression<'a>, right: Cow<str>) -> 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<str>) -> 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<str>) -> 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<str>) -> 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<str>) -> 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<str>) -> 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 {
Expand Down Expand Up @@ -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")
Expand Down
193 changes: 193 additions & 0 deletions src/visitor/postgres.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use std::borrow::Cow;
use crate::{
ast::*,
visitor::{self, Visitor},
Expand Down Expand Up @@ -416,6 +417,102 @@ impl<'a> Visitor<'a> for Postgres<'a> {

Ok(())
}

fn visit_like(&mut self, left: Expression<'a>, right: Cow<str>) -> 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<str>) -> 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<str>) -> 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<str>) -> 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<str>) -> 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<str>) -> 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)]
Expand Down Expand Up @@ -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")
Expand Down

0 comments on commit 03bfdad

Please sign in to comment.