Skip to content

Commit 56f815b

Browse files
andygrovemcheshkov
authored andcommitted
Parse LIKE patterns as Expr not Value (apache#579)
Can drop this after rebase on commit eb7f1b0 "Parse LIKE patterns as Expr not Value (apache#579)", first released in 0.21.0
1 parent 484c345 commit 56f815b

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
@@ -279,21 +279,21 @@ pub enum Expr {
279279
Like {
280280
negated: bool,
281281
expr: Box<Expr>,
282-
pattern: Box<Value>,
282+
pattern: Box<Expr>,
283283
escape_char: Option<char>,
284284
},
285285
/// ILIKE (case-insensitive LIKE)
286286
ILike {
287287
negated: bool,
288288
expr: Box<Expr>,
289-
pattern: Box<Value>,
289+
pattern: Box<Expr>,
290290
escape_char: Option<char>,
291291
},
292292
/// SIMILAR TO regex
293293
SimilarTo {
294294
negated: bool,
295295
expr: Box<Expr>,
296-
pattern: Box<Value>,
296+
pattern: Box<Expr>,
297297
escape_char: Option<char>,
298298
},
299299
/// 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
@@ -1256,21 +1256,21 @@ impl<'a> Parser<'a> {
12561256
Ok(Expr::Like {
12571257
negated,
12581258
expr: Box::new(expr),
1259-
pattern: Box::new(self.parse_value()?),
1259+
pattern: Box::new(self.parse_subexpr(Self::LIKE_PREC)?),
12601260
escape_char: self.parse_escape_char()?,
12611261
})
12621262
} else if self.parse_keyword(Keyword::ILIKE) {
12631263
Ok(Expr::ILike {
12641264
negated,
12651265
expr: Box::new(expr),
1266-
pattern: Box::new(self.parse_value()?),
1266+
pattern: Box::new(self.parse_subexpr(Self::LIKE_PREC)?),
12671267
escape_char: self.parse_escape_char()?,
12681268
})
12691269
} else if self.parse_keywords(&[Keyword::SIMILAR, Keyword::TO]) {
12701270
Ok(Expr::SimilarTo {
12711271
negated,
12721272
expr: Box::new(expr),
1273-
pattern: Box::new(self.parse_value()?),
1273+
pattern: Box::new(self.parse_subexpr(Self::LIKE_PREC)?),
12741274
escape_char: self.parse_escape_char()?,
12751275
})
12761276
} else {
@@ -1417,10 +1417,16 @@ impl<'a> Parser<'a> {
14171417
})
14181418
}
14191419

1420-
const UNARY_NOT_PREC: u8 = 15;
1421-
const BETWEEN_PREC: u8 = 20;
1420+
// use https://www.postgresql.org/docs/7.0/operators.htm#AEN2026 as a reference
14221421
const PLUS_MINUS_PREC: u8 = 30;
1422+
const XOR_PREC: u8 = 24;
14231423
const TIME_ZONE_PREC: u8 = 20;
1424+
const BETWEEN_PREC: u8 = 20;
1425+
const LIKE_PREC: u8 = 19;
1426+
const IS_PREC: u8 = 17;
1427+
const UNARY_NOT_PREC: u8 = 15;
1428+
const AND_PREC: u8 = 10;
1429+
const OR_PREC: u8 = 5;
14241430

14251431
/// Get the precedence of the next token
14261432
pub fn get_next_precedence(&self) -> Result<u8, ParserError> {
@@ -1431,9 +1437,9 @@ impl<'a> Parser<'a> {
14311437
let token_2 = self.peek_nth_token(2);
14321438
debug!("0: {token_0} 1: {token_1} 2: {token_2}");
14331439
match token {
1434-
Token::Word(w) if w.keyword == Keyword::OR => Ok(5),
1435-
Token::Word(w) if w.keyword == Keyword::AND => Ok(10),
1436-
Token::Word(w) if w.keyword == Keyword::XOR => Ok(24),
1440+
Token::Word(w) if w.keyword == Keyword::OR => Ok(Self::OR_PREC),
1441+
Token::Word(w) if w.keyword == Keyword::AND => Ok(Self::AND_PREC),
1442+
Token::Word(w) if w.keyword == Keyword::XOR => Ok(Self::XOR_PREC),
14371443

14381444
Token::Word(w) if w.keyword == Keyword::AT => {
14391445
match (self.peek_nth_token(1), self.peek_nth_token(2)) {
@@ -1454,17 +1460,17 @@ impl<'a> Parser<'a> {
14541460
// precedence.
14551461
Token::Word(w) if w.keyword == Keyword::IN => Ok(Self::BETWEEN_PREC),
14561462
Token::Word(w) if w.keyword == Keyword::BETWEEN => Ok(Self::BETWEEN_PREC),
1457-
Token::Word(w) if w.keyword == Keyword::LIKE => Ok(Self::BETWEEN_PREC),
1458-
Token::Word(w) if w.keyword == Keyword::ILIKE => Ok(Self::BETWEEN_PREC),
1459-
Token::Word(w) if w.keyword == Keyword::SIMILAR => Ok(Self::BETWEEN_PREC),
1463+
Token::Word(w) if w.keyword == Keyword::LIKE => Ok(Self::LIKE_PREC),
1464+
Token::Word(w) if w.keyword == Keyword::ILIKE => Ok(Self::LIKE_PREC),
1465+
Token::Word(w) if w.keyword == Keyword::SIMILAR => Ok(Self::LIKE_PREC),
14601466
_ => Ok(0),
14611467
},
1462-
Token::Word(w) if w.keyword == Keyword::IS => Ok(17),
1468+
Token::Word(w) if w.keyword == Keyword::IS => Ok(Self::IS_PREC),
14631469
Token::Word(w) if w.keyword == Keyword::IN => Ok(Self::BETWEEN_PREC),
14641470
Token::Word(w) if w.keyword == Keyword::BETWEEN => Ok(Self::BETWEEN_PREC),
1465-
Token::Word(w) if w.keyword == Keyword::LIKE => Ok(Self::BETWEEN_PREC),
1466-
Token::Word(w) if w.keyword == Keyword::ILIKE => Ok(Self::BETWEEN_PREC),
1467-
Token::Word(w) if w.keyword == Keyword::SIMILAR => Ok(Self::BETWEEN_PREC),
1471+
Token::Word(w) if w.keyword == Keyword::LIKE => Ok(Self::LIKE_PREC),
1472+
Token::Word(w) if w.keyword == Keyword::ILIKE => Ok(Self::LIKE_PREC),
1473+
Token::Word(w) if w.keyword == Keyword::SIMILAR => Ok(Self::LIKE_PREC),
14681474
Token::Eq
14691475
| Token::Lt
14701476
| Token::LtEq

tests/sqlparser_common.rs

Lines changed: 49 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -784,7 +784,7 @@ fn parse_not_precedence() {
784784
expr: Box::new(Expr::Like {
785785
expr: Box::new(Expr::Value(Value::SingleQuotedString("a".into()))),
786786
negated: true,
787-
pattern: Box::new(Value::SingleQuotedString("b".into())),
787+
pattern: Box::new(Expr::Value(Value::SingleQuotedString("b".into()))),
788788
escape_char: None
789789
}),
790790
},
@@ -817,7 +817,7 @@ fn parse_like() {
817817
Expr::Like {
818818
expr: Box::new(Expr::Identifier(Ident::new("name"))),
819819
negated,
820-
pattern: Box::new(Value::SingleQuotedString("%a".to_string())),
820+
pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))),
821821
escape_char: None
822822
},
823823
select.selection.unwrap()
@@ -833,7 +833,7 @@ fn parse_like() {
833833
Expr::Like {
834834
expr: Box::new(Expr::Identifier(Ident::new("name"))),
835835
negated,
836-
pattern: Box::new(Value::SingleQuotedString("%a".to_string())),
836+
pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))),
837837
escape_char: Some('\\')
838838
},
839839
select.selection.unwrap()
@@ -850,7 +850,7 @@ fn parse_like() {
850850
Expr::IsNull(Box::new(Expr::Like {
851851
expr: Box::new(Expr::Identifier(Ident::new("name"))),
852852
negated,
853-
pattern: Box::new(Value::SingleQuotedString("%a".to_string())),
853+
pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))),
854854
escape_char: None
855855
})),
856856
select.selection.unwrap()
@@ -860,6 +860,45 @@ fn parse_like() {
860860
chk(true);
861861
}
862862

