hive: support for special not expression !a and raise error for a! factorial operator (#1472)

Co-authored-by: Ifeanyi Ubah <ify1992@yahoo.com>
This commit is contained in:
wugeer 2024-11-13 14:36:33 +08:00 committed by GitHub
parent 334a5bf354
commit e857787309
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 142 additions and 3 deletions

View file

@ -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 => "!",
})
}
}

View file

@ -51,4 +51,9 @@ impl Dialect for HiveDialect {
fn require_interval_qualifier(&self) -> bool {
true
}
/// See Hive <https://cwiki.apache.org/confluence/pages/viewpage.action?pageId=27362061#Tutorial-BuiltInOperators>
fn supports_bang_not_operator(&self) -> bool {
true
}
}

View file

@ -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

View file

@ -201,6 +201,11 @@ impl Dialect for PostgreSqlDialect {
fn supports_notify(&self) -> bool {
true
}
/// see <https://www.postgresql.org/docs/13/functions-math.html>
fn supports_factorial_operator(&self) -> bool {
true
}
}
pub fn parse_comment(parser: &mut Parser) -> Result<Statement, ParserError> {

View file

@ -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),

View file

@ -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())
);
}
}