Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/polyglot-sql/src/optimizer/qualify_tables.rs
Original file line number Diff line number Diff line change
Expand Up @@ -974,7 +974,7 @@ mod tests {
fn test_can_disable_set_operation_wrapper_normalization() {
let options = QualifyTablesOptions::new().with_normalize_set_operation_subqueries(false);
let expr = parse(
"SELECT * FROM (SELECT * FROM tab_1) UNION ALL SELECT * FROM (SELECT * FROM tab_1)",
"SELECT * FROM (SELECT * FROM (SELECT * FROM tab_1) UNION ALL SELECT * FROM (SELECT * FROM tab_1))",
);
let qualified = qualify_tables(expr, &options);
let sql = gen(&qualified);
Expand Down
12 changes: 9 additions & 3 deletions crates/polyglot-sql/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4681,6 +4681,11 @@ impl Parser {
}))
}
} else if self.match_token(TokenType::LParen) {
// A parenthesized query continues into a set operation only where it opens an
// enclosing parenthesis, as in `FROM ((SELECT 1) UNION (SELECT 2))`. In
// `FROM (SELECT ...) UNION ALL SELECT ...` the set operation combines the outer query.
let set_operation_may_follow =
self.current < 2 || self.tokens[self.current - 2].token_type == TokenType::LParen;
// Subquery or parenthesized set operation or (VALUES ...)
if self.check(TokenType::Values) {
// (VALUES (...), (...)) AS t(c1, c2) or (VALUES (0) foo(bar))
Expand Down Expand Up @@ -4735,9 +4740,10 @@ impl Parser {
// Check for set operations after parenthesized query
// If there's a set operation, wrap query in Subquery first to preserve parens
// e.g., (SELECT 1) UNION (SELECT 2) - the left operand needs Subquery wrapper
let result = if self.check(TokenType::Union)
|| self.check(TokenType::Intersect)
|| self.check(TokenType::Except)
let result = if set_operation_may_follow
&& (self.check(TokenType::Union)
|| self.check(TokenType::Intersect)
|| self.check(TokenType::Except))
{
let left = Expression::Subquery(Box::new(Subquery {
this: query,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,34 @@ fn set_operation_after_subquery_operand_applies_to_enclosing_query() {
}
}

#[test]
fn set_operation_after_from_subquery_applies_to_enclosing_query() {
let cases = [
"SELECT * FROM (SELECT o.id FROM orders AS o) UNION ALL SELECT 1",
"SELECT * FROM (SELECT o.id FROM orders AS o) UNION SELECT 1",
"SELECT * FROM (SELECT o.id FROM orders AS o) EXCEPT SELECT 1",
"SELECT * FROM (SELECT o.id FROM orders AS o) INTERSECT SELECT 1",
"SELECT * FROM (SELECT o.id FROM orders AS o) AS t UNION ALL SELECT 1",
"SELECT * FROM (WITH c AS (SELECT 1 AS id) SELECT c.id FROM c) UNION ALL SELECT 1",
"SELECT * FROM (SELECT o.id FROM orders AS o) UNION ALL SELECT * FROM (SELECT 1 AS id)",
];
for dialect in DIALECTS {
for sql in cases {
let parsed = parse_stable(sql, dialect);
let (left, right) = set_operation_operands(&parsed);
assert!(
matches!(left, Expression::Select(_)) && matches!(right, Expression::Select(_)),
"{dialect:?}: {sql}: {parsed:?}"
);
assert_eq!(
set_operation_count(&parsed),
1,
"{dialect:?}: set operation absorbed by a FROM subquery: {sql}"
);
}
}
}

#[test]
fn set_operation_inside_enclosing_parentheses_stays_nested() {
let cases = [
Expand Down