Skip to content

Commit deb38db

Browse files
committed
feat: add support for special bang not operator !a and raise error for a! factorial operator in Hive dialect
1 parent e2197ee commit deb38db

File tree

6 files changed

+142
-3
lines changed

6 files changed

+142
-3
lines changed

src/ast/operator.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ pub enum UnaryOperator {
5151
PGPrefixFactorial,
5252
/// Absolute value, e.g. `@ -9` (PostgreSQL-specific)
5353
PGAbs,
54+
/// Unary logical not operator: e.g. `! false` (Hive-specific)
55+
BangNot,
5456
}
5557

5658
impl fmt::Display for UnaryOperator {
@@ -65,6 +67,7 @@ impl fmt::Display for UnaryOperator {
6567
UnaryOperator::PGPostfixFactorial => "!",
6668
UnaryOperator::PGPrefixFactorial => "!!",
6769
UnaryOperator::PGAbs => "@",
70+
UnaryOperator::BangNot => "!",
6871
})
6972
}
7073
}

src/dialect/hive.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,4 +51,9 @@ impl Dialect for HiveDialect {
5151
fn require_interval_qualifier(&self) -> bool {
5252
true
5353
}
54+
55+
/// See Hive <https://cwiki.apache.org/confluence/pages/viewpage.action?pageId=27362061#Tutorial-BuiltInOperators>
56+
fn supports_bang_not_operator(&self) -> bool {
57+
true
58+
}
5459
}

src/dialect/mod.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -575,6 +575,11 @@ pub trait Dialect: Debug + Any {
575575
false
576576
}
577577

578+
/// Returns true if the dialect supports `a!` expressions
579+
fn supports_factorial_operator(&self) -> bool {
580+
false
581+
}
582+
578583
/// Returns true if this dialect supports treating the equals operator `=` within a `SelectItem`
579584
/// as an alias assignment operator, rather than a boolean expression.
580585
/// For example: the following statements are equivalent for such a dialect:
@@ -590,6 +595,11 @@ pub trait Dialect: Debug + Any {
590595
fn supports_try_convert(&self) -> bool {
591596
false
592597
}
598+
599+
/// Returns true if the dialect supports `!a` syntax for boolean `NOT` expressions.
600+
fn supports_bang_not_operator(&self) -> bool {
601+
false
602+
}
593603
}
594604

595605
/// This represents the operators for which precedence must be defined

src/dialect/postgresql.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,11 @@ impl Dialect for PostgreSqlDialect {
191191
fn supports_explain_with_utility_options(&self) -> bool {
192192
true
193193
}
194+
195+
/// see <https://www.postgresql.org/docs/13/functions-math.html>
196+
fn supports_factorial_operator(&self) -> bool {
197+
true
198+
}
194199
}
195200

