Skip to content

Commit 10782e5

Browse files
andygroveMazterQyou
authored andcommitted
Parse LIKE patterns as Expr not Value (apache#579)
1 parent 512a18b commit 10782e5

File tree

3 files changed

+73
-28
lines changed

3 files changed

+73
-28
lines changed

src/ast/mod.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -236,21 +236,21 @@ pub enum Expr {
236236
Like {
237237
negated: bool,
238238
expr: Box<Expr>,
239-
pattern: Box<Value>,
239+
pattern: Box<Expr>,
240240
escape_char: Option<char>,
241241
},
242242
/// ILIKE (case-insensitive LIKE)
243243
ILike {
244244
negated: bool,
245245
expr: Box<Expr>,
246-
pattern: Box<Value>,
246+
pattern: Box<Expr>,
247247
escape_char: Option<char>,
248248
},
249249
/// SIMILAR TO regex
250250
SimilarTo {
251251
negated: bool,
252252
expr: Box<Expr>,
253-
pattern: Box<Value>,
253+
pattern: Box<Expr>,
254254
escape_char: Option<char>,
255255
},
256256
/// Any operation e.g. `1 ANY (1)` or `foo > ANY(bar)`, It will be wrapped in the right side of BinaryExpr

src/parser.rs

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1291,21 +1291,21 @@ impl<'a> Parser<'a> {
12911291
Ok(Expr::Like {
12921292
negated,
12931293
expr: Box::new(expr),
1294-
pattern: Box::new(self.parse_value()?),
1294+
pattern: Box::new(self.parse_subexpr(Self::LIKE_PREC)?),
12951295
escape_char: self.parse_escape_char()?,
12961296
})
12971297
} else if self.parse_keyword(Keyword::ILIKE) {
12981298
Ok(Expr::ILike {
12991299
negated,
13001300
expr: Box::new(expr),
1301-
pattern: Box::new(self.parse_value()?),
1301+
pattern: Box::new(self.parse_subexpr(Self::LIKE_PREC)?),
13021302
escape_char: self.parse_escape_char()?,
13031303
})
13041304
} else if self.parse_keywords(&[Keyword::SIMILAR, Keyword::TO]) {
13051305
Ok(Expr::SimilarTo {
13061306
negated,
13071307
expr: Box::new(expr),
1308-
pattern: Box::new(self.parse_value()?),
1308+
pattern: Box::new(self.parse_subexpr(Self::LIKE_PREC)?),
13091309
escape_char: self.parse_escape_char()?,
13101310
})
13111311
} else {
@@ -1435,10 +1435,16 @@ impl<'a> Parser<'a> {
14351435
})
14361436
}
14371437

1438-
const UNARY_NOT_PREC: u8 = 15;
1439-
const BETWEEN_PREC: u8 = 20;
1438+
// use https://www.postgresql.org/docs/7.0/operators.htm#AEN2026 as a reference
14401439
const PLUS_MINUS_PREC: u8 = 30;
1440+
const XOR_PREC: u8 = 24;
14411441
const TIME_ZONE_PREC: u8 = 20;
1442+
const BETWEEN_PREC: u8 = 20;
1443+
const LIKE_PREC: u8 = 19;
1444+
const IS_PREC: u8 = 17;
1445+
const UNARY_NOT_PREC: u8 = 15;
1446+
const AND_PREC: u8 = 10;
1447+
const OR_PREC: u8 = 5;
14421448

14431449
/// Get the precedence of the next token
14441450
pub fn get_next_precedence(&self) -> Result<u8, ParserError> {
@@ -1449,9 +1455,9 @@ impl<'a> Parser<'a> {
14491455
let token_2 = self.peek_nth_token(2);
14501456
debug!("0: {token_0} 1: {token_1} 2: {token_2}");
14511457
match token {
1452-
Token::Word(w) if w.keyword == Keyword::OR => Ok(5),
1453-
Token::Word(w) if w.keyword == Keyword::AND => Ok(10),
1454-
Token::Word(w) if w.keyword == Keyword::XOR => Ok(24),
1458+
Token::Word(w) if w.keyword == Keyword::OR => Ok(Self::OR_PREC),
1459+
Token::Word(w) if w.keyword == Keyword::AND => Ok(Self::AND_PREC),
1460+
Token::Word(w) if w.keyword == Keyword::XOR => Ok(Self::XOR_PREC),
14551461

14561462
Token::Word(w) if w.keyword == Keyword::AT => {
14571463
match (self.peek_nth_token(1), self.peek_nth_token(2)) {
@@ -1472,17 +1478,17 @@ impl<'a> Parser<'a> {
14721478
// precedence.
14731479
Token::Word(w) if w.keyword == Keyword::IN => Ok(Self::BETWEEN_PREC),
14741480
Token::Word(w) if w.keyword == Keyword::BETWEEN => Ok(Self::BETWEEN_PREC),
1475-
Token::Word(w) if w.keyword == Keyword::LIKE => Ok(Self::BETWEEN_PREC),
1476-
Token::Word(w) if w.keyword == Keyword::ILIKE => Ok(Self::BETWEEN_PREC),
1477-
Token::Word(w) if w.keyword == Keyword::SIMILAR => Ok(Self::BETWEEN_PREC),
1481+
Token::Word(w) if w.keyword == Keyword::LIKE => Ok(Self::LIKE_PREC),
1482+
Token::Word(w) if w.keyword == Keyword::ILIKE => Ok(Self::LIKE_PREC),
1483+
Token::Word(w) if w.keyword == Keyword::SIMILAR => Ok(Self::LIKE_PREC),
14781484
_ => Ok(0),
14791485
},
1480-
Token::Word(w) if w.keyword == Keyword::IS => Ok(17),
1486+
Token::Word(w) if w.keyword == Keyword::IS => Ok(Self::IS_PREC),
14811487
Token::Word(w) if w.keyword == Keyword::IN => Ok(Self::BETWEEN_PREC),
14821488
Token::Word(w) if w.keyword == Keyword::BETWEEN => Ok(Self::BETWEEN_PREC),
1483-
Token::Word(w) if w.keyword == Keyword::LIKE => Ok(Self::BETWEEN_PREC),
1484-
Token::Word(w) if w.keyword == Keyword::ILIKE => Ok(Self::BETWEEN_PREC),
1485-
Token::Word(w) if w.keyword == Keyword::SIMILAR => Ok(Self::BETWEEN_PREC),
1489+
Token::Word(w) if w.keyword == Keyword::LIKE => Ok(Self::LIKE_PREC),
1490+
Token::Word(w) if w.keyword == Keyword::ILIKE => Ok(Self::LIKE_PREC),
1491+
Token::Word(w) if w.keyword == Keyword::SIMILAR => Ok(Self::LIKE_PREC),
14861492
Token::Eq
14871493
| Token::Lt
14881494
| Token::LtEq

tests/sqlparser_common.rs

Lines changed: 49 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -793,7 +793,7 @@ fn parse_not_precedence() {
793793
expr: Box::new(Expr::Like {
794794
expr: Box::new(Expr::Value(Value::SingleQuotedString("a".into()))),
795795
negated: true,
796-
pattern: Box::new(Value::SingleQuotedString("b".into())),
796+
pattern: Box::new(Expr::Value(Value::SingleQuotedString("b".into()))),
797797
escape_char: None
798798
}),
799799
},
@@ -826,7 +826,7 @@ fn parse_like() {
826826
Expr::Like {
827827
expr: Box::new(Expr::Identifier(Ident::new("name"))),
828828
negated,
829-
pattern: Box::new(Value::SingleQuotedString("%a".to_string())),
829+
pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))),
830830
escape_char: None
831831
},
832832
select.selection.unwrap()
@@ -842,7 +842,7 @@ fn parse_like() {
842842
Expr::Like {
843843
expr: Box::new(Expr::Identifier(Ident::new("name"))),
844844
negated,
845-
pattern: Box::new(Value::SingleQuotedString("%a".to_string())),
845+
pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))),
846846
escape_char: Some('\\')
847847
},
848848
select.selection.unwrap()
@@ -859,7 +859,7 @@ fn parse_like() {
859859
Expr::IsNull(Box::new(Expr::Like {
860860
expr: Box::new(Expr::Identifier(Ident::new("name"))),
861861
negated,
862-
pattern: Box::new(Value::SingleQuotedString("%a".to_string())),
862+
pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))),
863863
escape_char: None
864864
})),
865865
select.selection.unwrap()
@@ -869,6 +869,45 @@ fn parse_like() {
869869
chk(true);
870870
}
871871

