Snowflake: support nested join without parentheses (#1799)

This commit is contained in:
bar sela 2025-04-15 08:57:26 +03:00 committed by GitHub
parent 6566c47593
commit 514d2ecdaf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 440 additions and 2 deletions

View file

@ -11712,6 +11712,11 @@ impl<'a> Parser<'a> {
// Note that for keywords to be properly handled here, they need to be
// added to `RESERVED_FOR_TABLE_ALIAS`, otherwise they may be parsed as
// a table alias.
let joins = self.parse_joins()?;
Ok(TableWithJoins { relation, joins })
}
fn parse_joins(&mut self) -> Result<Vec<Join>, ParserError> {
let mut joins = vec![];
loop {
let global = self.parse_keyword(Keyword::GLOBAL);
@ -11844,7 +11849,16 @@ impl<'a> Parser<'a> {
}
_ => break,
};
let relation = self.parse_table_factor()?;
let mut relation = self.parse_table_factor()?;
if self.peek_parens_less_nested_join() {
let joins = self.parse_joins()?;
relation = TableFactor::NestedJoin {
table_with_joins: Box::new(TableWithJoins { relation, joins }),
alias: None,
};
}
let join_constraint = self.parse_join_constraint(natural)?;
Join {
relation,
@ -11854,7 +11868,21 @@ impl<'a> Parser<'a> {
};
joins.push(join);
}
Ok(TableWithJoins { relation, joins })
Ok(joins)
}
fn peek_parens_less_nested_join(&self) -> bool {
matches!(
self.peek_token_ref().token,
Token::Word(Word {
keyword: Keyword::JOIN
| Keyword::INNER
| Keyword::LEFT
| Keyword::RIGHT
| Keyword::FULL,
..
})
)
}
/// A table name or a parenthesized subquery, followed by optional `[AS] alias`

View file

@ -3573,3 +3573,413 @@ fn test_alter_session_followed_by_statement() {
_ => panic!("Unexpected statements: {:?}", stmts),
}
}
#[test]
fn test_nested_join_without_parentheses() {
let query = "SELECT DISTINCT p.product_id FROM orders AS o INNER JOIN customers AS c INNER JOIN products AS p ON p.customer_id = c.customer_id ON c.order_id = o.order_id";
assert_eq!(
only(
snowflake()
.verified_only_select_with_canonical(query, "SELECT DISTINCT p.product_id FROM orders AS o INNER JOIN (customers AS c INNER JOIN products AS p ON p.customer_id = c.customer_id) ON c.order_id = o.order_id")
.from
)
.joins,
vec![Join {
relation: TableFactor::NestedJoin {
table_with_joins: Box::new(TableWithJoins {
relation: TableFactor::Table {
name: ObjectName::from(vec![Ident::new("customers".to_string())]),
alias: Some(TableAlias {
name: Ident {
value: "c".to_string(),
quote_style: None,
span: Span::empty(),
},
columns: vec![],
}),
args: None,
with_hints: vec![],
version: None,
partitions: vec![],
with_ordinality: false,
json_path: None,
sample: None,
index_hints: vec![],
},
joins: vec![Join {
relation: TableFactor::Table {
name: ObjectName::from(vec![Ident::new("products".to_string())]),
alias: Some(TableAlias {
name: Ident {
value: "p".to_string(),
quote_style: None,
span: Span::empty(),
},
columns: vec![],
}),
args: None,
with_hints: vec![],
version: None,
partitions: vec![],
with_ordinality: false,
json_path: None,
sample: None,
index_hints: vec![],
},
global: false,
join_operator: JoinOperator::Inner(JoinConstraint::On(Expr::BinaryOp {
left: Box::new(Expr::CompoundIdentifier(vec![
Ident::new("p".to_string()),
Ident::new("customer_id".to_string())
])),
op: BinaryOperator::Eq,
right: Box::new(Expr::CompoundIdentifier(vec![
Ident::new("c".to_string()),
Ident::new("customer_id".to_string())
])),
})),
}]
}),
alias: None
},
global: false,
join_operator: JoinOperator::Inner(JoinConstraint::On(Expr::BinaryOp {
left: Box::new(Expr::CompoundIdentifier(vec![
Ident::new("c".to_string()),
Ident::new("order_id".to_string())
])),
op: BinaryOperator::Eq,
right: Box::new(Expr::CompoundIdentifier(vec![
Ident::new("o".to_string()),
Ident::new("order_id".to_string())
])),
}))
}],
);
let query = "SELECT DISTINCT p.product_id FROM orders AS o JOIN customers AS c JOIN products AS p ON p.customer_id = c.customer_id ON c.order_id = o.order_id";
assert_eq!(
only(
snowflake()
.verified_only_select_with_canonical(query, "SELECT DISTINCT p.product_id FROM orders AS o JOIN (customers AS c JOIN products AS p ON p.customer_id = c.customer_id) ON c.order_id = o.order_id")
.from
)
.joins,
vec![Join {
relation: TableFactor::NestedJoin {
table_with_joins: Box::new(TableWithJoins {
relation: TableFactor::Table {
name: ObjectName::from(vec![Ident::new("customers".to_string())]),
alias: Some(TableAlias {
name: Ident {
value: "c".to_string(),
quote_style: None,
span: Span::empty(),
},
columns: vec![],
}),
args: None,
with_hints: vec![],
version: None,
partitions: vec![],
with_ordinality: false,
json_path: None,
sample: None,
index_hints: vec![],
},
joins: vec![Join {
relation: TableFactor::Table {
name: ObjectName::from(vec![Ident::new("products".to_string())]),
alias: Some(TableAlias {
name: Ident {
value: "p".to_string(),
quote_style: None,
span: Span::empty(),
},
columns: vec![],
}),
args: None,
with_hints: vec![],
version: None,
partitions: vec![],
with_ordinality: false,
json_path: None,
sample: None,
index_hints: vec![],
},
global: false,
join_operator: JoinOperator::Join(JoinConstraint::On(Expr::BinaryOp {
left: Box::new(Expr::CompoundIdentifier(vec![
Ident::new("p".to_string()),
Ident::new("customer_id".to_string())
])),
op: BinaryOperator::Eq,
right: Box::new(Expr::CompoundIdentifier(vec![
Ident::new("c".to_string()),
Ident::new("customer_id".to_string())
])),
})),
}]
}),
alias: None
},
global: false,
join_operator: JoinOperator::Join(JoinConstraint::On(Expr::BinaryOp {
left: Box::new(Expr::CompoundIdentifier(vec![
Ident::new("c".to_string()),
Ident::new("order_id".to_string())
])),
op: BinaryOperator::Eq,
right: Box::new(Expr::CompoundIdentifier(vec![
Ident::new("o".to_string()),
Ident::new("order_id".to_string())
])),
}))
}],
);
let query = "SELECT DISTINCT p.product_id FROM orders AS o LEFT JOIN customers AS c LEFT JOIN products AS p ON p.customer_id = c.customer_id ON c.order_id = o.order_id";
assert_eq!(
only(
snowflake()
.verified_only_select_with_canonical(query, "SELECT DISTINCT p.product_id FROM orders AS o LEFT JOIN (customers AS c LEFT JOIN products AS p ON p.customer_id = c.customer_id) ON c.order_id = o.order_id")
.from
)
.joins,
vec![Join {
relation: TableFactor::NestedJoin {
table_with_joins: Box::new(TableWithJoins {
relation: TableFactor::Table {
name: ObjectName::from(vec![Ident::new("customers".to_string())]),
alias: Some(TableAlias {
name: Ident {
value: "c".to_string(),
quote_style: None,
span: Span::empty(),
},
columns: vec![],
}),
args: None,
with_hints: vec![],
version: None,
partitions: vec![],
with_ordinality: false,
json_path: None,
sample: None,
index_hints: vec![],
},
joins: vec![Join {
relation: TableFactor::Table {
name: ObjectName::from(vec![Ident::new("products".to_string())]),
alias: Some(TableAlias {
name: Ident {
value: "p".to_string(),
quote_style: None,
span: Span::empty(),
},
columns: vec![],
}),
args: None,
with_hints: vec![],
version: None,
partitions: vec![],
with_ordinality: false,
json_path: None,
sample: None,
index_hints: vec![],
},
global: false,
join_operator: JoinOperator::Left(JoinConstraint::On(Expr::BinaryOp {
left: Box::new(Expr::CompoundIdentifier(vec![
Ident::new("p".to_string()),
Ident::new("customer_id".to_string())
])),
op: BinaryOperator::Eq,
right: Box::new(Expr::CompoundIdentifier(vec![
Ident::new("c".to_string()),
Ident::new("customer_id".to_string())
])),
})),
}]
}),
alias: None
},
global: false,
join_operator: JoinOperator::Left(JoinConstraint::On(Expr::BinaryOp {
left: Box::new(Expr::CompoundIdentifier(vec![
Ident::new("c".to_string()),
Ident::new("order_id".to_string())
])),
op: BinaryOperator::Eq,
right: Box::new(Expr::CompoundIdentifier(vec![
Ident::new("o".to_string()),
Ident::new("order_id".to_string())
])),
}))
}],
);
let query = "SELECT DISTINCT p.product_id FROM orders AS o RIGHT JOIN customers AS c RIGHT JOIN products AS p ON p.customer_id = c.customer_id ON c.order_id = o.order_id";
assert_eq!(
only(
snowflake()
.verified_only_select_with_canonical(query, "SELECT DISTINCT p.product_id FROM orders AS o RIGHT JOIN (customers AS c RIGHT JOIN products AS p ON p.customer_id = c.customer_id) ON c.order_id = o.order_id")
.from
)
.joins,
vec![Join {
relation: TableFactor::NestedJoin {
table_with_joins: Box::new(TableWithJoins {
relation: TableFactor::Table {
name: ObjectName::from(vec![Ident::new("customers".to_string())]),
alias: Some(TableAlias {
name: Ident {
value: "c".to_string(),
quote_style: None,
span: Span::empty(),
},
columns: vec![],
}),
args: None,
with_hints: vec![],
version: None,
partitions: vec![],
with_ordinality: false,
json_path: None,
sample: None,
index_hints: vec![],
},
joins: vec![Join {
relation: TableFactor::Table {
name: ObjectName::from(vec![Ident::new("products".to_string())]),
alias: Some(TableAlias {
name: Ident {
value: "p".to_string(),
quote_style: None,
span: Span::empty(),
},
columns: vec![],
}),
args: None,
with_hints: vec![],
version: None,
partitions: vec![],
with_ordinality: false,
json_path: None,
sample: None,
index_hints: vec![],
},
global: false,
join_operator: JoinOperator::Right(JoinConstraint::On(Expr::BinaryOp {
left: Box::new(Expr::CompoundIdentifier(vec![
Ident::new("p".to_string()),
Ident::new("customer_id".to_string())
])),
op: BinaryOperator::Eq,
right: Box::new(Expr::CompoundIdentifier(vec![
Ident::new("c".to_string()),
Ident::new("customer_id".to_string())
])),
})),
}]
}),
alias: None
},
global: false,
join_operator: JoinOperator::Right(JoinConstraint::On(Expr::BinaryOp {
left: Box::new(Expr::CompoundIdentifier(vec![
Ident::new("c".to_string()),
Ident::new("order_id".to_string())
])),
op: BinaryOperator::Eq,
right: Box::new(Expr::CompoundIdentifier(vec![
Ident::new("o".to_string()),
Ident::new("order_id".to_string())
])),
}))
}],
);
let query = "SELECT DISTINCT p.product_id FROM orders AS o FULL JOIN customers AS c FULL JOIN products AS p ON p.customer_id = c.customer_id ON c.order_id = o.order_id";
assert_eq!(
only(
snowflake()
.verified_only_select_with_canonical(query, "SELECT DISTINCT p.product_id FROM orders AS o FULL JOIN (customers AS c FULL JOIN products AS p ON p.customer_id = c.customer_id) ON c.order_id = o.order_id")
.from
)
.joins,
vec![Join {
relation: TableFactor::NestedJoin {
table_with_joins: Box::new(TableWithJoins {
relation: TableFactor::Table {
name: ObjectName::from(vec![Ident::new("customers".to_string())]),
alias: Some(TableAlias {
name: Ident {
value: "c".to_string(),
quote_style: None,
span: Span::empty(),
},
columns: vec![],
}),
args: None,
with_hints: vec![],
version: None,
partitions: vec![],
with_ordinality: false,
json_path: None,
sample: None,
index_hints: vec![],
},
joins: vec![Join {
relation: TableFactor::Table {
name: ObjectName::from(vec![Ident::new("products".to_string())]),
alias: Some(TableAlias {
name: Ident {
value: "p".to_string(),
quote_style: None,
span: Span::empty(),
},
columns: vec![],
}),
args: None,
with_hints: vec![],
version: None,
partitions: vec![],
with_ordinality: false,
json_path: None,
sample: None,
index_hints: vec![],
},
global: false,
join_operator: JoinOperator::FullOuter(JoinConstraint::On(
Expr::BinaryOp {
left: Box::new(Expr::CompoundIdentifier(vec![
Ident::new("p".to_string()),
Ident::new("customer_id".to_string())
])),
op: BinaryOperator::Eq,
right: Box::new(Expr::CompoundIdentifier(vec![
Ident::new("c".to_string()),
Ident::new("customer_id".to_string())
])),
}
)),
}]
}),
alias: None
},
global: false,
join_operator: JoinOperator::FullOuter(JoinConstraint::On(Expr::BinaryOp {
left: Box::new(Expr::CompoundIdentifier(vec![
Ident::new("c".to_string()),
Ident::new("order_id".to_string())
])),
op: BinaryOperator::Eq,
right: Box::new(Expr::CompoundIdentifier(vec![
Ident::new("o".to_string()),
Ident::new("order_id".to_string())
])),
}))
}],
);
}