196201
pub fn parse_comment(parser: &mut Parser) -> Result<Statement, ParserError> {

src/parser/mod.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1175,6 +1175,14 @@ impl<'a> Parser<'a> {
11751175
),
11761176
})
11771177
}
1178+
Token::ExclamationMark if self.dialect.supports_bang_not_operator() => {
1179+
Ok(Expr::UnaryOp {
1180+
op: UnaryOperator::BangNot,
1181+
expr: Box::new(
1182+
self.parse_subexpr(self.dialect.prec_value(Precedence::UnaryNot))?,
1183+
),
1184+
})
1185+
}
11781186
tok @ Token::DoubleExclamationMark
11791187
| tok @ Token::PGSquareRoot
11801188
| tok @ Token::PGCubeRoot
@@ -1268,7 +1276,6 @@ impl<'a> Parser<'a> {
12681276
}
12691277
_ => self.expected("an expression", next_token),
12701278
}?;
1271-
12721279
if self.parse_keyword(Keyword::COLLATE) {
12731280
Ok(Expr::Collate {
12741281
expr: Box::new(expr),
@@ -2799,8 +2806,7 @@ impl<'a> Parser<'a> {
27992806
data_type: self.parse_data_type()?,
28002807
format: None,
28012808
})
2802-
} else if Token::ExclamationMark == tok {
2803-
// PostgreSQL factorial operation
2809+
} else if Token::ExclamationMark == tok && self.dialect.supports_factorial_operator() {
28042810
Ok(Expr::UnaryOp {
28052811
op: UnaryOperator::PGPostfixFactorial,
28062812
expr: Box::new(expr),

tests/sqlparser_common.rs

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11399,3 +11399,113 @@ fn test_show_dbs_schemas_tables_views() {
1139911399
verified_stmt("SHOW MATERIALIZED VIEWS FROM db1");
1140011400
verified_stmt("SHOW MATERIALIZED VIEWS FROM db1 'abc'");
1140111401
}
11402+
11403+
#[test]
11404+
fn parse_bang_not() {
11405+
let dialects = all_dialects_where(|d| d.supports_bang_not_operator());
11406+
let sql = "SELECT !a, !(b > 3)";
11407+
let Select { projection, .. } = dialects.verified_only_select(sql);
11408+
11409+
for (i, expr) in [
11410+
Box::new(Expr::Identifier(Ident::new("a"))),
11411+
Box::new(Expr::Nested(Box::new(Expr::BinaryOp {
11412+
left: Box::new(Expr::Identifier(Ident::new("b"))),
11413+
op: BinaryOperator::Gt,
11414+
right: Box::new(Expr::Value(Value::Number("3".parse().unwrap(), false))),
11415+
}))),
11416+
]
11417+
.into_iter()
11418+
.enumerate()
11419+
{
11420+
assert_eq!(
11421+
SelectItem::UnnamedExpr(Expr::UnaryOp {
11422+
op: UnaryOperator::BangNot,
11423+
expr
11424+
}),
11425+
projection[i]
11426+
)
11427+
}
11428+
11429+
let sql_statements = ["SELECT a!", "SELECT a ! b", "SELECT a ! as b"];
11430+
11431+
for &sql in &sql_statements {
11432+
assert_eq!(
11433+
dialects.parse_sql_statements(sql).unwrap_err(),
11434+
ParserError::ParserError("No infix parser for token ExclamationMark".to_string())
11435+
);
11436+
}
11437+
11438+
let sql_statements = ["SELECT !a", "SELECT !a b", "SELECT !a as b"];
11439+
let dialects = all_dialects_where(|d| !d.supports_bang_not_operator());
11440+
11441+
for &sql in &sql_statements {
11442+
assert_eq!(
11443+
dialects.parse_sql_statements(sql).unwrap_err(),
11444+
ParserError::ParserError("Expected: an expression, found: !".to_string())
11445+
);
11446+
}
11447+
}
11448+
11449+
#[test]
11450+
fn parse_factorial_operator() {
11451+
let dialects = all_dialects_where(|d| d.supports_factorial_operator());
11452+
let sql = "SELECT a!, (b + c)!";
11453+
let Select { projection, .. } = dialects.verified_only_select(sql);
11454+
11455+
for (i, expr) in [
11456+
Box::new(Expr::Identifier(Ident::new("a"))),
11457+
Box::new(Expr::Nested(Box::new(Expr::BinaryOp {
11458+
left: Box::new(Expr::Identifier(Ident::new("b"))),
11459+
op: BinaryOperator::Plus,
11460+
right: Box::new(Expr::Identifier(Ident::new("c"))),
11461+
}))),
11462+
]
11463+
.into_iter()
11464+
.enumerate()
11465+
{
11466+
assert_eq!(
11467+
SelectItem::UnnamedExpr(Expr::UnaryOp {
11468+
op: UnaryOperator::PGPostfixFactorial,
11469+
expr
11470+
}),
11471+
projection[i]
11472+
)
11473+
}
11474+
11475+
let sql_statements = ["SELECT !a", "SELECT !a b", "SELECT !a as b"];
11476+
11477+
for &sql in &sql_statements {
11478+
assert_eq!(
11479+
dialects.parse_sql_statements(sql).unwrap_err(),
11480+
ParserError::ParserError("Expected: an expression, found: !".to_string())
11481+
);
11482+
}
11483+
11484+
let sql_statements = ["SELECT a!", "SELECT a ! b", "SELECT a ! as b"];
11485+
11486+
// Due to the exclamation mark, which is both part of the `bang not` operator
11487+
// and the `factorial` operator, additional filtering not supports
11488+
// `bang not` operator is required here.
11489+
let dialects =
11490+
all_dialects_where(|d| !d.supports_factorial_operator() && !d.supports_bang_not_operator());
11491+
11492+
for &sql in &sql_statements {
11493+
assert_eq!(
11494+
dialects.parse_sql_statements(sql).unwrap_err(),
11495+
ParserError::ParserError("No infix parser for token ExclamationMark".to_string())
11496+
);
11497+
}
11498+
11499+
// Due to the exclamation mark, which is both part of the `bang not` operator
11500+
// and the `factorial` operator, additional filtering supports
11501+
// `bang not` operator is required here.
11502+
let dialects =
11503+
all_dialects_where(|d| !d.supports_factorial_operator() && d.supports_bang_not_operator());
11504+
11505+
for &sql in &sql_statements {
11506+
assert_eq!(
11507+
dialects.parse_sql_statements(sql).unwrap_err(),
11508+
ParserError::ParserError("No infix parser for token ExclamationMark".to_string())
11509+
);
11510+
}
11511+
}

0 commit comments

Comments
 (0)