mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-09-22 13:42:31 +00:00
Add parsing for PostgreSQL math operators (#267)
This commit is contained in:
parent
2f71324c33
commit
926b03a31d
6 changed files with 171 additions and 6 deletions
|
@ -282,7 +282,13 @@ impl fmt::Display for Expr {
|
||||||
high
|
high
|
||||||
),
|
),
|
||||||
Expr::BinaryOp { left, op, right } => write!(f, "{} {} {}", left, op, right),
|
Expr::BinaryOp { left, op, right } => write!(f, "{} {} {}", left, op, right),
|
||||||
Expr::UnaryOp { op, expr } => write!(f, "{} {}", op, expr),
|
Expr::UnaryOp { op, expr } => {
|
||||||
|
if op == &UnaryOperator::PGPostfixFactorial {
|
||||||
|
write!(f, "{}{}", expr, op)
|
||||||
|
} else {
|
||||||
|
write!(f, "{} {}", op, expr)
|
||||||
|
}
|
||||||
|
}
|
||||||
Expr::Cast { expr, data_type } => write!(f, "CAST({} AS {})", expr, data_type),
|
Expr::Cast { expr, data_type } => write!(f, "CAST({} AS {})", expr, data_type),
|
||||||
Expr::Extract { field, expr } => write!(f, "EXTRACT({} FROM {})", field, expr),
|
Expr::Extract { field, expr } => write!(f, "EXTRACT({} FROM {})", field, expr),
|
||||||
Expr::Collate { expr, collation } => write!(f, "{} COLLATE {}", expr, collation),
|
Expr::Collate { expr, collation } => write!(f, "{} COLLATE {}", expr, collation),
|
||||||
|
|
|
@ -21,6 +21,18 @@ pub enum UnaryOperator {
|
||||||
Plus,
|
Plus,
|
||||||
Minus,
|
Minus,
|
||||||
Not,
|
Not,
|
||||||
|
/// Bitwise Not, e.g. `~9` (PostgreSQL-specific)
|
||||||
|
PGBitwiseNot,
|
||||||
|
/// Square root, e.g. `|/9` (PostgreSQL-specific)
|
||||||
|
PGSquareRoot,
|
||||||
|
/// Cube root, e.g. `||/27` (PostgreSQL-specific)
|
||||||
|
PGCubeRoot,
|
||||||
|
/// Factorial, e.g. `9!` (PostgreSQL-specific)
|
||||||
|
PGPostfixFactorial,
|
||||||
|
/// Factorial, e.g. `!!9` (PostgreSQL-specific)
|
||||||
|
PGPrefixFactorial,
|
||||||
|
/// Absolute value, e.g. `@ -9` (PostgreSQL-specific)
|
||||||
|
PGAbs,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for UnaryOperator {
|
impl fmt::Display for UnaryOperator {
|
||||||
|
@ -29,6 +41,12 @@ impl fmt::Display for UnaryOperator {
|
||||||
UnaryOperator::Plus => "+",
|
UnaryOperator::Plus => "+",
|
||||||
UnaryOperator::Minus => "-",
|
UnaryOperator::Minus => "-",
|
||||||
UnaryOperator::Not => "NOT",
|
UnaryOperator::Not => "NOT",
|
||||||
|
UnaryOperator::PGBitwiseNot => "~",
|
||||||
|
UnaryOperator::PGSquareRoot => "|/",
|
||||||
|
UnaryOperator::PGCubeRoot => "||/",
|
||||||
|
UnaryOperator::PGPostfixFactorial => "!",
|
||||||
|
UnaryOperator::PGPrefixFactorial => "!!",
|
||||||
|
UnaryOperator::PGAbs => "@",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -56,6 +74,9 @@ pub enum BinaryOperator {
|
||||||
BitwiseOr,
|
BitwiseOr,
|
||||||
BitwiseAnd,
|
BitwiseAnd,
|
||||||
BitwiseXor,
|
BitwiseXor,
|
||||||
|
PGBitwiseXor,
|
||||||
|
PGBitwiseShiftLeft,
|
||||||
|
PGBitwiseShiftRight,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for BinaryOperator {
|
impl fmt::Display for BinaryOperator {
|
||||||
|
@ -80,6 +101,9 @@ impl fmt::Display for BinaryOperator {
|
||||||
BinaryOperator::BitwiseOr => "|",
|
BinaryOperator::BitwiseOr => "|",
|
||||||
BinaryOperator::BitwiseAnd => "&",
|
BinaryOperator::BitwiseAnd => "&",
|
||||||
BinaryOperator::BitwiseXor => "^",
|
BinaryOperator::BitwiseXor => "^",
|
||||||
|
BinaryOperator::PGBitwiseXor => "#",
|
||||||
|
BinaryOperator::PGBitwiseShiftLeft => "<<",
|
||||||
|
BinaryOperator::PGBitwiseShiftRight => ">>",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -294,6 +294,26 @@ impl<'a> Parser<'a> {
|
||||||
expr: Box::new(self.parse_subexpr(Self::PLUS_MINUS_PREC)?),
|
expr: Box::new(self.parse_subexpr(Self::PLUS_MINUS_PREC)?),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
tok @ Token::DoubleExclamationMark
|
||||||
|
| tok @ Token::PGSquareRoot
|
||||||
|
| tok @ Token::PGCubeRoot
|
||||||
|
| tok @ Token::AtSign
|
||||||
|
| tok @ Token::Tilde
|
||||||
|
if dialect_of!(self is PostgreSqlDialect) =>
|
||||||
|
{
|
||||||
|
let op = match tok {
|
||||||
|
Token::DoubleExclamationMark => UnaryOperator::PGPrefixFactorial,
|
||||||
|
Token::PGSquareRoot => UnaryOperator::PGSquareRoot,
|
||||||
|
Token::PGCubeRoot => UnaryOperator::PGCubeRoot,
|
||||||
|
Token::AtSign => UnaryOperator::PGAbs,
|
||||||
|
Token::Tilde => UnaryOperator::PGBitwiseNot,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
Ok(Expr::UnaryOp {
|
||||||
|
op,
|
||||||
|
expr: Box::new(self.parse_subexpr(Self::PLUS_MINUS_PREC)?),
|
||||||
|
})
|
||||||
|
}
|
||||||
Token::Number(_)
|
Token::Number(_)
|
||||||
| Token::SingleQuotedString(_)
|
| Token::SingleQuotedString(_)
|
||||||
| Token::NationalStringLiteral(_)
|
| Token::NationalStringLiteral(_)
|
||||||
|
@ -658,6 +678,15 @@ impl<'a> Parser<'a> {
|
||||||
Token::Caret => Some(BinaryOperator::BitwiseXor),
|
Token::Caret => Some(BinaryOperator::BitwiseXor),
|
||||||
Token::Ampersand => Some(BinaryOperator::BitwiseAnd),
|
Token::Ampersand => Some(BinaryOperator::BitwiseAnd),
|
||||||
Token::Div => Some(BinaryOperator::Divide),
|
Token::Div => Some(BinaryOperator::Divide),
|
||||||
|
Token::ShiftLeft if dialect_of!(self is PostgreSqlDialect) => {
|
||||||
|
Some(BinaryOperator::PGBitwiseShiftLeft)
|
||||||
|
}
|
||||||
|
Token::ShiftRight if dialect_of!(self is PostgreSqlDialect) => {
|
||||||
|
Some(BinaryOperator::PGBitwiseShiftRight)
|
||||||
|
}
|
||||||
|
Token::Sharp if dialect_of!(self is PostgreSqlDialect) => {
|
||||||
|
Some(BinaryOperator::PGBitwiseXor)
|
||||||
|
}
|
||||||
Token::Word(w) => match w.keyword {
|
Token::Word(w) => match w.keyword {
|
||||||
Keyword::AND => Some(BinaryOperator::And),
|
Keyword::AND => Some(BinaryOperator::And),
|
||||||
Keyword::OR => Some(BinaryOperator::Or),
|
Keyword::OR => Some(BinaryOperator::Or),
|
||||||
|
@ -707,6 +736,12 @@ impl<'a> Parser<'a> {
|
||||||
}
|
}
|
||||||
} else if Token::DoubleColon == tok {
|
} else if Token::DoubleColon == tok {
|
||||||
self.parse_pg_cast(expr)
|
self.parse_pg_cast(expr)
|
||||||
|
} else if Token::ExclamationMark == tok {
|
||||||
|
// PostgreSQL factorial operation
|
||||||
|
Ok(Expr::UnaryOp {
|
||||||
|
op: UnaryOperator::PGPostfixFactorial,
|
||||||
|
expr: Box::new(expr),
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
// Can only happen if `get_next_precedence` got out of sync with this function
|
// Can only happen if `get_next_precedence` got out of sync with this function
|
||||||
panic!("No infix parser for token {:?}", tok)
|
panic!("No infix parser for token {:?}", tok)
|
||||||
|
@ -785,11 +820,12 @@ impl<'a> Parser<'a> {
|
||||||
Token::Word(w) if w.keyword == Keyword::LIKE => Ok(Self::BETWEEN_PREC),
|
Token::Word(w) if w.keyword == Keyword::LIKE => Ok(Self::BETWEEN_PREC),
|
||||||
Token::Eq | Token::Lt | Token::LtEq | Token::Neq | Token::Gt | Token::GtEq => Ok(20),
|
Token::Eq | Token::Lt | Token::LtEq | Token::Neq | Token::Gt | Token::GtEq => Ok(20),
|
||||||
Token::Pipe => Ok(21),
|
Token::Pipe => Ok(21),
|
||||||
Token::Caret => Ok(22),
|
Token::Caret | Token::Sharp | Token::ShiftRight | Token::ShiftLeft => Ok(22),
|
||||||
Token::Ampersand => Ok(23),
|
Token::Ampersand => Ok(23),
|
||||||
Token::Plus | Token::Minus => Ok(Self::PLUS_MINUS_PREC),
|
Token::Plus | Token::Minus => Ok(Self::PLUS_MINUS_PREC),
|
||||||
Token::Mult | Token::Div | Token::Mod | Token::StringConcat => Ok(40),
|
Token::Mult | Token::Div | Token::Mod | Token::StringConcat => Ok(40),
|
||||||
Token::DoubleColon => Ok(50),
|
Token::DoubleColon => Ok(50),
|
||||||
|
Token::ExclamationMark => Ok(50),
|
||||||
_ => Ok(0),
|
_ => Ok(0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,7 +54,7 @@ pub enum Token {
|
||||||
Neq,
|
Neq,
|
||||||
/// Less Than operator `<`
|
/// Less Than operator `<`
|
||||||
Lt,
|
Lt,
|
||||||
/// Greater han operator `>`
|
/// Greater Than operator `>`
|
||||||
Gt,
|
Gt,
|
||||||
/// Less Than Or Equals operator `<=`
|
/// Less Than Or Equals operator `<=`
|
||||||
LtEq,
|
LtEq,
|
||||||
|
@ -102,6 +102,24 @@ pub enum Token {
|
||||||
RBrace,
|
RBrace,
|
||||||
/// Right Arrow `=>`
|
/// Right Arrow `=>`
|
||||||
RArrow,
|
RArrow,
|
||||||
|
/// Sharp `#` used for PostgreSQL Bitwise XOR operator
|
||||||
|
Sharp,
|
||||||
|
/// Tilde `~` used for PostgreSQL Bitwise NOT operator
|
||||||
|
Tilde,
|
||||||
|
/// `<<`, a bitwise shift left operator in PostgreSQL
|
||||||
|
ShiftLeft,
|
||||||
|
/// `>>`, a bitwise shift right operator in PostgreSQL
|
||||||
|
ShiftRight,
|
||||||
|
/// Exclamation Mark `!` used for PostgreSQL factorial operator
|
||||||
|
ExclamationMark,
|
||||||
|
/// Double Exclamation Mark `!!` used for PostgreSQL prefix factorial operator
|
||||||
|
DoubleExclamationMark,
|
||||||
|
/// AtSign `@` used for PostgreSQL abs operator
|
||||||
|
AtSign,
|
||||||
|
/// `|/`, a square root math operator in PostgreSQL
|
||||||
|
PGSquareRoot,
|
||||||
|
/// `||/` , a cube root math operator in PostgreSQL
|
||||||
|
PGCubeRoot,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Token {
|
impl fmt::Display for Token {
|
||||||
|
@ -143,6 +161,15 @@ impl fmt::Display for Token {
|
||||||
Token::LBrace => f.write_str("{"),
|
Token::LBrace => f.write_str("{"),
|
||||||
Token::RBrace => f.write_str("}"),
|
Token::RBrace => f.write_str("}"),
|
||||||
Token::RArrow => f.write_str("=>"),
|
Token::RArrow => f.write_str("=>"),
|
||||||
|
Token::Sharp => f.write_str("#"),
|
||||||
|
Token::ExclamationMark => f.write_str("!"),
|
||||||
|
Token::DoubleExclamationMark => f.write_str("!!"),
|
||||||
|
Token::Tilde => f.write_str("~"),
|
||||||
|
Token::AtSign => f.write_str("@"),
|
||||||
|
Token::ShiftLeft => f.write_str("<<"),
|
||||||
|
Token::ShiftRight => f.write_str(">>"),
|
||||||
|
Token::PGSquareRoot => f.write_str("|/"),
|
||||||
|
Token::PGCubeRoot => f.write_str("||/"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -406,7 +433,14 @@ impl<'a> Tokenizer<'a> {
|
||||||
'|' => {
|
'|' => {
|
||||||
chars.next(); // consume the '|'
|
chars.next(); // consume the '|'
|
||||||
match chars.peek() {
|
match chars.peek() {
|
||||||
Some('|') => self.consume_and_return(chars, Token::StringConcat),
|
Some('/') => self.consume_and_return(chars, Token::PGSquareRoot),
|
||||||
|
Some('|') => {
|
||||||
|
chars.next(); // consume the second '|'
|
||||||
|
match chars.peek() {
|
||||||
|
Some('/') => self.consume_and_return(chars, Token::PGCubeRoot),
|
||||||
|
_ => Ok(Some(Token::StringConcat)),
|
||||||
|
}
|
||||||
|
}
|
||||||
// Bitshift '|' operator
|
// Bitshift '|' operator
|
||||||
_ => Ok(Some(Token::Pipe)),
|
_ => Ok(Some(Token::Pipe)),
|
||||||
}
|
}
|
||||||
|
@ -423,7 +457,8 @@ impl<'a> Tokenizer<'a> {
|
||||||
chars.next(); // consume
|
chars.next(); // consume
|
||||||
match chars.peek() {
|
match chars.peek() {
|
||||||
Some('=') => self.consume_and_return(chars, Token::Neq),
|
Some('=') => self.consume_and_return(chars, Token::Neq),
|
||||||
_ => self.tokenizer_error("Expected to see '=' after '!' character"),
|
Some('!') => self.consume_and_return(chars, Token::DoubleExclamationMark),
|
||||||
|
_ => Ok(Some(Token::ExclamationMark)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
'<' => {
|
'<' => {
|
||||||
|
@ -431,6 +466,7 @@ impl<'a> Tokenizer<'a> {
|
||||||
match chars.peek() {
|
match chars.peek() {
|
||||||
Some('=') => self.consume_and_return(chars, Token::LtEq),
|
Some('=') => self.consume_and_return(chars, Token::LtEq),
|
||||||
Some('>') => self.consume_and_return(chars, Token::Neq),
|
Some('>') => self.consume_and_return(chars, Token::Neq),
|
||||||
|
Some('<') => self.consume_and_return(chars, Token::ShiftLeft),
|
||||||
_ => Ok(Some(Token::Lt)),
|
_ => Ok(Some(Token::Lt)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -438,6 +474,7 @@ impl<'a> Tokenizer<'a> {
|
||||||
chars.next(); // consume
|
chars.next(); // consume
|
||||||
match chars.peek() {
|
match chars.peek() {
|
||||||
Some('=') => self.consume_and_return(chars, Token::GtEq),
|
Some('=') => self.consume_and_return(chars, Token::GtEq),
|
||||||
|
Some('>') => self.consume_and_return(chars, Token::ShiftRight),
|
||||||
_ => Ok(Some(Token::Gt)),
|
_ => Ok(Some(Token::Gt)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -464,6 +501,9 @@ impl<'a> Tokenizer<'a> {
|
||||||
comment,
|
comment,
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
|
'~' => self.consume_and_return(chars, Token::Tilde),
|
||||||
|
'#' => self.consume_and_return(chars, Token::Sharp),
|
||||||
|
'@' => self.consume_and_return(chars, Token::AtSign),
|
||||||
other => self.consume_and_return(chars, Token::Char(other)),
|
other => self.consume_and_return(chars, Token::Char(other)),
|
||||||
},
|
},
|
||||||
None => Ok(None),
|
None => Ok(None),
|
||||||
|
|
|
@ -343,7 +343,7 @@ fn parse_select_count_distinct() {
|
||||||
name: ObjectName(vec![Ident::new("COUNT")]),
|
name: ObjectName(vec![Ident::new("COUNT")]),
|
||||||
args: vec![FunctionArg::Unnamed(Expr::UnaryOp {
|
args: vec![FunctionArg::Unnamed(Expr::UnaryOp {
|
||||||
op: UnaryOperator::Plus,
|
op: UnaryOperator::Plus,
|
||||||
expr: Box::new(Expr::Identifier(Ident::new("x")))
|
expr: Box::new(Expr::Identifier(Ident::new("x"))),
|
||||||
})],
|
})],
|
||||||
over: None,
|
over: None,
|
||||||
distinct: true,
|
distinct: true,
|
||||||
|
|
|
@ -551,6 +551,65 @@ fn parse_prepare() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_pg_bitwise_binary_ops() {
|
||||||
|
let bitwise_ops = &[
|
||||||
|
("#", BinaryOperator::PGBitwiseXor),
|
||||||
|
(">>", BinaryOperator::PGBitwiseShiftRight),
|
||||||
|
("<<", BinaryOperator::PGBitwiseShiftLeft),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (str_op, op) in bitwise_ops {
|
||||||
|
let select = pg().verified_only_select(&format!("SELECT a {} b", &str_op));
|
||||||
|
assert_eq!(
|
||||||
|
SelectItem::UnnamedExpr(Expr::BinaryOp {
|
||||||
|
left: Box::new(Expr::Identifier(Ident::new("a"))),
|
||||||
|
op: op.clone(),
|
||||||
|
right: Box::new(Expr::Identifier(Ident::new("b"))),
|
||||||
|
}),
|
||||||
|
select.projection[0]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_pg_unary_ops() {
|
||||||
|
let pg_unary_ops = &[
|
||||||
|
("~", UnaryOperator::PGBitwiseNot),
|
||||||
|
("|/", UnaryOperator::PGSquareRoot),
|
||||||
|
("||/", UnaryOperator::PGCubeRoot),
|
||||||
|
("!!", UnaryOperator::PGPrefixFactorial),
|
||||||
|
("@", UnaryOperator::PGAbs),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (str_op, op) in pg_unary_ops {
|
||||||
|
let select = pg().verified_only_select(&format!("SELECT {} a", &str_op));
|
||||||
|
assert_eq!(
|
||||||
|
SelectItem::UnnamedExpr(Expr::UnaryOp {
|
||||||
|
op: op.clone(),
|
||||||
|
expr: Box::new(Expr::Identifier(Ident::new("a"))),
|
||||||
|
}),
|
||||||
|
select.projection[0]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_pg_postfix_factorial() {
|
||||||
|
let postfix_factorial = &[("!", UnaryOperator::PGPostfixFactorial)];
|
||||||
|
|
||||||
|
for (str_op, op) in postfix_factorial {
|
||||||
|
let select = pg().verified_only_select(&format!("SELECT a{}", &str_op));
|
||||||
|
assert_eq!(
|
||||||
|
SelectItem::UnnamedExpr(Expr::UnaryOp {
|
||||||
|
op: op.clone(),
|
||||||
|
expr: Box::new(Expr::Identifier(Ident::new("a"))),
|
||||||
|
}),
|
||||||
|
select.projection[0]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn pg() -> TestedDialects {
|
fn pg() -> TestedDialects {
|
||||||
TestedDialects {
|
TestedDialects {
|
||||||
dialects: vec![Box::new(PostgreSqlDialect {})],
|
dialects: vec![Box::new(PostgreSqlDialect {})],
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue