Skip to content

fix: avoid invalid qualifiers in unparsed subqueries - #24808

Open
blinding-pixels wants to merge 1 commit into
apache:mainfrom
blinding-pixels:agent/fix-unparser-subquery-scope
Open

fix: avoid invalid qualifiers in unparsed subqueries#24808
blinding-pixels wants to merge 1 commit into
apache:mainfrom
blinding-pixels:agent/fix-unparser-subquery-scope

Conversation

@blinding-pixels

Copy link
Copy Markdown
Contributor

Which issue does this PR close?

Rationale for this change

I noticed that this issue had an earlier PR from almost two years ago. That implementation was closed because it used broad identifier cleanup, and maintainers were concerned it could introduce subtle bugs.

Since then, DataFusion has added much of the machinery needed for the approaches recommended in that review. This solution uses that newer machinery and combines both approaches. It removes the qualifier when the derived table does not need an alias, and rewrites the reference to the derived alias when the dialect requires one.

What changes are included in this PR?

This PR detects when a projection enters a new derived-table scope and prevents its outer expressions from referring to an inner table alias that is no longer visible.

For dialects that do not require a derived-table alias, the invalid inner qualifier is removed. For dialects such as MySQL that require an alias, the outer reference is rewritten to the generated derived-table alias. Explicitly named subquery scopes remain unchanged.

What is the testing strategy for this PR?

The SQL unparser round-trip tests cover the issue's original query with both the generic and MySQL dialects. They also cover filtered and distinct derived inputs, and update existing limit and nested-projection cases to assert valid outer references.

The required formatting, Clippy, and extended workspace test suite all pass, including all 505 SQL logic test files.

Are there any user-facing changes?

Yes. SQL produced by the unparser no longer contains qualifiers that refer to tables outside their visible scope. Dialects that require derived-table aliases now qualify the outer reference with the generated alias. There are no public API changes.

@github-actions github-actions Bot added the sql SQL Planner label Aug 31, 2026
@codecov-commenter

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 93.33333% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 81.52%. Comparing base (e4cf35c) to head (78fd46f).

Files with missing lines Patch % Lines
datafusion/sql/src/unparser/plan.rs 91.17% 2 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #24808      +/-   ##
==========================================
- Coverage   81.52%   81.52%   -0.01%     
==========================================
  Files        1123     1123              
  Lines      406148   406193      +45     
  Branches   406148   406193      +45     
==========================================
+ Hits       331124   331159      +35     
- Misses      55659    55665       +6     
- Partials    19365    19369       +4     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@kumarUjjawal kumarUjjawal left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @blinding-pixels for working on this. I have left few comments for your consideration.

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)?;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new scope rewrite changes only the SELECT items. An outer Sort is rendered before this branch and keeps the old qualifier.

For example, SELECT j1_id FROM (...) ORDER BY j1_id can still emit ORDER BY ta.j1_id. Only derived_projection is visible there.

The same problem applies to an outer filter rendered before this branch. Please rebase all outer clauses and add an ORDER BY regression.

self.flatten_table_aliases.iter().any(|a| a == alias)
}

pub fn enter_subquery_alias(&mut self) {

Copy link
Copy Markdown
Contributor

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 ast is a public module. Only the unparser uses this scope state.

Please use pub(super) for these three methods.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

sql SQL Planner

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Table references in subqueries cause invalid references in top-level projection

3 participants