diff --git a/src/ast/operator.rs b/src/ast/operator.rs index c3bb379d6..e44ea2bf4 100644 --- a/src/ast/operator.rs +++ b/src/ast/operator.rs @@ -51,6 +51,8 @@ pub enum UnaryOperator { PGPrefixFactorial, /// Absolute value, e.g. `@ -9` (PostgreSQL-specific) PGAbs, + /// Unary logical not operator: e.g. `! false` (Hive-specific) + BangNot, } impl fmt::Display for UnaryOperator { @@ -65,6 +67,7 @@ impl fmt::Display for UnaryOperator { UnaryOperator::PGPostfixFactorial => "!", UnaryOperator::PGPrefixFactorial => "!!", UnaryOperator::PGAbs => "@", + UnaryOperator::BangNot => "!", }) } } diff --git a/src/dialect/hive.rs b/src/dialect/hive.rs index 63642b33c..b97bf69be 100644 --- a/src/dialect/hive.rs +++ b/src/dialect/hive.rs @@ -51,4 +51,9 @@ impl Dialect for HiveDialect { fn require_interval_qualifier(&self) -> bool { true } + + /// See Hive + fn supports_bang_not_operator(&self) -> bool { + true + } } diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs index 453fee3de..7592740ca 100644 --- a/src/dialect/mod.rs +++ b/src/dialect/mod.rs @@ -575,6 +575,11 @@ pub trait Dialect: Debug + Any { false } + /// Returns true if the dialect supports `a!` expressions + fn supports_factorial_operator(&self) -> bool { + false + } + /// Returns true if this dialect supports treating the equals operator `=` within a `SelectItem` /// as an alias assignment operator, rather than a boolean expression. /// For example: the following statements are equivalent for such a dialect: @@ -591,6 +596,11 @@ pub trait Dialect: Debug + Any { false } + /// Returns true if the dialect supports `!a` syntax for boolean `NOT` expressions. + fn supports_bang_not_operator(&self) -> bool { + false + } + /// Returns true if the dialect supports the `LISTEN` statement fn supports_listen(&self) -> bool { false diff --git a/src/dialect/postgresql.rs b/src/dialect/postgresql.rs index c40c826c4..72841c604 100644 --- a/src/dialect/postgresql.rs +++ b/src/dialect/postgresql.rs @@ -201,6 +201,11 @@ impl Dialect for PostgreSqlDialect { fn supports_notify(&self) -> bool { true } + + /// see + fn supports_factorial_operator(&self) -> bool { + true + } } pub fn parse_comment(parser: &mut Parser) -> Result { diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 942ff19fd..e329c0177 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -1194,6 +1194,14 @@ impl<'a> Parser<'a> { ), }) } + Token::ExclamationMark if self.dialect.supports_bang_not_operator() => { + Ok(Expr::UnaryOp { + op: UnaryOperator::BangNot, + expr: Box::new( + self.parse_subexpr(self.dialect.prec_value(Precedence::UnaryNot))?, + ), + }) + } tok @ Token::DoubleExclamationMark | tok @ Token::PGSquareRoot | tok @ Token::PGCubeRoot @@ -1287,7 +1295,6 @@ impl<'a> Parser<'a> { } _ => self.expected("an expression", next_token), }?; - if self.parse_keyword(Keyword::COLLATE) { Ok(Expr::Collate { expr: Box::new(expr), @@ -2818,8 +2825,7 @@ impl<'a> Parser<'a> { data_type: self.parse_data_type()?, format: None, }) - } else if Token::ExclamationMark == tok { - // PostgreSQL factorial operation + } else if Token::ExclamationMark == tok && self.dialect.supports_factorial_operator() { Ok(Expr::UnaryOp { op: UnaryOperator::PGPostfixFactorial, expr: Box::new(expr), diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index e37280636..84f2f718b 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -11532,3 +11532,113 @@ fn test_select_top() { dialects.verified_stmt("SELECT TOP 3 DISTINCT * FROM tbl"); dialects.verified_stmt("SELECT TOP 3 DISTINCT a, b, c FROM tbl"); } + +#[test] +fn parse_bang_not() { + let dialects = all_dialects_where(|d| d.supports_bang_not_operator()); + let sql = "SELECT !a, !(b > 3)"; + let Select { projection, .. } = dialects.verified_only_select(sql); + + for (i, expr) in [ + Box::new(Expr::Identifier(Ident::new("a"))), + Box::new(Expr::Nested(Box::new(Expr::BinaryOp { + left: Box::new(Expr::Identifier(Ident::new("b"))), + op: BinaryOperator::Gt, + right: Box::new(Expr::Value(Value::Number("3".parse().unwrap(), false))), + }))), + ] + .into_iter() + .enumerate() + { + assert_eq!( + SelectItem::UnnamedExpr(Expr::UnaryOp { + op: UnaryOperator::BangNot, + expr + }), + projection[i] + ) + } + + let sql_statements = ["SELECT a!", "SELECT a ! b", "SELECT a ! as b"]; + + for &sql in &sql_statements { + assert_eq!( + dialects.parse_sql_statements(sql).unwrap_err(), + ParserError::ParserError("No infix parser for token ExclamationMark".to_string()) + ); + } + + let sql_statements = ["SELECT !a", "SELECT !a b", "SELECT !a as b"]; + let dialects = all_dialects_where(|d| !d.supports_bang_not_operator()); + + for &sql in &sql_statements { + assert_eq!( + dialects.parse_sql_statements(sql).unwrap_err(), + ParserError::ParserError("Expected: an expression, found: !".to_string()) + ); + } +} + +#[test] +fn parse_factorial_operator() { + let dialects = all_dialects_where(|d| d.supports_factorial_operator()); + let sql = "SELECT a!, (b + c)!"; + let Select { projection, .. } = dialects.verified_only_select(sql); + + for (i, expr) in [ + Box::new(Expr::Identifier(Ident::new("a"))), + Box::new(Expr::Nested(Box::new(Expr::BinaryOp { + left: Box::new(Expr::Identifier(Ident::new("b"))), + op: BinaryOperator::Plus, + right: Box::new(Expr::Identifier(Ident::new("c"))), + }))), + ] + .into_iter() + .enumerate() + { + assert_eq!( + SelectItem::UnnamedExpr(Expr::UnaryOp { + op: UnaryOperator::PGPostfixFactorial, + expr + }), + projection[i] + ) + } + + let sql_statements = ["SELECT !a", "SELECT !a b", "SELECT !a as b"]; + + for &sql in &sql_statements { + assert_eq!( + dialects.parse_sql_statements(sql).unwrap_err(), + ParserError::ParserError("Expected: an expression, found: !".to_string()) + ); + } + + let sql_statements = ["SELECT a!", "SELECT a ! b", "SELECT a ! as b"]; + + // Due to the exclamation mark, which is both part of the `bang not` operator + // and the `factorial` operator, additional filtering not supports + // `bang not` operator is required here. + let dialects = + all_dialects_where(|d| !d.supports_factorial_operator() && !d.supports_bang_not_operator()); + + for &sql in &sql_statements { + assert_eq!( + dialects.parse_sql_statements(sql).unwrap_err(), + ParserError::ParserError("No infix parser for token ExclamationMark".to_string()) + ); + } + + // Due to the exclamation mark, which is both part of the `bang not` operator + // and the `factorial` operator, additional filtering supports + // `bang not` operator is required here. + let dialects = + all_dialects_where(|d| !d.supports_factorial_operator() && d.supports_bang_not_operator()); + + for &sql in &sql_statements { + assert_eq!( + dialects.parse_sql_statements(sql).unwrap_err(), + ParserError::ParserError("No infix parser for token ExclamationMark".to_string()) + ); + } +}