Repository: apache/datafusion-sqlparser-rs (Rust)

Issue:
Regression in parsing `NULL LIKE <expr>`
This test passes against 0.20.0 but fails in the main branch. This is likely caused by my recent changes for parsing `LIKE`.

```rust
#[test]
fn parse_null_like() {
    let sql =
        "SELECT \
            column1 LIKE NULL AS col_null, \
            NULL LIKE column1 AS null_col \
        FROM customers";
    let _ = verified_only_select(sql);
}
```

Failure:

```
SQL(ParserError(\"Expected a concrete value, found: column1\"))
```



Discussion:

Patch:
diff --git a/src/ast/mod.rs b/src/ast/mod.rs
index b91aae43c..6b5ccca0e 100644
--- a/src/ast/mod.rs
+++ b/src/ast/mod.rs
@@ -272,21 +272,21 @@ pub enum Expr {
     Like {
         negated: bool,
         expr: Box<Expr>,
-        pattern: Box<Value>,
+        pattern: Box<Expr>,
         escape_char: Option<char>,
     },
     /// ILIKE (case-insensitive LIKE)
     ILike {
         negated: bool,
         expr: Box<Expr>,
-        pattern: Box<Value>,
+        pattern: Box<Expr>,
         escape_char: Option<char>,
     },
     /// SIMILAR TO regex
     SimilarTo {
         negated: bool,
         expr: Box<Expr>,
-        pattern: Box<Value>,
+        pattern: Box<Expr>,
         escape_char: Option<char>,
     },
     /// Any operation e.g. `1 ANY (1)` or `foo > ANY(bar)`, It will be wrapped in the right side of BinaryExpr
diff --git a/src/parser.rs b/src/parser.rs
index 3a90b3ccb..222daa154 100644
--- a/src/parser.rs
+++ b/src/parser.rs
@@ -1309,21 +1309,21 @@ impl<'a> Parser<'a> {
                         Ok(Expr::Like {
                             negated,
                             expr: Box::new(expr),
-                            pattern: Box::new(self.parse_value()?),
+                            pattern: Box::new(self.parse_subexpr(Self::LIKE_PREC)?),
                             escape_char: self.parse_escape_char()?,
                         })
                     } else if self.parse_keyword(Keyword::ILIKE) {
                         Ok(Expr::ILike {
                             negated,
                             expr: Box::new(expr),
-                            pattern: Box::new(self.parse_value()?),
+                            pattern: Box::new(self.parse_subexpr(Self::LIKE_PREC)?),
                             escape_char: self.parse_escape_char()?,
                         })
                     } else if self.parse_keywords(&[Keyword::SIMILAR, Keyword::TO]) {
                         Ok(Expr::SimilarTo {
                             negated,
                             expr: Box::new(expr),
-                            pattern: Box::new(self.parse_value()?),
+                            pattern: Box::new(self.parse_subexpr(Self::LIKE_PREC)?),
                             escape_char: self.parse_escape_char()?,
                         })
                     } else {
@@ -1470,10 +1470,16 @@ impl<'a> Parser<'a> {
         })
     }
 
-    const UNARY_NOT_PREC: u8 = 15;
-    const BETWEEN_PREC: u8 = 20;
+    // use https://www.postgresql.org/docs/7.0/operators.htm#AEN2026 as a reference
     const PLUS_MINUS_PREC: u8 = 30;
+    const XOR_PREC: u8 = 24;
     const TIME_ZONE_PREC: u8 = 20;
