diff --git a/datafusion/sql/src/set_expr.rs b/datafusion/sql/src/set_expr.rs index d4e771cb48585..cd8e94a11ae5e 100644 --- a/datafusion/sql/src/set_expr.rs +++ b/datafusion/sql/src/set_expr.rs @@ -20,7 +20,9 @@ use datafusion_common::{ DataFusionError, Diagnostic, Result, Span, not_impl_err, plan_err, }; use datafusion_expr::{LogicalPlan, LogicalPlanBuilder}; -use sqlparser::ast::{SetExpr, SetOperator, SetQuantifier, Spanned}; +use sqlparser::ast::{ + Ident, SetExpr, SetOperator, SetQuantifier, Select, SelectItem, Spanned, +}; impl SqlToRel<'_, S> { #[cfg_attr(feature = "recursive_protection", recursive::recursive)] @@ -42,7 +44,17 @@ impl SqlToRel<'_, S> { let left_span = Span::try_from_sqlparser_span(left.span()); let right_span = Span::try_from_sqlparser_span(right.span()); let left_plan = self.set_expr_to_plan(*left, planner_context); - let right_plan = self.set_expr_to_plan(*right, planner_context); + + let right_expr = *right; + let right_plan = self.set_expr_to_plan(right_expr.clone(), planner_context); + let right_plan = match (right_plan, set_quantifier) { + (Err(err), SetQuantifier::ByName | SetQuantifier::AllByName) => Err(err), + (Err(err), _) if is_projection_unique_name_err(&err) => { + self.set_expr_to_plan(alias_set_operand_projection(right_expr), planner_context) + } + (result, _) => result, + }; + let (left_plan, right_plan) = match (left_plan, right_plan) { (Ok(left_plan), Ok(right_plan)) => (left_plan, right_plan), (Err(left_err), Err(right_err)) => { @@ -160,3 +172,42 @@ impl SqlToRel<'_, S> { } } } + +fn is_projection_unique_name_err(err: &DataFusionError) -> bool { + err.to_string() + .contains("Projections require unique expression names") +} + +fn alias_set_operand_projection(set_expr: SetExpr) -> SetExpr { + match set_expr { + SetExpr::Select(select) => SetExpr::Select(Box::new(alias_select_projection(*select))), + SetExpr::SetOperation { + op, + left, + right, + set_quantifier, + } => SetExpr::SetOperation { + op, + left, + right: Box::new(alias_set_operand_projection(*right)), + set_quantifier, + }, + other => other, + } +} + +fn alias_select_projection(mut select: Select) -> Select { + select.projection = select + .projection + .into_iter() + .enumerate() + .map(|(idx, item)| match item { + SelectItem::UnnamedExpr(expr) => SelectItem::ExprWithAlias { + expr, + alias: Ident::new(format!("__set_col_{}", idx + 1)), + }, + other => other, + }) + .collect(); + select +} diff --git a/datafusion/sql/tests/sql_integration.rs b/datafusion/sql/tests/sql_integration.rs index 29c17be69ce5f..def4d0591e2b3 100644 --- a/datafusion/sql/tests/sql_integration.rs +++ b/datafusion/sql/tests/sql_integration.rs @@ -2618,6 +2618,22 @@ fn union_all() { ); } +#[test] +fn union_all_right_side_duplicate_literal_names() { + let sql = "SELECT 1 c1, 0 c2, 0 c3 UNION ALL SELECT 2, 0, 0"; + let plan = logical_plan(sql).unwrap(); + assert_snapshot!( + plan, + @r" + Union + Projection: Int64(1) AS c1, Int64(0) AS c2, Int64(0) AS c3 + EmptyRelation + Projection: Int64(2) AS __set_col_1, Int64(0) AS __set_col_2, Int64(0) AS __set_col_3 + EmptyRelation + " + ); +} + #[test] fn union_all_by_name_different_columns() { let sql =