mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-08-31 19:27:21 +00:00
Support for (+)
outer join syntax (#1145)
This commit is contained in:
parent
b284fdfb7e
commit
6a9b6f547d
3 changed files with 111 additions and 2 deletions
|
@ -713,6 +713,21 @@ pub enum Expr {
|
|||
/// Qualified wildcard, e.g. `alias.*` or `schema.table.*`.
|
||||
/// (Same caveats apply to `QualifiedWildcard` as to `Wildcard`.)
|
||||
QualifiedWildcard(ObjectName),
|
||||
/// Some dialects support an older syntax for outer joins where columns are
|
||||
/// marked with the `(+)` operator in the WHERE clause, for example:
|
||||
///
|
||||
/// ```sql
|
||||
/// SELECT t1.c1, t2.c2 FROM t1, t2 WHERE t1.c1 = t2.c2 (+)
|
||||
/// ```
|
||||
///
|
||||
/// which is equivalent to
|
||||
///
|
||||
/// ```sql
|
||||
/// SELECT t1.c1, t2.c2 FROM t1 LEFT OUTER JOIN t2 ON t1.c1 = t2.c2
|
||||
/// ```
|
||||
///
|
||||
/// See <https://docs.snowflake.com/en/sql-reference/constructs/where#joins-in-the-where-clause>.
|
||||
OuterJoin(Box<Expr>),
|
||||
}
|
||||
|
||||
impl fmt::Display for CastFormat {
|
||||
|
@ -1174,6 +1189,9 @@ impl fmt::Display for Expr {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
Expr::OuterJoin(expr) => {
|
||||
write!(f, "{expr} (+)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -998,8 +998,19 @@ impl<'a> Parser<'a> {
|
|||
if ends_with_wildcard {
|
||||
Ok(Expr::QualifiedWildcard(ObjectName(id_parts)))
|
||||
} else if self.consume_token(&Token::LParen) {
|
||||
self.prev_token();
|
||||
self.parse_function(ObjectName(id_parts))
|
||||
if dialect_of!(self is SnowflakeDialect | MsSqlDialect)
|
||||
&& self.consume_tokens(&[Token::Plus, Token::RParen])
|
||||
{
|
||||
Ok(Expr::OuterJoin(Box::new(
|
||||
match <[Ident; 1]>::try_from(id_parts) {
|
||||
Ok([ident]) => Expr::Identifier(ident),
|
||||
Err(parts) => Expr::CompoundIdentifier(parts),
|
||||
},
|
||||
)))
|
||||
} else {
|
||||
self.prev_token();
|
||||
self.parse_function(ObjectName(id_parts))
|
||||
}
|
||||
} else {
|
||||
Ok(Expr::CompoundIdentifier(id_parts))
|
||||
}
|
||||
|
@ -2860,6 +2871,21 @@ impl<'a> Parser<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
/// If the current and subsequent tokens exactly match the `tokens`
|
||||
/// sequence, consume them and returns true. Otherwise, no tokens are
|
||||
/// consumed and returns false
|
||||
#[must_use]
|
||||
pub fn consume_tokens(&mut self, tokens: &[Token]) -> bool {
|
||||
let index = self.index;
|
||||
for token in tokens {
|
||||
if !self.consume_token(token) {
|
||||
self.index = index;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// Bail out if the current token is not an expected keyword, or consume it if it is
|
||||
pub fn expect_token(&mut self, expected: &Token) -> Result<(), ParserError> {
|
||||
if self.consume_token(expected) {
|
||||
|
|
|
@ -1173,3 +1173,68 @@ fn parse_top() {
|
|||
"SELECT TOP 4 c1 FROM testtable",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_comma_outer_join() {
|
||||
// compound identifiers
|
||||
let case1 =
|
||||
snowflake().verified_only_select("SELECT t1.c1, t2.c2 FROM t1, t2 WHERE t1.c1 = t2.c2 (+)");
|
||||
assert_eq!(
|
||||
case1.selection,
|
||||
Some(Expr::BinaryOp {
|
||||
left: Box::new(Expr::CompoundIdentifier(vec![
|
||||
Ident::new("t1"),
|
||||
Ident::new("c1")
|
||||
])),
|
||||
op: BinaryOperator::Eq,
|
||||
right: Box::new(Expr::OuterJoin(Box::new(Expr::CompoundIdentifier(vec![
|
||||
Ident::new("t2"),
|
||||
Ident::new("c2")
|
||||
]))))
|
||||
})
|
||||
);
|
||||
|
||||
// regular identifiers
|
||||
let case2 =
|
||||
snowflake().verified_only_select("SELECT t1.c1, t2.c2 FROM t1, t2 WHERE c1 = c2 (+)");
|
||||
assert_eq!(
|
||||
case2.selection,
|
||||
Some(Expr::BinaryOp {
|
||||
left: Box::new(Expr::Identifier(Ident::new("c1"))),
|
||||
op: BinaryOperator::Eq,
|
||||
right: Box::new(Expr::OuterJoin(Box::new(Expr::Identifier(Ident::new(
|
||||
"c2"
|
||||
)))))
|
||||
})
|
||||
);
|
||||
|
||||
// ensure we can still parse function calls with a unary plus arg
|
||||
let case3 =
|
||||
snowflake().verified_only_select("SELECT t1.c1, t2.c2 FROM t1, t2 WHERE c1 = myudf(+42)");
|
||||
assert_eq!(
|
||||
case3.selection,
|
||||
Some(Expr::BinaryOp {
|
||||
left: Box::new(Expr::Identifier(Ident::new("c1"))),
|
||||
op: BinaryOperator::Eq,
|
||||
right: Box::new(Expr::Function(Function {
|
||||
name: ObjectName(vec![Ident::new("myudf")]),
|
||||
args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::UnaryOp {
|
||||
op: UnaryOperator::Plus,
|
||||
expr: Box::new(Expr::Value(number("42")))
|
||||
}))],
|
||||
filter: None,
|
||||
null_treatment: None,
|
||||
over: None,
|
||||
distinct: false,
|
||||
special: false,
|
||||
order_by: vec![]
|
||||
}))
|
||||
})
|
||||
);
|
||||
|
||||
// permissive with whitespace
|
||||
snowflake().verified_only_select_with_canonical(
|
||||
"SELECT t1.c1, t2.c2 FROM t1, t2 WHERE t1.c1 = t2.c2( + )",
|
||||
"SELECT t1.c1, t2.c2 FROM t1, t2 WHERE t1.c1 = t2.c2 (+)",
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue