Skip to content

Commit eb7f1b0

Browse files
authored
Parse LIKE patterns as Expr not Value (#579)
1 parent fc71719 commit eb7f1b0

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
@@ -280,21 +280,21 @@ pub enum Expr {
280280
Like {
281281
negated: bool,
282282
expr: Box<Expr>,
283-
pattern: Box<Value>,
283+
pattern: Box<Expr>,
284284
escape_char: Option<char>,
285285
},
286286
/// ILIKE (case-insensitive LIKE)
287287
ILike {
288288
negated: bool,
289289
expr: Box<Expr>,
290-
pattern: Box<Value>,
290+
pattern: Box<Expr>,
291291
escape_char: Option<char>,
292292
},
293293
/// SIMILAR TO regex
294294
SimilarTo {
295295
negated: bool,
296296
expr: Box<Expr>,
297-
pattern: Box<Value>,
297+
pattern: Box<Expr>,
298298
escape_char: Option<char>,
299299
},
300300
/// 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
@@ -1317,21 +1317,21 @@ impl<'a> Parser<'a> {
13171317
Ok(Expr::Like {
13181318
negated,
13191319
expr: Box::new(expr),
1320-
pattern: Box::new(self.parse_value()?),
1320+
pattern: Box::new(self.parse_subexpr(Self::LIKE_PREC)?),
13211321
escape_char: self.parse_escape_char()?,
13221322
})
13231323
} else if self.parse_keyword(Keyword::ILIKE) {
13241324
Ok(Expr::ILike {
13251325
negated,
13261326
expr: Box::new(expr),
1327-
pattern: Box::new(self.parse_value()?),
1327+
pattern: Box::new(self.parse_subexpr(Self::LIKE_PREC)?),
13281328
escape_char: self.parse_escape_char()?,
13291329
})
13301330
} else if self.parse_keywords(&[Keyword::SIMILAR, Keyword::TO]) {
13311331
Ok(Expr::SimilarTo {
13321332
negated,
13331333
expr: Box::new(expr),
1334-
pattern: Box::new(self.parse_value()?),
1334+
pattern: Box::new(self.parse_subexpr(Self::LIKE_PREC)?),
13351335
escape_char: self.parse_escape_char()?,
13361336
})
13371337
} else {
@@ -1478,10 +1478,16 @@ impl<'a> Parser<'a> {
14781478
})
14791479
}
14801480

1481-
const UNARY_NOT_PREC: u8 = 15;
1482-
const BETWEEN_PREC: u8 = 20;
1481+
// use https://www.postgresql.org/docs/7.0/operators.htm#AEN2026 as a reference
14831482
const PLUS_MINUS_PREC: u8 = 30;
1483+
const XOR_PREC: u8 = 24;
14841484
const TIME_ZONE_PREC: u8 = 20;
1485+
const BETWEEN_PREC: u8 = 20;
1486+
const LIKE_PREC: u8 = 19;
1487+
const IS_PREC: u8 = 17;
1488+
const UNARY_NOT_PREC: u8 = 15;
1489+
const AND_PREC: u8 = 10;
1490+
const OR_PREC: u8 = 5;
14851491

14861492
/// Get the precedence of the next token
14871493
pub fn get_next_precedence(&self) -> Result<u8, ParserError> {
@@ -1492,9 +1498,9 @@ impl<'a> Parser<'a> {
14921498
let token_2 = self.peek_nth_token(2);
14931499
debug!("0: {token_0} 1: {token_1} 2: {token_2}");
14941500
match token {
1495-
Token::Word(w) if w.keyword == Keyword::OR => Ok(5),
1496-
Token::Word(w) if w.keyword == Keyword::AND => Ok(10),
1497-
Token::Word(w) if w.keyword == Keyword::XOR => Ok(24),
1501+
Token::Word(w) if w.keyword == Keyword::OR => Ok(Self::OR_PREC),
1502+
Token::Word(w) if w.keyword == Keyword::AND => Ok(Self::AND_PREC),
1503+
Token::Word(w) if w.keyword == Keyword::XOR => Ok(Self::XOR_PREC),
14981504

14991505
Token::Word(w) if w.keyword == Keyword::AT => {
15001506
match (self.peek_nth_token(1), self.peek_nth_token(2)) {
@@ -1515,18 +1521,18 @@ impl<'a> Parser<'a> {
15151521
// precedence.
15161522
Token::Word(w) if w.keyword == Keyword::IN => Ok(Self::BETWEEN_PREC),
15171523
Token::Word(w) if w.keyword == Keyword::BETWEEN => Ok(Self::BETWEEN_PREC),
1518-
Token::Word(w) if w.keyword == Keyword::LIKE => Ok(Self::BETWEEN_PREC),
1519-
Token::Word(w) if w.keyword == Keyword::ILIKE => Ok(Self::BETWEEN_PREC),
1520-
Token::Word(w) if w.keyword == Keyword::SIMILAR => Ok(Self::BETWEEN_PREC),
1524+
Token::Word(w) if w.keyword == Keyword::LIKE => Ok(Self::LIKE_PREC),
1525+
Token::Word(w) if w.keyword == Keyword::ILIKE => Ok(Self::LIKE_PREC),
1526+
Token::Word(w) if w.keyword == Keyword::SIMILAR => Ok(Self::LIKE_PREC),
15211527
_ => Ok(0),
15221528
},
1523-
Token::Word(w) if w.keyword == Keyword::IS => Ok(17),
1529+
Token::Word(w) if w.keyword == Keyword::IS => Ok(Self::IS_PREC),
15241530
Token::Word(w) if w.keyword == Keyword::IN => Ok(Self::BETWEEN_PREC),
15251531
Token::Word(w) if w.keyword == Keyword::BETWEEN => Ok(Self::BETWEEN_PREC),
1526-
Token::Word(w) if w.keyword == Keyword::LIKE => Ok(Self::BETWEEN_PREC),
1527-
Token::Word(w) if w.keyword == Keyword::ILIKE => Ok(Self::BETWEEN_PREC),
1532+
Token::Word(w) if w.keyword == Keyword::LIKE => Ok(Self::LIKE_PREC),
1533+
Token::Word(w) if w.keyword == Keyword::ILIKE => Ok(Self::LIKE_PREC),
1534+
Token::Word(w) if w.keyword == Keyword::SIMILAR => Ok(Self::LIKE_PREC),
15281535
Token::Word(w) if w.keyword == Keyword::OPERATOR => Ok(Self::BETWEEN_PREC),
1529-
Token::Word(w) if w.keyword == Keyword::SIMILAR => Ok(Self::BETWEEN_PREC),
15301536
Token::Eq
15311537
| Token::Lt
15321538
| Token::LtEq

tests/sqlparser_common.rs

Lines changed: 49 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -862,7 +862,7 @@ fn parse_not_precedence() {
862862
expr: Box::new(Expr::Like {
863863
expr: Box::new(Expr::Value(Value::SingleQuotedString("a".into()))),
864864
negated: true,
865-
pattern: Box::new(Value::SingleQuotedString("b".into())),
865+
pattern: Box::new(Expr::Value(Value::SingleQuotedString("b".into()))),
866866
escape_char: None
867867
}),
868868
},
@@ -895,7 +895,7 @@ fn parse_like() {
895895
Expr::Like {
896896
expr: Box::new(Expr::Identifier(Ident::new("name"))),
897897
negated,
898-
pattern: Box::new(Value::SingleQuotedString("%a".to_string())),
898+
pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))),
899899
escape_char: None
900900
},
901901
select.selection.unwrap()
@@ -911,7 +911,7 @@ fn parse_like() {
911911
Expr::Like {
912912
expr: Box::new(Expr::Identifier(Ident::new("name"))),
913913
negated,
914-
pattern: Box::new(Value::SingleQuotedString("%a".to_string())),
914+
pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))),
915915
escape_char: Some('\\')
916916
},
917917
select.selection.unwrap()
@@ -928,7 +928,7 @@ fn parse_like() {
928928
Expr::IsNull(Box::new(Expr::Like {
929929
expr: Box::new(Expr::Identifier(Ident::new("name"))),
930930
negated,
931-
pattern: Box::new(Value::SingleQuotedString("%a".to_string())),
931+
pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))),
932932
escape_char: None
933933
})),
934934
select.selection.unwrap()
@@ -938,6 +938,45 @@ fn parse_like() {
938938
chk(true);
939939
}
940940

