Skip to content

Commit 2182f7e

Browse files
authored
Add support for the MATCH and REGEXP binary operators (#1840)
1 parent 6cd237e commit 2182f7e

File tree

4 files changed

+65
-1
lines changed

4 files changed

+65
-1
lines changed

src/ast/operator.rs

+7
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,11 @@ pub enum BinaryOperator {
139139
DuckIntegerDivide,
140140
/// MySQL [`DIV`](https://dev.mysql.com/doc/refman/8.0/en/arithmetic-functions.html) integer division
141141
MyIntegerDivide,
142+
/// MATCH operator, e.g. `a MATCH b` (SQLite-specific)
143+
/// See <https://www.sqlite.org/lang_expr.html#the_like_glob_regexp_match_and_extract_operators>
144+
Match,
145+
/// REGEXP operator, e.g. `a REGEXP b` (SQLite-specific)
146+
Regexp,
142147
/// Support for custom operators (such as Postgres custom operators)
143148
Custom(String),
144149
/// Bitwise XOR, e.g. `a # b` (PostgreSQL-specific)
@@ -350,6 +355,8 @@ impl fmt::Display for BinaryOperator {
350355
BinaryOperator::BitwiseXor => f.write_str("^"),
351356
BinaryOperator::DuckIntegerDivide => f.write_str("//"),
352357
BinaryOperator::MyIntegerDivide => f.write_str("DIV"),
358+
BinaryOperator::Match => f.write_str("MATCH"),
359+
BinaryOperator::Regexp => f.write_str("REGEXP"),
353360
BinaryOperator::Custom(s) => f.write_str(s),
354361
BinaryOperator::PGBitwiseXor => f.write_str("#"),
355362
BinaryOperator::PGBitwiseShiftLeft => f.write_str("<<"),

src/dialect/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -619,6 +619,7 @@ pub trait Dialect: Debug + Any {
619619
Token::Word(w) if w.keyword == Keyword::ILIKE => Ok(p!(Like)),
620620
Token::Word(w) if w.keyword == Keyword::RLIKE => Ok(p!(Like)),
621621
Token::Word(w) if w.keyword == Keyword::REGEXP => Ok(p!(Like)),
622+
Token::Word(w) if w.keyword == Keyword::MATCH => Ok(p!(Like)),
622623
Token::Word(w) if w.keyword == Keyword::SIMILAR => Ok(p!(Like)),
623624
_ => Ok(self.prec_unknown()),
624625
},
@@ -630,6 +631,7 @@ pub trait Dialect: Debug + Any {
630631
Token::Word(w) if w.keyword == Keyword::ILIKE => Ok(p!(Like)),
631632
Token::Word(w) if w.keyword == Keyword::RLIKE => Ok(p!(Like)),
632633
Token::Word(w) if w.keyword == Keyword::REGEXP => Ok(p!(Like)),
634+
Token::Word(w) if w.keyword == Keyword::MATCH => Ok(p!(Like)),
633635
Token::Word(w) if w.keyword == Keyword::SIMILAR => Ok(p!(Like)),
634636
Token::Word(w) if w.keyword == Keyword::OPERATOR => Ok(p!(Between)),
635637
Token::Word(w) if w.keyword == Keyword::DIV => Ok(p!(MulDivModOp)),

src/dialect/sqlite.rs

+26-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,11 @@
1515
// specific language governing permissions and limitations
1616
// under the License.
1717

18-
use crate::ast::Statement;
18+
#[cfg(not(feature = "std"))]
19+
use alloc::boxed::Box;
20+
21+
use crate::ast::BinaryOperator;
22+
use crate::ast::{Expr, Statement};
1923
use crate::dialect::Dialect;
2024
use crate::keywords::Keyword;
2125
use crate::parser::{Parser, ParserError};
@@ -70,6 +74,27 @@ impl Dialect for SQLiteDialect {
7074
}
7175
}
7276

77+
fn parse_infix(
78+
&self,
79+
parser: &mut crate::parser::Parser,
80+
expr: &crate::ast::Expr,
81+
_precedence: u8,
82+
) -> Option<Result<crate::ast::Expr, ParserError>> {
83+
// Parse MATCH and REGEXP as operators
84+
// See <https://www.sqlite.org/lang_expr.html#the_like_glob_regexp_match_and_extract_operators>
85+
for (keyword, op) in [
86+
(Keyword::REGEXP, BinaryOperator::Regexp),
87+
(Keyword::MATCH, BinaryOperator::Match),
88+
] {
89+
if parser.parse_keyword(keyword) {
90+
let left = Box::new(expr.clone());
91+
let right = Box::new(parser.parse_expr().unwrap());
92+
return Some(Ok(Expr::BinaryOp { left, op, right }));
93+
}
94+
}
95+
None
96+
}
97+
7398
fn supports_in_empty_list(&self) -> bool {
7499
true
75100
}

tests/sqlparser_sqlite.rs

+30
Original file line numberDiff line numberDiff line change
@@ -562,6 +562,36 @@ fn test_dollar_identifier_as_placeholder() {
562562
}
563563
}
564564

565+
#[test]
566+
fn test_match_operator() {
567+
assert_eq!(
568+
sqlite().verified_expr("col MATCH 'pattern'"),
569+
Expr::BinaryOp {
570+
op: BinaryOperator::Match,
571+
left: Box::new(Expr::Identifier(Ident::new("col"))),
572+
right: Box::new(Expr::Value(
573+
(Value::SingleQuotedString("pattern".to_string())).with_empty_span()
574+
))
575+
}
576+
);
577+
sqlite().verified_only_select("SELECT * FROM email WHERE email MATCH 'fts5'");
578+
}
579+
580+
#[test]
581+
fn test_regexp_operator() {
582+
assert_eq!(
583+
sqlite().verified_expr("col REGEXP 'pattern'"),
584+
Expr::BinaryOp {
585+
op: BinaryOperator::Regexp,
586+
left: Box::new(Expr::Identifier(Ident::new("col"))),
587+
right: Box::new(Expr::Value(
588+
(Value::SingleQuotedString("pattern".to_string())).with_empty_span()
589+
))
590+
}
591+
);
592+
sqlite().verified_only_select(r#"SELECT count(*) FROM messages WHERE msg_text REGEXP '\d+'"#);
593+
}
594+
565595
fn sqlite() -> TestedDialects {
566596
TestedDialects::new(vec![Box::new(SQLiteDialect {})])
567597
}

0 commit comments

Comments
 (0)