Skip to content

Commit e857787

Browse files
wugeeriffyio
andauthored
hive: support for special not expression !a and raise error for a! factorial operator (#1472)
Co-authored-by: Ifeanyi Ubah <ify1992@yahoo.com>
1 parent 334a5bf commit e857787

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:
@@ -591,6 +596,11 @@ pub trait Dialect: Debug + Any {
591596
false
592597
}
593598

599+
/// Returns true if the dialect supports `!a` syntax for boolean `NOT` expressions.
600+
fn supports_bang_not_operator(&self) -> bool {
601+
false
602+
}
603+
594604
/// Returns true if the dialect supports the `LISTEN` statement
595605
fn supports_listen(&self) -> bool {
596606
false

src/dialect/postgresql.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,11 @@ impl Dialect for PostgreSqlDialect {
201201
fn supports_notify(&self) -> bool {
202202
true
203203
}
204+
205+
/// see <https://www.postgresql.org/docs/13/functions-math.html>
206+
fn supports_factorial_operator(&self) -> bool {
207+
true
208+
}
204209
}
205210

206211
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
@@ -1194,6 +1194,14 @@ impl<'a> Parser<'a> {
11941194
),
11951195
})
11961196
}
1197+
Token::ExclamationMark if self.dialect.supports_bang_not_operator() => {
1198+
Ok(Expr::UnaryOp {
1199+
op: UnaryOperator::BangNot,
1200+
expr: Box::new(
1201+
self.parse_subexpr(self.dialect.prec_value(Precedence::UnaryNot))?,
1202+
),
1203+
})
1204+
}
11971205
tok @ Token::DoubleExclamationMark
11981206
| tok @ Token::PGSquareRoot
11991207
| tok @ Token::PGCubeRoot
@@ -1287,7 +1295,6 @@ impl<'a> Parser<'a> {
12871295
}
12881296
_ => self.expected("an expression", next_token),
12891297
}?;
1290-
12911298
if self.parse_keyword(Keyword::COLLATE) {
12921299
Ok(Expr::Collate {
12931300
expr: Box::new(expr),
@@ -2818,8 +2825,7 @@ impl<'a> Parser<'a> {
28182825
data_type: self.parse_data_type()?,
28192826
format: None,
28202827
})
2821-
} else if Token::ExclamationMark == tok {
2822-
// PostgreSQL factorial operation
2828+
} else if Token::ExclamationMark == tok && self.dialect.supports_factorial_operator() {
28232829
Ok(Expr::UnaryOp {
28242830
op: UnaryOperator::PGPostfixFactorial,
28252831
expr: Box::new(expr),

tests/sqlparser_common.rs

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11532,3 +11532,113 @@ fn test_select_top() {
1153211532
dialects.verified_stmt("SELECT TOP 3 DISTINCT * FROM tbl");
1153311533
dialects.verified_stmt("SELECT TOP 3 DISTINCT a, b, c FROM tbl");
1153411534
}
11535+
11536+
#[test]
11537+
fn parse_bang_not() {
11538+
let dialects = all_dialects_where(|d| d.supports_bang_not_operator());
11539+
let sql = "SELECT !a, !(b > 3)";
11540+
let Select { projection, .. } = dialects.verified_only_select(sql);
11541+
11542+
for (i, expr) in [
11543+
Box::new(Expr::Identifier(Ident::new("a"))),
11544+
Box::new(Expr::Nested(Box::new(Expr::BinaryOp {
11545+
left: Box::new(Expr::Identifier(Ident::new("b"))),
11546+
op: BinaryOperator::Gt,
11547+
right: Box::new(Expr::Value(Value::Number("3".parse().unwrap(), false))),
11548+
}))),
11549+
]
11550+
.into_iter()
11551+
.enumerate()
11552+
{
11553+
assert_eq!(
11554+
SelectItem::UnnamedExpr(Expr::UnaryOp {
11555+
op: UnaryOperator::BangNot,
11556+
expr
11557+
}),
11558+
projection[i]
11559+
)
11560+
}
11561+
11562+
let sql_statements = ["SELECT a!", "SELECT a ! b", "SELECT a ! as b"];
11563+
11564+
for &sql in &sql_statements {
11565+
assert_eq!(
11566+
dialects.parse_sql_statements(sql).unwrap_err(),
11567+
ParserError::ParserError("No infix parser for token ExclamationMark".to_string())
11568+
);
11569+
}
11570+
11571+
let sql_statements = ["SELECT !a", "SELECT !a b", "SELECT !a as b"];
11572+
let dialects = all_dialects_where(|d| !d.supports_bang_not_operator());
11573+
11574+
for &sql in &sql_statements {
11575+
assert_eq!(
11576+
dialects.parse_sql_statements(sql).unwrap_err(),
11577+
ParserError::ParserError("Expected: an expression, found: !".to_string())
11578+
);
11579+
}
11580+
}
11581+
11582+
#[test]
11583+
fn parse_factorial_operator() {
11584+
let dialects = all_dialects_where(|d| d.supports_factorial_operator());
11585+
let sql = "SELECT a!, (b + c)!";
11586+
let Select { projection, .. } = dialects.verified_only_select(sql);
11587+
11588+
for (i, expr) in [
11589+
Box::new(Expr::Identifier(Ident::new("a"))),
11590+
Box::new(Expr::Nested(Box::new(Expr::BinaryOp {
11591+
left: Box::new(Expr::Identifier(Ident::new("b"))),
11592+
op: BinaryOperator::Plus,
11593+
right: Box::new(Expr::Identifier(Ident::new("c"))),
11594+
}))),
11595+
]
11596+
.into_iter()
11597+
.enumerate()
11598+
{
11599+
assert_eq!(
11600+
SelectItem::UnnamedExpr(Expr::UnaryOp {
11601+
op: UnaryOperator::PGPostfixFactorial,
11602+
expr
11603+
}),
11604+
projection[i]
11605+
)
11606+
}
11607+
11608+
let sql_statements = ["SELECT !a", "SELECT !a b", "SELECT !a as b"];
11609+
11610+
for &sql in &sql_statements {
11611+
assert_eq!(
11612+
dialects.parse_sql_statements(sql).unwrap_err(),
11613+
ParserError::ParserError("Expected: an expression, found: !".to_string())
11614+
);
11615+
}
11616+
11617+
let sql_statements = ["SELECT a!", "SELECT a ! b", "SELECT a ! as b"];
11618+
11619+
// Due to the exclamation mark, which is both part of the `bang not` operator
11620+
// and the `factorial` operator, additional filtering not supports
11621+
// `bang not` operator is required here.
11622+
let dialects =
11623+
all_dialects_where(|d| !d.supports_factorial_operator() && !d.supports_bang_not_operator());
11624+
11625+
for &sql in &sql_statements {
11626+
assert_eq!(
11627+
dialects.parse_sql_statements(sql).unwrap_err(),
11628+
ParserError::ParserError("No infix parser for token ExclamationMark".to_string())
11629+
);
11630+
}
11631+
11632+
// Due to the exclamation mark, which is both part of the `bang not` operator
11633+
// and the `factorial` operator, additional filtering supports
11634+
// `bang not` operator is required here.
11635+
let dialects =
11636+
all_dialects_where(|d| !d.supports_factorial_operator() && d.supports_bang_not_operator());
11637+
11638+
for &sql in &sql_statements {
11639+
assert_eq!(
11640+
dialects.parse_sql_statements(sql).unwrap_err(),
11641+
ParserError::ParserError("No infix parser for token ExclamationMark".to_string())
11642+
);
11643+
}
11644+
}

0 commit comments

Comments
 (0)