Skip to content

Commit 21f076c

Browse files
ayushdgmcheshkov
authored andcommitted
Support for SIMILAR TO syntax, change Like and ILike to Expr variants, allow escape char for like/ilike (apache#569)
* Remove [not]like,[not]ilike from binary operator list * Add like, ilike and similar as an expr variant. Also adds support for escape char to like/ilike * Add parsing logic for similar to, update parsing logic for like/ilike * Add tests for similar to, update tests for like/ilike * Fix linter warnings * remove additional copyright license from files * Add more coverage w/wo escape char for like,ilike,similar to Can drop this after rebase on commit f07063f " Support for SIMILAR TO syntax, change Like and ILike to Expr variants, allow escape char for like/ilike (apache#569)", first released in 0.21.0
1 parent 9c9cebb commit 21f076c

File tree

4 files changed

+237
-57
lines changed

4 files changed

+237
-57
lines changed

src/ast/mod.rs

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,27 @@ pub enum Expr {
275275
op: BinaryOperator,
276276
right: Box<Expr>,
277277
},
278+
/// LIKE
279+
Like {
280+
negated: bool,
281+
expr: Box<Expr>,
282+
pattern: Box<Value>,
283+
escape_char: Option<char>,
284+
},
285+
/// ILIKE (case-insensitive LIKE)
286+
ILike {
287+
negated: bool,
288+
expr: Box<Expr>,
289+
pattern: Box<Value>,
290+
escape_char: Option<char>,
291+
},
292+
/// SIMILAR TO regex
293+
SimilarTo {
294+
negated: bool,
295+
expr: Box<Expr>,
296+
pattern: Box<Value>,
297+
escape_char: Option<char>,
298+
},
278299
/// Any operation e.g. `1 ANY (1)` or `foo > ANY(bar)`, It will be wrapped in the right side of BinaryExpr
279300
AnyOp(Box<Expr>),
280301
/// ALL operation e.g. `1 ALL (1)` or `foo > ALL(bar)`, It will be wrapped in the right side of BinaryExpr
@@ -447,6 +468,72 @@ impl fmt::Display for Expr {
447468
high
448469
),
449470
Expr::BinaryOp { left, op, right } => write!(f, "{} {} {}", left, op, right),
471+
Expr::Like {
472+
negated,
473+
expr,
474+
pattern,
475+
escape_char,
476+
} => match escape_char {
477+
Some(ch) => write!(
478+
f,
479+
"{} {}LIKE {} ESCAPE '{}'",
480+
expr,
481+
if *negated { "NOT " } else { "" },
482+
pattern,
483+
ch
484+
),
485+
_ => write!(
486+
f,
487+
"{} {}LIKE {}",
488+
expr,
489+
if *negated { "NOT " } else { "" },
490+
pattern
491+
),
492+
},
493+
Expr::ILike {
494+
negated,
495+
expr,
496+
pattern,
497+
escape_char,
498+
} => match escape_char {
499+
Some(ch) => write!(
500+
f,
501+
"{} {}ILIKE {} ESCAPE '{}'",
502+
expr,
503+
if *negated { "NOT " } else { "" },
504+
pattern,
505+
ch
506+
),
507+
_ => write!(
508+
f,
509+
"{} {}ILIKE {}",
510+
expr,
511+
if *negated { "NOT " } else { "" },
512+
pattern
513+
),
514+
},
515+
Expr::SimilarTo {
516+
negated,
517+
expr,
518+
pattern,
519+
escape_char,
520+
} => match escape_char {
521+
Some(ch) => write!(
522+
f,
523+
"{} {}SIMILAR TO {} ESCAPE '{}'",
524+
expr,
525+
if *negated { "NOT " } else { "" },
526+
pattern,
527+
ch
528+
),
529+
_ => write!(
530+
f,
531+
"{} {}SIMILAR TO {}",
532+
expr,
533+
if *negated { "NOT " } else { "" },
534+
pattern
535+
),
536+
},
450537
Expr::AnyOp(expr) => write!(f, "ANY({})", expr),
451538
Expr::AllOp(expr) => write!(f, "ALL({})", expr),
452539
Expr::UnaryOp { op, expr } => {

src/ast/operator.rs

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -72,10 +72,6 @@ pub enum BinaryOperator {
7272
And,
7373
Or,
7474
Xor,
75-
Like,
76-
NotLike,
77-
ILike,
78-
NotILike,
7975
BitwiseOr,
8076
BitwiseAnd,
8177
BitwiseXor,
@@ -107,10 +103,6 @@ impl fmt::Display for BinaryOperator {
107103
BinaryOperator::And => "AND",
108104
BinaryOperator::Or => "OR",
109105
BinaryOperator::Xor => "XOR",
110-
BinaryOperator::Like => "LIKE",
111-
BinaryOperator::NotLike => "NOT LIKE",
112-
BinaryOperator::ILike => "ILIKE",
113-
BinaryOperator::NotILike => "NOT ILIKE",
114106
BinaryOperator::BitwiseOr => "|",
115107
BinaryOperator::BitwiseAnd => "&",
116108
BinaryOperator::BitwiseXor => "^",

src/parser.rs

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1141,17 +1141,6 @@ impl<'a> Parser<'a> {
11411141
Token::Word(w) => match w.keyword {
11421142
Keyword::AND => Some(BinaryOperator::And),
11431143
Keyword::OR => Some(BinaryOperator::Or),
1144-
Keyword::LIKE => Some(BinaryOperator::Like),
1145-
Keyword::ILIKE => Some(BinaryOperator::ILike),
1146-
Keyword::NOT => {
1147-
if self.parse_keyword(Keyword::LIKE) {
1148-
Some(BinaryOperator::NotLike)
1149-
} else if self.parse_keyword(Keyword::ILIKE) {
1150-
Some(BinaryOperator::NotILike)
1151-
} else {
1152-
None
1153-
}
1154-
}
11551144
Keyword::XOR => Some(BinaryOperator::Xor),
11561145
_ => None,
11571146
},
@@ -1237,13 +1226,39 @@ impl<'a> Parser<'a> {
12371226
self.expected("Expected Token::Word after AT", tok)
12381227
}
12391228
}
1240-
Keyword::NOT | Keyword::IN | Keyword::BETWEEN => {
1229+
Keyword::NOT
1230+
| Keyword::IN
1231+
| Keyword::BETWEEN
1232+
| Keyword::LIKE
1233+
| Keyword::ILIKE
1234+
| Keyword::SIMILAR => {
12411235
self.prev_token();
12421236
let negated = self.parse_keyword(Keyword::NOT);
12431237
if self.parse_keyword(Keyword::IN) {
12441238
self.parse_in(expr, negated)
12451239
} else if self.parse_keyword(Keyword::BETWEEN) {
12461240
self.parse_between(expr, negated)
1241+
} else if self.parse_keyword(Keyword::LIKE) {
1242+
Ok(Expr::Like {
1243+
negated,
1244+
expr: Box::new(expr),
1245+
pattern: Box::new(self.parse_value()?),
1246+
escape_char: self.parse_escape_char()?,
1247+
})
1248+
} else if self.parse_keyword(Keyword::ILIKE) {
1249+
Ok(Expr::ILike {
1250+
negated,
1251+
expr: Box::new(expr),
1252+
pattern: Box::new(self.parse_value()?),
1253+
escape_char: self.parse_escape_char()?,
1254+
})
1255+
} else if self.parse_keywords(&[Keyword::SIMILAR, Keyword::TO]) {
1256+
Ok(Expr::SimilarTo {
1257+
negated,
1258+
expr: Box::new(expr),
1259+
pattern: Box::new(self.parse_value()?),
1260+
escape_char: self.parse_escape_char()?,
1261+
})
12471262
} else {
12481263
self.expected("IN or BETWEEN after NOT", self.peek_token())
12491264
}
@@ -1288,6 +1303,15 @@ impl<'a> Parser<'a> {
12881303
}
12891304
}
12901305

1306+
/// parse the ESCAPE CHAR portion of LIKE, ILIKE, and SIMILAR TO
1307+
pub fn parse_escape_char(&mut self) -> Result<Option<char>, ParserError> {
1308+
if self.parse_keyword(Keyword::ESCAPE) {
1309+
Ok(Some(self.parse_literal_char()?))
1310+
} else {
1311+
Ok(None)
1312+
}
1313+
}
1314+
12911315
pub fn parse_array_index(&mut self, expr: Expr) -> Result<Expr, ParserError> {
12921316
let index = self.parse_expr()?;
12931317
self.expect_token(&Token::RBracket)?;
@@ -1418,13 +1442,15 @@ impl<'a> Parser<'a> {
14181442
Token::Word(w) if w.keyword == Keyword::BETWEEN => Ok(Self::BETWEEN_PREC),
14191443
Token::Word(w) if w.keyword == Keyword::LIKE => Ok(Self::BETWEEN_PREC),
14201444
Token::Word(w) if w.keyword == Keyword::ILIKE => Ok(Self::BETWEEN_PREC),
1445+
Token::Word(w) if w.keyword == Keyword::SIMILAR => Ok(Self::BETWEEN_PREC),
14211446
_ => Ok(0),
14221447
},
14231448
Token::Word(w) if w.keyword == Keyword::IS => Ok(17),
14241449
Token::Word(w) if w.keyword == Keyword::IN => Ok(Self::BETWEEN_PREC),
14251450
Token::Word(w) if w.keyword == Keyword::BETWEEN => Ok(Self::BETWEEN_PREC),
14261451
Token::Word(w) if w.keyword == Keyword::LIKE => Ok(Self::BETWEEN_PREC),
14271452
Token::Word(w) if w.keyword == Keyword::ILIKE => Ok(Self::BETWEEN_PREC),
1453+
Token::Word(w) if w.keyword == Keyword::SIMILAR => Ok(Self::BETWEEN_PREC),
14281454
Token::Eq
14291455
| Token::Lt
14301456
| Token::LtEq

0 commit comments

Comments
 (0)