You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@datafusion.apache.org by if...@apache.org on 2025/05/08 23:48:27 UTC

(datafusion-sqlparser-rs) branch main updated: Add support for the MATCH and REGEXP binary operators (#1840)

This is an automated email from the ASF dual-hosted git repository.

iffyio pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/datafusion-sqlparser-rs.git


The following commit(s) were added to refs/heads/main by this push:
     new 2182f7ea Add support for the MATCH and REGEXP binary operators (#1840)
2182f7ea is described below

commit 2182f7ea71242a7e9674932d559baef67dae522d
Author: Ophir LOJKINE <co...@ophir.dev>
AuthorDate: Fri May 9 01:48:23 2025 +0200

    Add support for the MATCH and REGEXP binary operators (#1840)
---
 src/ast/operator.rs       |  7 +++++++
 src/dialect/mod.rs        |  2 ++
 src/dialect/sqlite.rs     | 27 ++++++++++++++++++++++++++-
 tests/sqlparser_sqlite.rs | 30 ++++++++++++++++++++++++++++++
 4 files changed, 65 insertions(+), 1 deletion(-)

diff --git a/src/ast/operator.rs b/src/ast/operator.rs
index 73fe9cf4..d0bb05e3 100644
--- a/src/ast/operator.rs
+++ b/src/ast/operator.rs
@@ -139,6 +139,11 @@ pub enum BinaryOperator {
     DuckIntegerDivide,
     /// MySQL [`DIV`](https://dev.mysql.com/doc/refman/8.0/en/arithmetic-functions.html) integer division
     MyIntegerDivide,
+    /// MATCH operator, e.g. `a MATCH b` (SQLite-specific)
+    /// See <https://www.sqlite.org/lang_expr.html#the_like_glob_regexp_match_and_extract_operators>
+    Match,
+    /// REGEXP operator, e.g. `a REGEXP b` (SQLite-specific)
+    Regexp,
     /// Support for custom operators (such as Postgres custom operators)
     Custom(String),
     /// Bitwise XOR, e.g. `a # b` (PostgreSQL-specific)
@@ -350,6 +355,8 @@ impl fmt::Display for BinaryOperator {
             BinaryOperator::BitwiseXor => f.write_str("^"),
             BinaryOperator::DuckIntegerDivide => f.write_str("//"),
             BinaryOperator::MyIntegerDivide => f.write_str("DIV"),
+            BinaryOperator::Match => f.write_str("MATCH"),
+            BinaryOperator::Regexp => f.write_str("REGEXP"),
             BinaryOperator::Custom(s) => f.write_str(s),
             BinaryOperator::PGBitwiseXor => f.write_str("#"),
             BinaryOperator::PGBitwiseShiftLeft => f.write_str("<<"),
diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs
index b754a04f..6fbbc7a2 100644
--- a/src/dialect/mod.rs
+++ b/src/dialect/mod.rs
@@ -619,6 +619,7 @@ pub trait Dialect: Debug + Any {
                 Token::Word(w) if w.keyword == Keyword::ILIKE => Ok(p!(Like)),
                 Token::Word(w) if w.keyword == Keyword::RLIKE => Ok(p!(Like)),
                 Token::Word(w) if w.keyword == Keyword::REGEXP => Ok(p!(Like)),
+                Token::Word(w) if w.keyword == Keyword::MATCH => Ok(p!(Like)),
                 Token::Word(w) if w.keyword == Keyword::SIMILAR => Ok(p!(Like)),
                 _ => Ok(self.prec_unknown()),
             },
@@ -630,6 +631,7 @@ pub trait Dialect: Debug + Any {
             Token::Word(w) if w.keyword == Keyword::ILIKE => Ok(p!(Like)),
             Token::Word(w) if w.keyword == Keyword::RLIKE => Ok(p!(Like)),
             Token::Word(w) if w.keyword == Keyword::REGEXP => Ok(p!(Like)),
+            Token::Word(w) if w.keyword == Keyword::MATCH => Ok(p!(Like)),
             Token::Word(w) if w.keyword == Keyword::SIMILAR => Ok(p!(Like)),
             Token::Word(w) if w.keyword == Keyword::OPERATOR => Ok(p!(Between)),
             Token::Word(w) if w.keyword == Keyword::DIV => Ok(p!(MulDivModOp)),
diff --git a/src/dialect/sqlite.rs b/src/dialect/sqlite.rs
index 138c4692..847e0d13 100644
--- a/src/dialect/sqlite.rs
+++ b/src/dialect/sqlite.rs
@@ -15,7 +15,11 @@
 // specific language governing permissions and limitations
 // under the License.
 
-use crate::ast::Statement;
+#[cfg(not(feature = "std"))]
+use alloc::boxed::Box;
+
+use crate::ast::BinaryOperator;
+use crate::ast::{Expr, Statement};
 use crate::dialect::Dialect;
 use crate::keywords::Keyword;
 use crate::parser::{Parser, ParserError};
@@ -70,6 +74,27 @@ impl Dialect for SQLiteDialect {
         }
     }
 
+    fn parse_infix(
+        &self,
+        parser: &mut crate::parser::Parser,
+        expr: &crate::ast::Expr,
+        _precedence: u8,
+    ) -> Option<Result<crate::ast::Expr, ParserError>> {
+        // Parse MATCH and REGEXP as operators
+        // See <https://www.sqlite.org/lang_expr.html#the_like_glob_regexp_match_and_extract_operators>
+        for (keyword, op) in [
+            (Keyword::REGEXP, BinaryOperator::Regexp),
+            (Keyword::MATCH, BinaryOperator::Match),
+        ] {
+            if parser.parse_keyword(keyword) {
+                let left = Box::new(expr.clone());
+                let right = Box::new(parser.parse_expr().unwrap());
+                return Some(Ok(Expr::BinaryOp { left, op, right }));
+            }
+        }
+        None
+    }
+
     fn supports_in_empty_list(&self) -> bool {
         true
     }
diff --git a/tests/sqlparser_sqlite.rs b/tests/sqlparser_sqlite.rs
index 361c9b05..b759065f 100644
--- a/tests/sqlparser_sqlite.rs
+++ b/tests/sqlparser_sqlite.rs
@@ -562,6 +562,36 @@ fn test_dollar_identifier_as_placeholder() {
     }
 }
 
+#[test]
+fn test_match_operator() {
+    assert_eq!(
+        sqlite().verified_expr("col MATCH 'pattern'"),
+        Expr::BinaryOp {
+            op: BinaryOperator::Match,
+            left: Box::new(Expr::Identifier(Ident::new("col"))),
+            right: Box::new(Expr::Value(
+                (Value::SingleQuotedString("pattern".to_string())).with_empty_span()
+            ))
+        }
+    );
+    sqlite().verified_only_select("SELECT * FROM email WHERE email MATCH 'fts5'");
+}
+
+#[test]
+fn test_regexp_operator() {
+    assert_eq!(
+        sqlite().verified_expr("col REGEXP 'pattern'"),
+        Expr::BinaryOp {
+            op: BinaryOperator::Regexp,
+            left: Box::new(Expr::Identifier(Ident::new("col"))),
+            right: Box::new(Expr::Value(
+                (Value::SingleQuotedString("pattern".to_string())).with_empty_span()
+            ))
+        }
+    );
+    sqlite().verified_only_select(r#"SELECT count(*) FROM messages WHERE msg_text REGEXP '\d+'"#);
+}
+
 fn sqlite() -> TestedDialects {
     TestedDialects::new(vec![Box::new(SQLiteDialect {})])
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@datafusion.apache.org
For additional commands, e-mail: commits-help@datafusion.apache.org