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
4 changes: 3 additions & 1 deletion crates/polyglot-sql/src/optimizer/qualify_tables.rs
Original file line number Diff line number Diff line change
Expand Up @@ -868,9 +868,11 @@ mod tests {
let qualified = qualify_tables(expr, &options);
let sql = gen(&qualified);

// The parser keeps the set operation outside the derived table, so
// there is no wrapper left to normalize.
assert_eq!(
sql,
"SELECT * FROM (SELECT * FROM (SELECT * FROM tab_1 AS tab_1) AS _0 UNION ALL SELECT * FROM (SELECT * FROM tab_1 AS tab_1) AS _1) AS _2"
"SELECT * FROM (SELECT * FROM tab_1 AS tab_1) AS _0 UNION ALL SELECT * FROM (SELECT * FROM tab_1 AS tab_1) AS _1"
);
}
}
55 changes: 24 additions & 31 deletions crates/polyglot-sql/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4807,34 +4807,11 @@ impl Parser {
};
self.expect(TokenType::RParen)?;
let trailing = self.previous_trailing_comments().to_vec();
// 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 left = Expression::Subquery(Box::new(Subquery {
this: query,
alias: None,
column_aliases: Vec::new(),
alias_explicit_as: false,
alias_keyword: None,
order_by: None,
limit: None,
offset: None,
lateral: false,
modifiers_inside: false,
trailing_comments: Vec::new(),
distribute_by: None,
sort_by: None,
cluster_by: None,
inferred_type: None,
}));
self.parse_set_operation(left)?
} else {
query
};
// A set operation after the closing paren is not part of this
// FROM item: it belongs to the enclosing query, or, inside
// nested parens (`((SELECT 1) UNION (SELECT 2))`), to the
// nested-paren branch, which parses it itself.
let result = query;
Expression::Subquery(Box::new(Subquery {
this: result,
alias: None,
Expand Down Expand Up @@ -4917,10 +4894,26 @@ impl Parser {
|| self.check(TokenType::Intersect)
|| self.check(TokenType::Except);
let result = if had_set_operation {
// This is a set operation like ((SELECT 1) UNION (SELECT 2))
// Wrap inner in a subquery-like expression and parse set operation
// This is a set operation like ((SELECT 1) UNION (SELECT 2)):
// the parens around it make it one derived table.
let set_result = self.parse_set_operation(inner)?;
set_result
Expression::Subquery(Box::new(Subquery {
this: set_result,
alias: None,
column_aliases: Vec::new(),
alias_explicit_as: false,
alias_keyword: None,
order_by: None,
limit: None,
offset: None,
distribute_by: None,
sort_by: None,
cluster_by: None,
lateral: false,
modifiers_inside: false,
trailing_comments: Vec::new(),
inferred_type: None,
}))
} else if self.check(TokenType::Cross)
|| self.check(TokenType::Outer)
|| self.check(TokenType::Inner)
Expand Down
36 changes: 36 additions & 0 deletions crates/polyglot-sql/tests/unaliased_derived_table_set_operation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//! An unaliased derived table at the end of a set operation's first operand
//! belongs to that operand: the set operation after it is not part of it.

use polyglot_sql::{transpile, DialectType};

fn reprint(sql: &str, dialect: DialectType) -> String {
transpile(sql, dialect, dialect)
.unwrap_or_else(|e| panic!("failed to transpile {sql:?}: {e}"))
.remove(0)
}

#[test]
fn a_set_operation_after_an_unaliased_derived_table_stays_outside_it() {
for sql in [
"SELECT 1 AS s, * FROM (SELECT x FROM a) UNION ALL SELECT 2, x FROM b",
"SELECT 'p' AS side, * FROM (SELECT * FROM a LIMIT 1) UNION ALL SELECT 'l' AS side, * FROM (SELECT * FROM b LIMIT 1) LIMIT 2",
"SELECT * FROM c, (SELECT x FROM a LIMIT 1) UNION ALL SELECT x FROM b",
"SELECT y, * FROM (SELECT x FROM a) EXCEPT SELECT y, x FROM b",
] {
for dialect in [DialectType::Trino, DialectType::Generic] {
assert_eq!(reprint(sql, dialect), sql, "{dialect:?}");
}
}
}

#[test]
fn a_parenthesized_set_operation_keeps_its_own_modifiers_inside_its_parens() {
for sql in [
"SELECT * FROM ((SELECT 1) UNION (SELECT 2))",
"SELECT * FROM ((SELECT 1) UNION SELECT 2)",
"SELECT * FROM (((SELECT 1) UNION SELECT 2) ORDER BY x LIMIT 1 OFFSET 1)",
"SELECT * FROM ((SELECT 1) AS a UNION ALL (SELECT 2) AS b)",
] {
assert_eq!(reprint(sql, DialectType::Generic), sql);
}
}
Loading