You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@datafusion.apache.org by go...@apache.org on 2025/04/23 15:54:35 UTC
(datafusion) branch main updated: Support unparsing `UNION` for distinct results (#15814)
This is an automated email from the ASF dual-hosted git repository.
goldmedal pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/datafusion.git
The following commit(s) were added to refs/heads/main by this push:
new 07a310fea8 Support unparsing `UNION` for distinct results (#15814)
07a310fea8 is described below
commit 07a310fea8d287805d1490e9dc3c1b7b1c2775d8
Author: Phillip LeBlanc <ph...@leblanc.tech>
AuthorDate: Thu Apr 24 00:54:30 2025 +0900
Support unparsing `UNION` for distinct results (#15814)
---
datafusion/sql/src/unparser/ast.rs | 10 ++++++++++
datafusion/sql/src/unparser/plan.rs | 28 +++++++++++++++++++++++++++-
datafusion/sql/tests/cases/plan_to_sql.rs | 7 +++++++
3 files changed, 44 insertions(+), 1 deletion(-)
diff --git a/datafusion/sql/src/unparser/ast.rs b/datafusion/sql/src/unparser/ast.rs
index b902b88845..117971af76 100644
--- a/datafusion/sql/src/unparser/ast.rs
+++ b/datafusion/sql/src/unparser/ast.rs
@@ -32,6 +32,8 @@ pub struct QueryBuilder {
fetch: Option<ast::Fetch>,
locks: Vec<ast::LockClause>,
for_clause: Option<ast::ForClause>,
+ // If true, we need to unparse LogicalPlan::Union as a SQL `UNION` rather than a `UNION ALL`.
+ distinct_union: bool,
}
#[allow(dead_code)]
@@ -75,6 +77,13 @@ impl QueryBuilder {
self.for_clause = value;
self
}
+ pub fn distinct_union(&mut self) -> &mut Self {
+ self.distinct_union = true;
+ self
+ }
+ pub fn is_distinct_union(&self) -> bool {
+ self.distinct_union
+ }
pub fn build(&self) -> Result<ast::Query, BuilderError> {
let order_by = self
.order_by_kind
@@ -112,6 +121,7 @@ impl QueryBuilder {
fetch: Default::default(),
locks: Default::default(),
for_clause: Default::default(),
+ distinct_union: false,
}
}
}
diff --git a/datafusion/sql/src/unparser/plan.rs b/datafusion/sql/src/unparser/plan.rs
index 77edef4f86..b849ca45d2 100644
--- a/datafusion/sql/src/unparser/plan.rs
+++ b/datafusion/sql/src/unparser/plan.rs
@@ -545,6 +545,23 @@ impl Unparser<'_> {
false,
);
}
+
+ // If this distinct is the parent of a Union and we're in a query context,
+ // then we need to unparse as a `UNION` rather than a `UNION ALL`.
+ if let Distinct::All(input) = distinct {
+ if matches!(input.as_ref(), LogicalPlan::Union(_)) {
+ if let Some(query_mut) = query.as_mut() {
+ query_mut.distinct_union();
+ return self.select_to_sql_recursively(
+ input.as_ref(),
+ query,
+ select,
+ relation,
+ );
+ }
+ }
+ }
+
let (select_distinct, input) = match distinct {
Distinct::All(input) => (ast::Distinct::Distinct, input.as_ref()),
Distinct::On(on) => {
@@ -829,6 +846,15 @@ impl Unparser<'_> {
return internal_err!("UNION operator requires at least 2 inputs");
}
+ let set_quantifier =
+ if query.as_ref().is_some_and(|q| q.is_distinct_union()) {
+ // Setting the SetQuantifier to None will unparse as a `UNION`
+ // rather than a `UNION ALL`.
+ ast::SetQuantifier::None
+ } else {
+ ast::SetQuantifier::All
+ };
+
// Build the union expression tree bottom-up by reversing the order
// note that we are also swapping left and right inputs because of the rev
let union_expr = input_exprs
@@ -836,7 +862,7 @@ impl Unparser<'_> {
.rev()
.reduce(|a, b| SetExpr::SetOperation {
op: ast::SetOperator::Union,
- set_quantifier: ast::SetQuantifier::All,
+ set_quantifier,
left: Box::new(b),
right: Box::new(a),
})
diff --git a/datafusion/sql/tests/cases/plan_to_sql.rs b/datafusion/sql/tests/cases/plan_to_sql.rs
index 17ed10d08c..a458d72d28 100644
--- a/datafusion/sql/tests/cases/plan_to_sql.rs
+++ b/datafusion/sql/tests/cases/plan_to_sql.rs
@@ -170,6 +170,13 @@ fn roundtrip_statement() -> Result<()> {
UNION ALL
SELECT j3_string AS col1, j3_id AS id FROM j3
) AS subquery GROUP BY col1, id ORDER BY col1 ASC, id ASC"#,
+ r#"SELECT col1, id FROM (
+ SELECT j1_string AS col1, j1_id AS id FROM j1
+ UNION
+ SELECT j2_string AS col1, j2_id AS id FROM j2
+ UNION
+ SELECT j3_string AS col1, j3_id AS id FROM j3
+ ) AS subquery ORDER BY col1 ASC, id ASC"#,
"SELECT id, count(*) over (PARTITION BY first_name ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING),
last_name, sum(id) over (PARTITION BY first_name ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING),
first_name from person",
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@datafusion.apache.org
For additional commands, e-mail: commits-help@datafusion.apache.org