Conversation
`split_and_push_projection` decided if it needs a recovery projection from the set of unqualified field names of the pushed plan. A name says nothing about the value behind it. A projection such as `(- t.a) AS a, t.s, get_field(t.s, "b") AS __datafusion_extracted_1` keeps every name when the extraction goes below it, but the pushed plan exposes the table column `t.a` where the projection computed `- t.a`. The recovery projection went away and the computed column became its own input column. The recovery projection now also stays when a recovery expression computes a value, that is, when it is not a pass-through of a column. The name comparison stays for the leaked-column case that it was written for. Closes apache#25414 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Six SQL shapes go in `struct.slt`: through a Filter, through a Limit, a computed column with a different type, an outer filter on the computed column, a group key, and through a Sort. Five of them gave wrong results or an internal error before the fix. The Sort shape was already correct and guards it. One EXPLAIN shows that the projection that computes `-a` stays in the plan. The unit test builds the plan shape that loses the computed column and runs the two leaf rules alone, in their production order. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #25445 +/- ##
==========================================
- Coverage 82.33% 82.33% -0.01%
==========================================
Files 1137 1137
Lines 432498 432525 +27
Branches 432498 432525 +27
==========================================
+ Hits 356115 356124 +9
- Misses 54844 54854 +10
- Partials 21539 21547 +8 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
This was referenced Sep 18, 2026
adriangb
added a commit
to pydantic/datafusion
that referenced
this pull request
Sep 18, 2026
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>
adriangb
added a commit
to pydantic/datafusion
that referenced
this pull request
Sep 18, 2026
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>
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?
Rationale for this change
A subquery that computes a column under the same name as its input column gives wrong results. This happens with the default configuration.
mainselect a, s['b'] from (select -a as a, s from t where a > 0)1 x/2 y-1 x/-2 yselect a, s['b'] from (select -a as a, s from t limit 10)1 x/2 y-1 x/-2 yselect a, s['b'] from (select a * 10 as a, s from t limit 10)Internal error10 x/20 yselect a, s['b'] from (select -a as a, s from t) where a < 01 x/2 y-1 x/-2 yselect a, count(s['b']) from (select -a as a, s from t where a > 0) group by a1and2-1and-2The fourth query returns rows that fail its own filter. The third query stops the plan with this message:
The results are correct with
set datafusion.optimizer.enable_leaf_expression_pushdown = false;.The cause is in
split_and_push_projectionindatafusion/optimizer/src/extract_leaf_expressions.rs. The rule pushes the extraction ofs['b']below the projection that computes-a AS a. It then decides if it must keep a recovery projection, and it decides from the set of unqualified field names of the pushed plan.A name says nothing about the value behind it. For this projection:
the pushed plan holds the names
a,sand__datafusion_extracted_1again, but itsais the table columnt.a, not- t.a. The two sets are equal, the rule drops the recovery projection, and the computed column becomes its own input column:The same comparison ignores data types. That is why
a * 10 AS afails the optimizer schema check instead of giving wrong data.What changes are included in this PR?
The recovery projection now also stays when a recovery expression computes a value, that is, when it is not a pass-through of a column.
passthrough_columngives that answer, and it already accepts a requalification such ast.a AS a.The name comparison stays as it is. It catches the leaked-column case that it was written for, which the expression check does not see. The comparison still ignores qualifiers, so the
SubqueryAliasrequalification behaviour does not change.The change is inside
split_and_push_projectiononly. #25412 touchesbuild_extraction_projection_implin the same file. The two changes are independent and fix different bugs. The only overlap is that both PRs append a block to the end ofdatafusion/sqllogictest/test_files/struct.slt, so the second one to merge needs a trivial rebase there.What is the testing strategy for this PR?
Six SQL shapes go in
datafusion/sqllogictest/test_files/struct.slt: through aFilter, through aLimit, a computed column with a different data type, an outer filter on the computed column, a group key, and through aSort. OneEXPLAINshows that the projection that computes-astays in the plan.A counterfactual run confirms each one. With the fix reverted, the new block gives 6 errors: the
EXPLAIN, four wrong results and the internal error. TheSortshape was already correct and guards it.A unit test,
test_recovery_kept_for_same_name_computed_columninextract_leaf_expressions.rs, builds the plan shape that loses the computed column and runs the two leaf rules alone, in their production order.All 883
datafusion-optimizertests pass. No existing insta snapshot changes. All 520 sqllogictest files pass.Are there any user-facing changes?
Queries of this shape now give correct results. There are no API changes.
Part of the leaf-pushdown EPIC: #25459
🤖 Generated with Claude Code