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

Commit 03bfdad

Browse files
committed
Now LIKE operations explicitly cast to text in pgSQL
- This affects only comparisson between text and columns - This is required to solve prisma/prisma#10103
1 parent 8be4fab commit 03bfdad

File tree

2 files changed

+263
-58
lines changed

2 files changed

+263
-58
lines changed

src/visitor.rs

+70-58
Original file line numberDiff line numberDiff line change
@@ -646,6 +646,70 @@ pub trait Visitor<'a> {
646646
self.visit_expression(right)
647647
}
648648

649+
fn visit_like(&mut self, left: Expression<'a>, right: Cow<str>) -> Result {
650+
self.visit_expression(left)?;
651+
652+
self.add_parameter(Value::text(format!(
653+
"{}{}{}",
654+
Self::C_WILDCARD,
655+
right,
656+
Self::C_WILDCARD
657+
)));
658+
659+
self.write(" LIKE ")?;
660+
self.parameter_substitution()
661+
}
662+
663+
fn visit_not_like(&mut self, left: Expression<'a>, right: Cow<str>) -> Result {
664+
self.visit_expression(left)?;
665+
666+
self.add_parameter(Value::text(format!(
667+
"{}{}{}",
668+
Self::C_WILDCARD,
669+
right,
670+
Self::C_WILDCARD
671+
)));
672+
673+
self.write(" NOT LIKE ")?;
674+
self.parameter_substitution()
675+
}
676+
677+
fn visit_begins_with(&mut self, left: Expression<'a>, right: Cow<str>) -> Result {
678+
self.visit_expression(left)?;
679+
680+
self.add_parameter(Value::text(format!("{}{}", right, Self::C_WILDCARD)));
681+
682+
self.write(" LIKE ")?;
683+
self.parameter_substitution()
684+
}
685+
686+
fn visit_not_begins_with(&mut self, left: Expression<'a>, right: Cow<str>) -> Result {
687+
self.visit_expression(left)?;
688+
689+
self.add_parameter(Value::text(format!("{}{}", right, Self::C_WILDCARD)));
690+
691+
self.write(" NOT LIKE ")?;
692+
self.parameter_substitution()
693+
}
694+
695+
fn visit_ends_with(&mut self, left: Expression<'a>, right: Cow<str>) -> Result {
696+
self.visit_expression(left)?;
697+
698+
self.add_parameter(Value::text(format!("{}{}", Self::C_WILDCARD, right,)));
699+
700+
self.write(" LIKE ")?;
701+
self.parameter_substitution()
702+
}
703+
704+
fn visit_not_ends_with(&mut self, left: Expression<'a>, right: Cow<str>) -> Result {
705+
self.visit_expression(left)?;
706+
707+
self.add_parameter(Value::text(format!("{}{}", Self::C_WILDCARD, right,)));
708+
709+
self.write(" NOT LIKE ")?;
710+
self.parameter_substitution()
711+
}
712+
649713
/// A comparison expression
650714
fn visit_compare(&mut self, compare: Compare<'a>) -> Result {
651715
match compare {
@@ -799,64 +863,12 @@ pub trait Visitor<'a> {
799863
self.visit_expression(right)
800864
}
801865
},
802-
Compare::Like(left, right) => {
803-
self.visit_expression(*left)?;
804-
805-
self.add_parameter(Value::text(format!(
806-
"{}{}{}",
807-
Self::C_WILDCARD,
808-
right,
809-
Self::C_WILDCARD
810-
)));
811-
812-
self.write(" LIKE ")?;
813-
self.parameter_substitution()
814-
}
815-
Compare::NotLike(left, right) => {
816-
self.visit_expression(*left)?;
817-
818-
self.add_parameter(Value::text(format!(
819-
"{}{}{}",
820-
Self::C_WILDCARD,
821-
right,
822-
Self::C_WILDCARD
823-
)));
824-
825-
self.write(" NOT LIKE ")?;
826-
self.parameter_substitution()
827-
}
828-
Compare::BeginsWith(left, right) => {
829-
self.visit_expression(*left)?;
830-
831-
self.add_parameter(Value::text(format!("{}{}", right, Self::C_WILDCARD)));
832-
833-
self.write(" LIKE ")?;
834-
self.parameter_substitution()
835-
}
836-
Compare::NotBeginsWith(left, right) => {
837-
self.visit_expression(*left)?;
838-
839-
self.add_parameter(Value::text(format!("{}{}", right, Self::C_WILDCARD)));
840-
841-
self.write(" NOT LIKE ")?;
842-
self.parameter_substitution()
843-
}
844-
Compare::EndsInto(left, right) => {
845-
self.visit_expression(*left)?;
846-
847-
self.add_parameter(Value::text(format!("{}{}", Self::C_WILDCARD, right,)));
848-
849-
self.write(" LIKE ")?;
850-
self.parameter_substitution()
851-
}
852-
Compare::NotEndsInto(left, right) => {
853-
self.visit_expression(*left)?;
854-
855-
self.add_parameter(Value::text(format!("{}{}", Self::C_WILDCARD, right,)));
856-
857-
self.write(" NOT LIKE ")?;
858-
self.parameter_substitution()
859-
}
866+
Compare::Like(left, right) => self.visit_like(*left, right),
867+
Compare::NotLike(left, right) => self.visit_not_like(*left, right),
868+
Compare::BeginsWith(left, right) => self.visit_begins_with(*left, right),
869+
Compare::NotBeginsWith(left, right) => self.visit_not_begins_with(*left, right),
870+
Compare::EndsInto(left, right) => self.visit_ends_with(*left, right),
871+
Compare::NotEndsInto(left, right) => self.visit_not_ends_with(*left, right),
860872
Compare::Null(column) => {
861873
self.visit_expression(*column)?;
862874
self.write(" IS NULL")

src/visitor/postgres.rs

+193
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use std::borrow::Cow;
12
use crate::{
23
ast::*,
34
visitor::{self, Visitor},
@@ -416,6 +417,102 @@ impl<'a> Visitor<'a> for Postgres<'a> {
416417

417418
Ok(())
418419
}
420+
421+
fn visit_like(&mut self, left: Expression<'a>, right: Cow<str>) -> visitor::Result {
422+
let need_cast = matches!(&left.kind, ExpressionKind::Column(_));
423+
self.visit_expression(left)?;
424+
425+
// NOTE: Pg is strongly typed, LIKE comparisons are only between strings.
426+
// to avoid problems with types without implicit casting we explicitly cast to text
427+
if need_cast {
428+
self.write("::text")?;
429+
}
430+
431+
self.add_parameter(Value::text(format!(
432+
"{}{}{}",
433+
Self::C_WILDCARD,
434+
right,
435+
Self::C_WILDCARD
436+
)));
437+
438+
self.write(" LIKE ")?;
439+
self.parameter_substitution()
440+
}
441+
442+
fn visit_not_like(&mut self, left: Expression<'a>, right: Cow<str>) -> visitor::Result {
443+
let need_cast = matches!(&left.kind, ExpressionKind::Column(_));
444+
self.visit_expression(left)?;
445+
446+
if need_cast {
447+
self.write("::text")?;
448+
}
449+
450+
self.add_parameter(Value::text(format!(
451+
"{}{}{}",
452+
Self::C_WILDCARD,
453+
right,
454+
Self::C_WILDCARD
455+
)));
456+
457+
self.write(" NOT LIKE ")?;
458+
self.parameter_substitution()
459+
}
460+
461+
fn visit_begins_with(&mut self, left: Expression<'a>, right: Cow<str>) -> visitor::Result {
462+
let need_cast = matches!(&left.kind, ExpressionKind::Column(_));
463+
self.visit_expression(left)?;
464+
465+
if need_cast {
466+
self.write("::text")?;
467+
}
468+
469+
self.add_parameter(Value::text(format!("{}{}", right, Self::C_WILDCARD)));
470+
471+
self.write(" LIKE ")?;
472+
self.parameter_substitution()
473+
}
474+
475+
fn visit_not_begins_with(&mut self, left: Expression<'a>, right: Cow<str>) -> visitor::Result {
476+
let need_cast = matches!(&left.kind, ExpressionKind::Column(_));
477+
self.visit_expression(left)?;
478+
479+
if need_cast {
480+
self.write("::text")?;
481+
}
482+
483+
self.add_parameter(Value::text(format!("{}{}", right, Self::C_WILDCARD)));
484+
485+
self.write(" NOT LIKE ")?;
486+
self.parameter_substitution()
487+
}
488+
489+
fn visit_ends_with(&mut self, left: Expression<'a>, right: Cow<str>) -> visitor::Result {
490+
let need_cast = matches!(&left.kind, ExpressionKind::Column(_));
491+
self.visit_expression(left)?;
492+
493+
if need_cast {
494+
self.write("::text")?;
495+
}
496+
497+
self.add_parameter(Value::text(format!("{}{}", Self::C_WILDCARD, right,)));
498+
499+
self.write(" LIKE ")?;
500+
self.parameter_substitution()
501+
}
502+
503+
fn visit_not_ends_with(&mut self, left: Expression<'a>, right: Cow<str>) -> visitor::Result {
504+
let need_cast = matches!(&left.kind, ExpressionKind::Column(_));
505+
self.visit_expression(left)?;
506+
507+
if need_cast {
508+
self.write("::text")?;
509+
}
510+
511+
self.add_parameter(Value::text(format!("{}{}", Self::C_WILDCARD, right,)));
512+
513+
self.write(" NOT LIKE ")?;
514+
self.parameter_substitution()
515+
}
419516
}
420517