941+
#[test]
942+
fn parse_null_like() {
943+
let sql = "SELECT \
944+
column1 LIKE NULL AS col_null, \
945+
NULL LIKE column1 AS null_col \
946+
FROM customers";
947+
let select = verified_only_select(sql);
948+
assert_eq!(
949+
SelectItem::ExprWithAlias {
950+
expr: Expr::Like {
951+
expr: Box::new(Expr::Identifier(Ident::new("column1"))),
952+
negated: false,
953+
pattern: Box::new(Expr::Value(Value::Null)),
954+
escape_char: None
955+
},
956+
alias: Ident {
957+
value: "col_null".to_owned(),
958+
quote_style: None
959+
}
960+
},
961+
select.projection[0]
962+
);
963+
assert_eq!(
964+
SelectItem::ExprWithAlias {
965+
expr: Expr::Like {
966+
expr: Box::new(Expr::Value(Value::Null)),
967+
negated: false,
968+
pattern: Box::new(Expr::Identifier(Ident::new("column1"))),
969+
escape_char: None
970+
},
971+
alias: Ident {
972+
value: "null_col".to_owned(),
973+
quote_style: None
974+
}
975+
},
976+
select.projection[1]
977+
);
978+
}
979+
941980
#[test]
942981
fn parse_ilike() {
943982
fn chk(negated: bool) {
@@ -950,7 +989,7 @@ fn parse_ilike() {
950989
Expr::ILike {
951990
expr: Box::new(Expr::Identifier(Ident::new("name"))),
952991
negated,
953-
pattern: Box::new(Value::SingleQuotedString("%a".to_string())),
992+
pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))),
954993
escape_char: None
955994
},
956995
select.selection.unwrap()
@@ -966,7 +1005,7 @@ fn parse_ilike() {
9661005
Expr::ILike {
9671006
expr: Box::new(Expr::Identifier(Ident::new("name"))),
9681007
negated,
969-
pattern: Box::new(Value::SingleQuotedString("%a".to_string())),
1008+
pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))),
9701009
escape_char: Some('^')
9711010
},
9721011
select.selection.unwrap()
@@ -983,7 +1022,7 @@ fn parse_ilike() {
9831022
Expr::IsNull(Box::new(Expr::ILike {
9841023
expr: Box::new(Expr::Identifier(Ident::new("name"))),
9851024
negated,
986-
pattern: Box::new(Value::SingleQuotedString("%a".to_string())),
1025+
pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))),
9871026
escape_char: None
9881027
})),
9891028
select.selection.unwrap()
@@ -1005,7 +1044,7 @@ fn parse_similar_to() {
10051044
Expr::SimilarTo {
10061045
expr: Box::new(Expr::Identifier(Ident::new("name"))),
10071046
negated,
1008-
pattern: Box::new(Value::SingleQuotedString("%a".to_string())),
1047+
pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))),
10091048
escape_char: None
10101049
},
10111050
select.selection.unwrap()
@@ -1021,7 +1060,7 @@ fn parse_similar_to() {
10211060
Expr::SimilarTo {
10221061
expr: Box::new(Expr::Identifier(Ident::new("name"))),
10231062
negated,
1024-
pattern: Box::new(Value::SingleQuotedString("%a".to_string())),
1063+
pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))),
10251064
escape_char: Some('\\')
10261065
},
10271066
select.selection.unwrap()
@@ -1037,7 +1076,7 @@ fn parse_similar_to() {
10371076
Expr::IsNull(Box::new(Expr::SimilarTo {
10381077
expr: Box::new(Expr::Identifier(Ident::new("name"))),
10391078
negated,
1040-
pattern: Box::new(Value::SingleQuotedString("%a".to_string())),
1079+
pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))),
10411080
escape_char: Some('\\')
10421081
})),
10431082
select.selection.unwrap()

0 commit comments

Comments
 (0)