diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 5f8eec8bc..462dae516 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -1546,6 +1546,7 @@ impl fmt::Display for TransactionIsolationLevel { #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum ShowStatementFilter { Like(String), + ILike(String), Where(Expr), } @@ -1554,6 +1555,7 @@ impl fmt::Display for ShowStatementFilter { use ShowStatementFilter::*; match self { Like(pattern) => write!(f, "LIKE '{}'", value::escape_single_quote_string(pattern)), + ILike(pattern) => write!(f, "ILIKE {}", value::escape_single_quote_string(pattern)), Where(expr) => write!(f, "WHERE {}", expr), } } diff --git a/src/ast/operator.rs b/src/ast/operator.rs index 732c81232..ff978fb97 100644 --- a/src/ast/operator.rs +++ b/src/ast/operator.rs @@ -72,6 +72,8 @@ pub enum BinaryOperator { Or, Like, NotLike, + ILike, + NotILike, BitwiseOr, BitwiseAnd, BitwiseXor, @@ -100,6 +102,8 @@ impl fmt::Display for BinaryOperator { BinaryOperator::Or => "OR", BinaryOperator::Like => "LIKE", BinaryOperator::NotLike => "NOT LIKE", + BinaryOperator::ILike => "ILIKE", + BinaryOperator::NotILike => "NOT ILIKE", BinaryOperator::BitwiseOr => "|", BinaryOperator::BitwiseAnd => "&", BinaryOperator::BitwiseXor => "^", diff --git a/src/dialect/keywords.rs b/src/dialect/keywords.rs index 3371ff570..8b88496b6 100644 --- a/src/dialect/keywords.rs +++ b/src/dialect/keywords.rs @@ -236,6 +236,7 @@ define_keywords!( IDENTITY, IF, IGNORE, + ILIKE, IN, INDEX, INDICATOR, diff --git a/src/parser.rs b/src/parser.rs index bacae7873..863fc66d0 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -839,9 +839,12 @@ impl<'a> Parser<'a> { Keyword::AND => Some(BinaryOperator::And), Keyword::OR => Some(BinaryOperator::Or), Keyword::LIKE => Some(BinaryOperator::Like), + Keyword::ILIKE => Some(BinaryOperator::ILike), Keyword::NOT => { if self.parse_keyword(Keyword::LIKE) { Some(BinaryOperator::NotLike) + } else if self.parse_keyword(Keyword::ILIKE) { + Some(BinaryOperator::NotILike) } else { None } @@ -975,12 +978,14 @@ impl<'a> Parser<'a> { 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), _ => Ok(0), }, Token::Word(w) if w.keyword == Keyword::IS => Ok(17), 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::Eq | Token::Lt | Token::LtEq @@ -1472,7 +1477,7 @@ impl<'a> Parser<'a> { ) -> Result { let if_not_exists = self.parse_keywords(&[Keyword::IF, Keyword::NOT, Keyword::EXISTS]); let table_name = self.parse_object_name()?; - let like = if self.parse_keyword(Keyword::LIKE) { + let like = if self.parse_keyword(Keyword::LIKE) || self.parse_keyword(Keyword::ILIKE) { self.parse_object_name().ok() } else { None @@ -2497,6 +2502,10 @@ impl<'a> Parser<'a> { Ok(Some(ShowStatementFilter::Like( self.parse_literal_string()?, ))) + } else if self.parse_keyword(Keyword::ILIKE) { + Ok(Some(ShowStatementFilter::ILike( + self.parse_literal_string()?, + ))) } else if self.parse_keyword(Keyword::WHERE) { Ok(Some(ShowStatementFilter::Where(self.parse_expr()?))) } else { diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 10ac79d84..e43bd12ce 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -687,6 +687,51 @@ fn parse_like() { chk(true); } +#[test] +fn parse_ilike() { + fn chk(negated: bool) { + let sql = &format!( + "SELECT * FROM customers WHERE name {}ILIKE '%a'", + if negated { "NOT " } else { "" } + ); + let select = verified_only_select(sql); + assert_eq!( + Expr::BinaryOp { + left: Box::new(Expr::Identifier(Ident::new("name"))), + op: if negated { + BinaryOperator::NotILike + } else { + BinaryOperator::ILike + }, + right: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), + }, + select.selection.unwrap() + ); + + // This statement tests that LIKE and NOT LIKE have the same precedence. + // This was previously mishandled (#81). + let sql = &format!( + "SELECT * FROM customers WHERE name {}ILIKE '%a' IS NULL", + if negated { "NOT " } else { "" } + ); + let select = verified_only_select(sql); + assert_eq!( + Expr::IsNull(Box::new(Expr::BinaryOp { + left: Box::new(Expr::Identifier(Ident::new("name"))), + op: if negated { + BinaryOperator::NotILike + } else { + BinaryOperator::ILike + }, + right: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), + })), + select.selection.unwrap() + ); + } + chk(false); + chk(true); +} + #[test] fn parse_in_list() { fn chk(negated: bool) {