863+
#[test]
864+
fn parse_null_like() {
865+
let sql = "SELECT \
866+
column1 LIKE NULL AS col_null, \
867+
NULL LIKE column1 AS null_col \
868+
FROM customers";
869+
let select = verified_only_select(sql);
870+
assert_eq!(
871+
SelectItem::ExprWithAlias {
872+
expr: Expr::Like {
873+
expr: Box::new(Expr::Identifier(Ident::new("column1"))),
874+
negated: false,
875+
pattern: Box::new(Expr::Value(Value::Null)),
876+
escape_char: None
877+
},
878+
alias: Ident {
879+
value: "col_null".to_owned(),
880+
quote_style: None
881+
}
882+
},
883+
select.projection[0]
884+
);
885+
assert_eq!(
886+
SelectItem::ExprWithAlias {
887+
expr: Expr::Like {
888+
expr: Box::new(Expr::Value(Value::Null)),
889+
negated: false,
890+
pattern: Box::new(Expr::Identifier(Ident::new("column1"))),
891+
escape_char: None
892+
},
893+
alias: Ident {
894+
value: "null_col".to_owned(),
895+
quote_style: None
896+
}
897+
},
898+
select.projection[1]
899+
);
900+
}
901+
863902
#[test]
864903
fn parse_ilike() {
865904
fn chk(negated: bool) {
@@ -872,7 +911,7 @@ fn parse_ilike() {
872911
Expr::ILike {
873912
expr: Box::new(Expr::Identifier(Ident::new("name"))),
874913
negated,
875-
pattern: Box::new(Value::SingleQuotedString("%a".to_string())),
914+
pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))),
876915
escape_char: None
877916
},
878917
select.selection.unwrap()
@@ -888,7 +927,7 @@ fn parse_ilike() {
888927
Expr::ILike {
889928
expr: Box::new(Expr::Identifier(Ident::new("name"))),
890929
negated,
891-
pattern: Box::new(Value::SingleQuotedString("%a".to_string())),
930+
pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))),
892931
escape_char: Some('^')
893932
},
894933
select.selection.unwrap()
@@ -905,7 +944,7 @@ fn parse_ilike() {
905944
Expr::IsNull(Box::new(Expr::ILike {
906945
expr: Box::new(Expr::Identifier(Ident::new("name"))),
907946
negated,
908-
pattern: Box::new(Value::SingleQuotedString("%a".to_string())),
947+
pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))),
909948
escape_char: None
910949
})),
911950
select.selection.unwrap()
@@ -927,7 +966,7 @@ fn parse_similar_to() {
927966
Expr::SimilarTo {
928967
expr: Box::new(Expr::Identifier(Ident::new("name"))),
929968
negated,
930-
pattern: Box::new(Value::SingleQuotedString("%a".to_string())),
969+
pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))),
931970
escape_char: None
932971
},
933972
select.selection.unwrap()
@@ -943,7 +982,7 @@ fn parse_similar_to() {
943982
Expr::SimilarTo {
944983
expr: Box::new(Expr::Identifier(Ident::new("name"))),
945984
negated,
946-
pattern: Box::new(Value::SingleQuotedString("%a".to_string())),
985+
pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))),
947986
escape_char: Some('\\')
948987
},
949988
select.selection.unwrap()
@@ -959,7 +998,7 @@ fn parse_similar_to() {
959998
Expr::IsNull(Box::new(Expr::SimilarTo {
960999
expr: Box::new(Expr::Identifier(Ident::new("name"))),
9611000
negated,
962-
pattern: Box::new(Value::SingleQuotedString("%a".to_string())),
1001+
pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))),
9631002
escape_char: Some('\\')
9641003
})),
9651004
select.selection.unwrap()

0 commit comments

Comments
 (0)