Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/ast/operator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,10 @@ pub enum BinaryOperator {
/// ':=' Assignment Operator
/// See <https://dev.mysql.com/doc/refman/8.4/en/assignment-operators.html#operator_assign-value>
Assignment,
/// `IS` operator
Is,
/// `IS NOT` operator
IsNot,
}

impl fmt::Display for BinaryOperator {
Expand Down Expand Up @@ -409,6 +413,8 @@ impl fmt::Display for BinaryOperator {
BinaryOperator::At => f.write_str("@"),
BinaryOperator::TildeEq => f.write_str("~="),
BinaryOperator::Assignment => f.write_str(":="),
BinaryOperator::Is => f.write_str("IS"),
BinaryOperator::IsNot => f.write_str("IS NOT"),
}
}
}
5 changes: 5 additions & 0 deletions src/dialect/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1518,6 +1518,11 @@ pub trait Dialect: Debug + Any {
false
}

/// Returns true if the dialect supports binary `IS` and `IS NOT` operators.
fn supports_is_operator(&self) -> bool {
false
}

/// Returns true if this dialect allows an optional `SIGNED` suffix after integer data types.
///
/// Example:
Expand Down
4 changes: 4 additions & 0 deletions src/dialect/sqlite.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,10 @@ impl Dialect for SQLiteDialect {
true
}

fn supports_is_operator(&self) -> bool {
true
}

fn supports_comma_separated_trim(&self) -> bool {
true
}
Expand Down
45 changes: 37 additions & 8 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4107,6 +4107,27 @@ impl<'a> Parser<'a> {
self.parse_is_json_predicate(expr, false)
} else if self.parse_keywords(&[Keyword::NOT, Keyword::JSON]) {
self.parse_is_json_predicate(expr, true)
} else if self.dialect.supports_is_operator() {
if let Some((form, negated)) =
self.maybe_parse(|parser| parser.parse_unicode_is_normalized_suffix())?
{
Ok(Expr::IsNormalized {
expr: Box::new(expr),
form,
negated,
})
} else {
let op = if self.parse_keyword(Keyword::NOT) {
BinaryOperator::IsNot
} else {
BinaryOperator::Is
};
Ok(Expr::BinaryOp {
left: Box::new(expr),
op,
right: Box::new(self.parse_subexpr(precedence)?),
})
}
} else if let Ok(is_normalized) = self.parse_unicode_is_normalized(expr) {
Ok(is_normalized)
} else {
Expand Down Expand Up @@ -12737,8 +12758,19 @@ impl<'a> Parser<'a> {

/// Parse a literal unicode normalization clause
pub fn parse_unicode_is_normalized(&mut self, expr: Expr) -> Result<Expr, ParserError> {
let neg = self.parse_keyword(Keyword::NOT);
let normalized_form = self.maybe_parse(|parser| {
let (form, negated) = self.parse_unicode_is_normalized_suffix()?;
Ok(Expr::IsNormalized {
expr: Box::new(expr),
form,
negated,
})
}

fn parse_unicode_is_normalized_suffix(
&mut self,
) -> Result<(Option<NormalizationForm>, bool), ParserError> {
let negated = self.parse_keyword(Keyword::NOT);
let form = self.maybe_parse(|parser| {
match parser.parse_one_of_keywords(&[
Keyword::NFC,
Keyword::NFD,
Expand All @@ -12753,13 +12785,10 @@ impl<'a> Parser<'a> {
}
})?;
if self.parse_keyword(Keyword::NORMALIZED) {
return Ok(Expr::IsNormalized {
expr: Box::new(expr),
form: normalized_form,
negated: neg,
});
Ok((form, negated))
} else {
self.expected_ref("unicode normalization form", self.peek_token_ref())
}
self.expected_ref("unicode normalization form", self.peek_token_ref())
}

/// Parse parenthesized enum members, used with `ENUM(...)` type definitions.
Expand Down
4 changes: 3 additions & 1 deletion tests/sqlparser_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11258,7 +11258,9 @@ fn parse_is_boolean() {
"SELECT s, s IS TRIM(' NFKC ') FROM foo",
] {
assert!(
parse_sql_statements(sql).is_err(),
all_dialects_except(|dialect| dialect.supports_is_operator())
.parse_sql_statements(sql)
.is_err(),
"expected a parse failure for `{sql}`"
);
}
Expand Down
47 changes: 47 additions & 0 deletions tests/sqlparser_sqlite.rs
Original file line number Diff line number Diff line change
Expand Up @@ -957,6 +957,53 @@ fn parse_pattern_operators_bind_at_like_precedence() {
}
}

#[test]
fn sqlite_is_operators() {
for (sql, op) in [
("a IS b", BinaryOperator::Is),
("a IS NOT b", BinaryOperator::IsNot),
] {
assert_eq!(
sqlite().verified_expr(sql),
Expr::BinaryOp {
left: Box::new(Expr::Identifier(Ident::new("a"))),
op,
right: Box::new(Expr::Identifier(Ident::new("b"))),
}
);
}

assert_eq!(
sqlite().verified_expr("a IS b + c"),
Expr::BinaryOp {
left: Box::new(Expr::Identifier(Ident::new("a"))),
op: BinaryOperator::Is,
right: Box::new(Expr::BinaryOp {
left: Box::new(Expr::Identifier(Ident::new("b"))),
op: BinaryOperator::Plus,
right: Box::new(Expr::Identifier(Ident::new("c"))),
}),
}
);

assert_eq!(
sqlite().verified_expr("a IS b AND c IS NOT d"),
Expr::BinaryOp {
left: Box::new(Expr::BinaryOp {
left: Box::new(Expr::Identifier(Ident::new("a"))),
op: BinaryOperator::Is,
right: Box::new(Expr::Identifier(Ident::new("b"))),
}),
op: BinaryOperator::And,
right: Box::new(Expr::BinaryOp {
left: Box::new(Expr::Identifier(Ident::new("c"))),
op: BinaryOperator::IsNot,
right: Box::new(Expr::Identifier(Ident::new("d"))),
}),
}
);
}

fn sqlite() -> TestedDialects {
TestedDialects::new(vec![Box::new(SQLiteDialect {})])
}
Expand Down
Loading