-
Notifications
You must be signed in to change notification settings - Fork 2.4k
fix: avoid invalid qualifiers in unparsed subqueries #24808
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -54,7 +54,7 @@ use datafusion_expr::{ | |
| TableScan, Unnest, UserDefinedLogicalNode, Window, expr::Alias, | ||
| }; | ||
| use sqlparser::ast::{self, Ident, OrderByKind, SetExpr, TableAliasColumnDef}; | ||
| use std::{sync::Arc, vec}; | ||
| use std::{collections::HashSet, sync::Arc, vec}; | ||
|
|
||
| /// Convert a DataFusion [`LogicalPlan`] to [`ast::Statement`] | ||
| /// | ||
|
|
@@ -451,6 +451,24 @@ impl Unparser<'_> { | |
| } | ||
| } | ||
|
|
||
| /// Return the alias recursion would assign when `plan` must become a | ||
| /// derived relation below an already rendered projection. | ||
| fn derived_input_alias(plan: &LogicalPlan) -> Option<&'static str> { | ||
| match plan { | ||
| LogicalPlan::Projection(_) => Some("derived_projection"), | ||
| LogicalPlan::Limit(_) => Some("derived_limit"), | ||
| LogicalPlan::Sort(_) => Some("derived_sort"), | ||
| LogicalPlan::Distinct(_) => Some("derived_distinct"), | ||
| LogicalPlan::Filter(filter) => { | ||
| Self::derived_input_alias(filter.input.as_ref()) | ||
| } | ||
| LogicalPlan::Repartition(repartition) => { | ||
| Self::derived_input_alias(repartition.input.as_ref()) | ||
| } | ||
| _ => None, | ||
| } | ||
| } | ||
|
|
||
| fn contains_aggregate_before_relation(plan: &LogicalPlan) -> bool { | ||
| match plan { | ||
| LogicalPlan::Aggregate(_) => true, | ||
|
|
@@ -829,6 +847,61 @@ impl Unparser<'_> { | |
| columns, | ||
| ); | ||
| } | ||
|
|
||
| let qualified_projection = p.expr.iter().try_fold(false, |found, expr| { | ||
| if found { | ||
| Ok(true) | ||
| } else { | ||
| expr.exists(|expr| { | ||
| Ok(matches!(expr, Expr::Column(column) if column.relation.is_some())) | ||
| }) | ||
| } | ||
| })?; | ||
| let mut input_names = HashSet::new(); | ||
| let unique_input_names = p | ||
| .input | ||
| .schema() | ||
| .fields() | ||
| .iter() | ||
| .all(|field| input_names.insert(field.name())); | ||
| if let Some(input_alias) = Self::derived_input_alias(p.input.as_ref()) | ||
| && qualified_projection | ||
| && unique_input_names | ||
| && find_unnest_node_within_select(plan).is_none() | ||
| && !select.inside_subquery_alias() | ||
| { | ||
| // The input is about to enter a new SQL scope. Preserve that | ||
| // boundary explicitly and make the outer expressions resolve | ||
| // against the relation that will actually be visible there. | ||
| let requires_alias = self.dialect.requires_derived_table_alias(); | ||
| let alias = requires_alias | ||
| .then(|| self.new_table_alias(input_alias.to_string(), vec![])); | ||
| self.derive(p.input.as_ref(), relation, alias, false)?; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The new scope rewrite changes only the For example, The same problem applies to an outer filter rendered before this branch. Please rebase all outer clauses and add an |
||
|
|
||
| let items = p | ||
| .expr | ||
| .iter() | ||
| .cloned() | ||
| .map(|expr| { | ||
| if requires_alias { | ||
| let mut alias_rewriter = TableAliasRewriter { | ||
| table_schema: p.input.schema().as_ref(), | ||
| alias_name: TableReference::bare(input_alias), | ||
| rewrite_unqualified: false, | ||
| }; | ||
| expr.rewrite(&mut alias_rewriter).data() | ||
| } else { | ||
| Self::strip_column_qualifiers_for_schema( | ||
| expr, | ||
| p.input.schema().as_ref(), | ||
| ) | ||
| } | ||
| }) | ||
| .map(|expr| self.select_item_to_sql(&expr?)) | ||
| .collect::<Result<Vec<_>>>()?; | ||
| select.projection(items); | ||
| return Ok(()); | ||
| } | ||
| // For Snowflake FLATTEN: when the outer Projection has | ||
| // UNNEST(...) display-name columns (from SELECT * / SELECT | ||
| // UNNEST(...)), generate a flatten alias now so that | ||
|
|
@@ -1537,7 +1610,8 @@ impl Unparser<'_> { | |
| )]); | ||
| } | ||
| let plan = unparsed_table_scan.unwrap_or_else(|| plan.clone()); | ||
| if !columns.is_empty() | ||
| select.enter_subquery_alias(); | ||
| let recursive_result = if !columns.is_empty() | ||
| && !self.dialect.supports_column_alias_in_table_alias() | ||
| { | ||
| // Instead of specifying column aliases as part of the outer table, inject them directly into the inner projection | ||
|
|
@@ -1558,10 +1632,12 @@ impl Unparser<'_> { | |
| query, | ||
| select, | ||
| relation, | ||
| )?; | ||
| ) | ||
| } else { | ||
| self.select_to_sql_recursively(&plan, query, select, relation)?; | ||
| } | ||
| self.select_to_sql_recursively(&plan, query, select, relation) | ||
| }; | ||
| select.exit_subquery_alias(); | ||
| recursive_result?; | ||
|
|
||
| relation.alias(Some( | ||
| self.new_table_alias(plan_alias.alias.table().to_string(), columns), | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These methods are public because
astis a public module. Only the unparser uses this scope state.Please use
pub(super)for these three methods.