421518
#[cfg(test)]
@@ -813,6 +910,102 @@ mod tests {
813910
assert_eq!(r#"SELECT "foo".* FROM "foo" WHERE "bar" ILIKE $1"#, sql);
814911
}
815912

913+
#[test]
914+
fn test_like_cast_to_string() {
915+
let expected = expected_values(
916+
r#"SELECT "test".* FROM "test" WHERE "jsonField"::text LIKE $1"#,
917+
vec!["%foo%"],
918+
);
919+
920+
let query =
921+
Select::from_table("test")
922+
.so_that(Column::from("jsonField").like("foo"));
923+
let (sql, params) = Postgres::build(query).unwrap();
924+
925+
assert_eq!(expected.0, sql);
926+
assert_eq!(expected.1, params);
927+
}
928+
929+
#[test]
930+
fn test_not_like_cast_to_string() {
931+
let expected = expected_values(
932+
r#"SELECT "test".* FROM "test" WHERE "jsonField"::text NOT LIKE $1"#,
933+
vec!["%foo%"],
934+
);
935+
936+
let query =
937+
Select::from_table("test")
938+
.so_that(Column::from("jsonField").not_like("foo"));
939+
let (sql, params) = Postgres::build(query).unwrap();
940+
941+
assert_eq!(expected.0, sql);
942+
assert_eq!(expected.1, params);
943+
}
944+
945+
#[test]
946+
fn test_begins_with_cast_to_string() {
947+
let expected = expected_values(
948+
r#"SELECT "test".* FROM "test" WHERE "jsonField"::text LIKE $1"#,
949+
vec!["foo%"],
950+
);
951+
952+
let query =
953+
Select::from_table("test")
954+
.so_that(Column::from("jsonField").begins_with("foo"));
955+
let (sql, params) = Postgres::build(query).unwrap();
956+
957+
assert_eq!(expected.0, sql);
958+
assert_eq!(expected.1, params);
959+
}
960+
961+
#[test]
962+
fn test_not_begins_with_cast_to_string() {
963+
let expected = expected_values(
964+
r#"SELECT "test".* FROM "test" WHERE "jsonField"::text NOT LIKE $1"#,
965+
vec!["foo%"],
966+
);
967+
968+
let query =
969+
Select::from_table("test")
970+
.so_that(Column::from("jsonField").not_begins_with("foo"));
971+
let (sql, params) = Postgres::build(query).unwrap();
972+
973+
assert_eq!(expected.0, sql);
974+
assert_eq!(expected.1, params);
975+
}
976+
977+
#[test]
978+
fn test_ends_with_cast_to_string() {
979+
let expected = expected_values(
980+
r#"SELECT "test".* FROM "test" WHERE "jsonField"::text LIKE $1"#,
981+
vec!["%foo"],
982+
);
983+
984+
let query =
985+
Select::from_table("test")
986+
.so_that(Column::from("jsonField").ends_into("foo"));
987+
let (sql, params) = Postgres::build(query).unwrap();
988+
989+
assert_eq!(expected.0, sql);
990+
assert_eq!(expected.1, params);
991+
}
992+
993+
#[test]
994+
fn test_not_ends_with_cast_to_string() {
995+
let expected = expected_values(
996+
r#"SELECT "test".* FROM "test" WHERE "jsonField"::text NOT LIKE $1"#,
997+
vec!["%foo"],
998+
);
999+
1000+
let query =
1001+
Select::from_table("test")
1002+
.so_that(Column::from("jsonField").not_ends_into("foo"));
1003+
let (sql, params) = Postgres::build(query).unwrap();
1004+
1005+
assert_eq!(expected.0, sql);
1006+
assert_eq!(expected.1, params);
1007+
}
1008+
8161009
#[test]
8171010
fn test_default_insert() {
8181011
let insert = Insert::single_into("foo")

0 commit comments

Comments
 (0)