+    const BETWEEN_PREC: u8 = 20;
+    const LIKE_PREC: u8 = 19;
+    const IS_PREC: u8 = 17;
+    const UNARY_NOT_PREC: u8 = 15;
+    const AND_PREC: u8 = 10;
+    const OR_PREC: u8 = 5;
 
     /// Get the precedence of the next token
     pub fn get_next_precedence(&self) -> Result<u8, ParserError> {
@@ -1484,9 +1490,9 @@ impl<'a> Parser<'a> {
         let token_2 = self.peek_nth_token(2);
         debug!("0: {token_0} 1: {token_1} 2: {token_2}");
         match token {
-            Token::Word(w) if w.keyword == Keyword::OR => Ok(5),
-            Token::Word(w) if w.keyword == Keyword::AND => Ok(10),
-            Token::Word(w) if w.keyword == Keyword::XOR => Ok(24),
+            Token::Word(w) if w.keyword == Keyword::OR => Ok(Self::OR_PREC),
+            Token::Word(w) if w.keyword == Keyword::AND => Ok(Self::AND_PREC),
+            Token::Word(w) if w.keyword == Keyword::XOR => Ok(Self::XOR_PREC),
 
             Token::Word(w) if w.keyword == Keyword::AT => {
                 match (self.peek_nth_token(1), self.peek_nth_token(2)) {
@@ -1507,18 +1513,18 @@ impl<'a> Parser<'a> {
                 // precedence.
                 Token::Word(w) if w.keyword == Keyword::IN => Ok(Self::BETWEEN_PREC),
                 Token::Word(w) if w.keyword == Keyword::BETWEEN => Ok(Self::BETWEEN_PREC),
-                Token::Word(w) if w.keyword == Keyword::LIKE => Ok(Self::BETWEEN_PREC),
-                Token::Word(w) if w.keyword == Keyword::ILIKE => Ok(Self::BETWEEN_PREC),
-                Token::Word(w) if w.keyword == Keyword::SIMILAR => Ok(Self::BETWEEN_PREC),
+                Token::Word(w) if w.keyword == Keyword::LIKE => Ok(Self::LIKE_PREC),
+                Token::Word(w) if w.keyword == Keyword::ILIKE => Ok(Self::LIKE_PREC),
+                Token::Word(w) if w.keyword == Keyword::SIMILAR => Ok(Self::LIKE_PREC),
                 _ => Ok(0),
             },
-            Token::Word(w) if w.keyword == Keyword::IS => Ok(17),
+            Token::Word(w) if w.keyword == Keyword::IS => Ok(Self::IS_PREC),
             Token::Word(w) if w.keyword == Keyword::IN => Ok(Self::BETWEEN_PREC),
             Token::Word(w) if w.keyword == Keyword::BETWEEN => Ok(Self::BETWEEN_PREC),
-            Token::Word(w) if w.keyword == Keyword::LIKE => Ok(Self::BETWEEN_PREC),
-            Token::Word(w) if w.keyword == Keyword::ILIKE => Ok(Self::BETWEEN_PREC),
+            Token::Word(w) if w.keyword == Keyword::LIKE => Ok(Self::LIKE_PREC),
+            Token::Word(w) if w.keyword == Keyword::ILIKE => Ok(Self::LIKE_PREC),
+            Token::Word(w) if w.keyword == Keyword::SIMILAR => Ok(Self::LIKE_PREC),
             Token::Word(w) if w.keyword == Keyword::OPERATOR => Ok(Self::BETWEEN_PREC),
-            Token::Word(w) if w.keyword == Keyword::SIMILAR => Ok(Self::BETWEEN_PREC),
             Token::Eq
             | Token::Lt
             | Token::LtEq


Test patch:
diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs
index c87b0c5e8..5f4b48567 100644
--- a/tests/sqlparser_common.rs
+++ b/tests/sqlparser_common.rs
@@ -862,7 +862,7 @@ fn parse_not_precedence() {
             expr: Box::new(Expr::Like {
                 expr: Box::new(Expr::Value(Value::SingleQuotedString("a".into()))),
                 negated: true,
-                pattern: Box::new(Value::SingleQuotedString("b".into())),
+                pattern: Box::new(Expr::Value(Value::SingleQuotedString("b".into()))),
                 escape_char: None
             }),
         },
@@ -895,7 +895,7 @@ fn parse_like() {
             Expr::Like {
                 expr: Box::new(Expr::Identifier(Ident::new("name"))),
                 negated,
-                pattern: Box::new(Value::SingleQuotedString("%a".to_string())),
+                pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))),
                 escape_char: None
             },
             select.selection.unwrap()
@@ -911,7 +911,7 @@ fn parse_like() {
             Expr::Like {
                 expr: Box::new(Expr::Identifier(Ident::new("name"))),
                 negated,
-                pattern: Box::new(Value::SingleQuotedString("%a".to_string())),
+                pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))),
                 escape_char: Some('\\')
             },
             select.selection.unwrap()
@@ -928,7 +928,7 @@ fn parse_like() {
             Expr::IsNull(Box::new(Expr::Like {
                 expr: Box::new(Expr::Identifier(Ident::new("name"))),
                 negated,
-                pattern: Box::new(Value::SingleQuotedString("%a".to_string())),
+                pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))),
                 escape_char: None
             })),
             select.selection.unwrap()
