Conversation
A sub-query projection that renames a column to the name of a different
column of the same input made `push_down_leaf_projections` fail when a
struct field is read above it:
Schema error: Schema contains qualified field name t.a and
unqualified field name a which would be ambiguous
The merge of the extraction projection into the projection below it
resolved every column the parent needs through the projection's rename
map, then added the input column that the name resolves to. For
`select t.a as b, t.b as a, s from t`, the parent's `b` resolves to
`t.a`, and `t.a` lands beside the output field `a`.
The merge keeps every expression of the projection below it, so the
parent can still read each name that projection produces. Skip those
names instead of resolving them, and never add a pass-through column
whose name is one of the projection's output names.
The recovery check then needs the same care, or the shape gives wrong
results instead of an error. Equal sets of field names do not prove that
the plan below carries the same values. That hunk is the same change as
apache#25445, so the two merge
without a conflict.
Three plan snapshots lose a duplicate pass-through column in the
intermediate stage. The optimized plans do not change.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Add the statements from the report as sqllogictest cases: a rename beside a same-name alias under a filter, a limit, an order by and a group by, a swap of two column names, and a swap where the struct field name is also a column name. Add two optimizer unit tests: one for the ambiguous schema, one for the recovery projection that must keep a rename alive. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #25478 +/- ##
========================================
Coverage 82.35% 82.35%
========================================
Files 1137 1137
Lines 432746 432936 +190
Branches 432746 432936 +190
========================================
+ Hits 356375 356539 +164
- Misses 54843 54854 +11
- Partials 21528 21543 +15 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Which issue does this PR close?
push_down_leaf_projectionsfails with an ambiguous schema when a sub-query projection renames columns #25446.Part of the leaf-pushdown EPIC: #25459
Rationale for this change
A valid statement fails to plan. A sub-query projection renames a column to the name of a different column of the same input, and the query reads a struct field above it.
datafusion.optimizer.enable_leaf_expression_pushdownistrueby default, so this is a plain planning failure.Observed on
main, for all three:Expected, and what you get with
set datafusion.optimizer.enable_leaf_expression_pushdown = false;:order byandgroup byabove the rename fail the same way. Query generators emit this shape.What changes are included in this PR?
Both changes are in
datafusion/optimizer/src/extract_leaf_expressions.rs.1. The merge of the extraction projection.
build_extraction_projection_implmerges the extraction projection into the projection below it. For every column the parent needs, it resolved the name through the projection's rename map and then added the input column that the name resolves to. Forselect t.a as b, t.b as a, s from tthe parent'sbresolves tot.a, andt.alands beside the output fieldaof the other rename.Projection::try_newrejects that schema.The merge keeps every expression of the projection below it, so the parent can still read each name that projection produces. Those names are now skipped instead of resolved, and a pass-through column whose name is one of the projection's output names is never added. A rename is a computed output, and the parent refers to it by its output name only.
2. The recovery check. With the merge fixed, the shape reaches
split_and_push_projection, which decided if it needs a recovery projection from the set of unqualified field names alone. Equal name sets do not prove equal values: the pushed plan exposes the table columnt.awhere the projection computedt.a AS b. Without this hunk the statements above return the columns swapped instead of an error. The recovery projection now also stays when a recovery expression is not a pass-through of a column.That second hunk is the same change as #25445, down to the text, so the two merge without a conflict. If that PR lands first, the hunk falls out of the rebase.
How this composes with the other open PRs on this function. #25412 resolves both sides of the pass-through comparison against the input schema, and #25456 replaces the rename map with
ProjectionInliner. This change sits above both: it decides which columns to add before either one resolves a name. I merged this branch into a local branch that holds both PRs. The single conflict is the loop header, which takes the guards of this PR around the body of the other two. The optimizer tests, thestruct,cseandpush_down_filtersqllogictest files, and the differential fuzz all pass there with the swap-alias shape of the fuzz generator turned on: 250 short cases, 125 short Parquet cases, 5000 extended cases and 1000 extended Parquet cases, 0 failures and 0 skips.What is the testing strategy for this PR?
datafusion/sqllogictest/test_files/struct.slt: the three statements above, plus theorder byandgroup byvariants, plus a swap where the struct field name is also a column name of the table, plus oneEXPLAINthat shows the renames above the extraction projection and the struct field still read at the scan.datafusion/optimizer/src/extract_leaf_expressions.rs:test_extract_above_projection_that_swaps_column_namescovers the ambiguous schema.test_extract_above_projection_that_redefines_column_namecovers the recovery projection that must keep a rename alive.Commands and results:
Are there any user-facing changes?
Statements of this shape plan and run. There is no API change.
🤖 Generated with Claude Code