872+
#[test]
873+
fn parse_null_like() {
874+
let sql = "SELECT \
875+
column1 LIKE NULL AS col_null, \
876+
NULL LIKE column1 AS null_col \
877+
FROM customers";
878+
let select = verified_only_select(sql);
879+
assert_eq!(
880+
SelectItem::ExprWithAlias {
881+
expr: Expr::Like {
882+
expr: Box::new(Expr::Identifier(Ident::new("column1"))),
883+
negated: false,
884+
pattern: Box::new(Expr::Value(Value::Null)),
885+
escape_char: None
886+
},
887+
alias: Ident {
888+
value: "col_null".to_owned(),
889+
quote_style: None
890+
}
891+
},
892+
select.projection[0]
893+
);
894+
assert_eq!(
895+
SelectItem::ExprWithAlias {
896+
expr: Expr::Like {
897+
expr: Box::new(Expr::Value(Value::Null)),
898+
negated: false,
899+
pattern: Box::new(Expr::Identifier(Ident::new("column1"))),
900+
escape_char: None
901+
},
902+
alias: Ident {
903+
value: "null_col".to_owned(),
904+
quote_style: None
905+
}
906+
},
907+
select.projection[1]
908+
);
909+
}
910+
872911
#[test]
873912
fn parse_ilike() {
874913
fn chk(negated: bool) {
@@ -881,7 +920,7 @@ fn parse_ilike() {
881920
Expr::ILike {
882921
expr: Box::new(Expr::Identifier(Ident::new("name"))),
883922
negated,
884-
pattern: Box::new(Value::SingleQuotedString("%a".to_string())),
923+
pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))),
885924
escape_char: None
886925
},
887926
select.selection.unwrap()
@@ -897,7 +936,7 @@ fn parse_ilike() {
897936
Expr::ILike {
898937
expr: Box::new(Expr::Identifier(Ident::new("name"))),
899938
negated,
900-
pattern: Box::new(Value::SingleQuotedString("%a".to_string())),
939+
pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))),
901940
escape_char: Some('^')
902941
},
903942
select.selection.unwrap()
@@ -914,7 +953,7 @@ fn parse_ilike() {
914953
Expr::IsNull(Box::new(Expr::ILike {
915954
expr: Box::new(Expr::Identifier(Ident::new("name"))),
916955
negated,
917-
pattern: Box::new(Value::SingleQuotedString("%a".to_string())),
956+
pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))),
918957
escape_char: None
919958
})),
920959
select.selection.unwrap()
@@ -936,7 +975,7 @@ fn parse_similar_to() {
936975
Expr::SimilarTo {
937976
expr: Box::new(Expr::Identifier(Ident::new("name"))),
938977
negated,
939-
pattern: Box::new(Value::SingleQuotedString("%a".to_string())),
978+
pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))),
940979
escape_char: None
941980
},
942981
select.selection.unwrap()
@@ -952,7 +991,7 @@ fn parse_similar_to() {
952991
Expr::SimilarTo {
953992
expr: Box::new(Expr::Identifier(Ident::new("name"))),
954993
negated,
955-
pattern: Box::new(Value::SingleQuotedString("%a".to_string())),
994+
pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))),
956995
escape_char: Some('\\')
957996
},
958997
select.selection.unwrap()
@@ -968,7 +1007,7 @@ fn parse_similar_to() {
9681007
Expr::IsNull(Box::new(Expr::SimilarTo {
9691008
expr: Box::new(Expr::Identifier(Ident::new("name"))),
9701009
negated,
971-
pattern: Box::new(Value::SingleQuotedString("%a".to_string())),
1010+
pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))),
9721011
escape_char: Some('\\')
9731012
})),
9741013
select.selection.unwrap()

0 commit comments

Comments
 (0)