@@ -938,6 +938,45 @@ fn parse_like() {
     chk(true);
 }
 
+#[test]
+fn parse_null_like() {
+    let sql = "SELECT \
+            column1 LIKE NULL AS col_null, \
+            NULL LIKE column1 AS null_col \
+        FROM customers";
+    let select = verified_only_select(sql);
+    assert_eq!(
+        SelectItem::ExprWithAlias {
+            expr: Expr::Like {
+                expr: Box::new(Expr::Identifier(Ident::new("column1"))),
+                negated: false,
+                pattern: Box::new(Expr::Value(Value::Null)),
+                escape_char: None
+            },
+            alias: Ident {
+                value: "col_null".to_owned(),
+                quote_style: None
+            }
+        },
+        select.projection[0]
+    );
+    assert_eq!(
+        SelectItem::ExprWithAlias {
+            expr: Expr::Like {
+                expr: Box::new(Expr::Value(Value::Null)),
+                negated: false,
+                pattern: Box::new(Expr::Identifier(Ident::new("column1"))),
+                escape_char: None
+            },
+            alias: Ident {
+                value: "null_col".to_owned(),
+                quote_style: None
+            }
+        },
+        select.projection[1]
+    );
+}
+
 #[test]
 fn parse_ilike() {
     fn chk(negated: bool) {
@@ -950,7 +989,7 @@ fn parse_ilike() {
             Expr::ILike {
                 expr: Box::new(Expr::Identifier(Ident::new("name"))),
                 negated,
-                pattern: Box::new(Value::SingleQuotedString("%a".to_string())),
+                pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))),
                 escape_char: None
             },
             select.selection.unwrap()
@@ -966,7 +1005,7 @@ fn parse_ilike() {
             Expr::ILike {
                 expr: Box::new(Expr::Identifier(Ident::new("name"))),
                 negated,
-                pattern: Box::new(Value::SingleQuotedString("%a".to_string())),
+                pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))),
                 escape_char: Some('^')
             },
             select.selection.unwrap()
@@ -983,7 +1022,7 @@ fn parse_ilike() {
             Expr::IsNull(Box::new(Expr::ILike {
                 expr: Box::new(Expr::Identifier(Ident::new("name"))),
                 negated,
-                pattern: Box::new(Value::SingleQuotedString("%a".to_string())),
+                pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))),
                 escape_char: None
             })),
             select.selection.unwrap()
@@ -1005,7 +1044,7 @@ fn parse_similar_to() {
             Expr::SimilarTo {
                 expr: Box::new(Expr::Identifier(Ident::new("name"))),
                 negated,
-                pattern: Box::new(Value::SingleQuotedString("%a".to_string())),
+                pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))),
                 escape_char: None
             },
             select.selection.unwrap()
@@ -1021,7 +1060,7 @@ fn parse_similar_to() {
             Expr::SimilarTo {
                 expr: Box::new(Expr::Identifier(Ident::new("name"))),
                 negated,
-                pattern: Box::new(Value::SingleQuotedString("%a".to_string())),
+                pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))),
                 escape_char: Some('\\')
             },
             select.selection.unwrap()
@@ -1037,7 +1076,7 @@ fn parse_similar_to() {
             Expr::IsNull(Box::new(Expr::SimilarTo {
                 expr: Box::new(Expr::Identifier(Ident::new("name"))),
                 negated,
-                pattern: Box::new(Value::SingleQuotedString("%a".to_string())),
+                pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))),
                 escape_char: Some('\\')
             })),
             select.selection.unwrap()