Repository navigation
fix: resolve pass-through columns against the input when merging an extraction projection - #25412
Conversation
|
cc @pepijnve since you're working on these same files |
There was a problem hiding this comment.
🟡 Changes recommended
Same-name pass-through aliases remain excluded from deduplication and can recreate the ambiguous schema.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
Fixes projection merging to resolve pass-through columns against the input schema.
Changes:
- Normalizes column qualifiers before deduplication.
- Adds an SQL regression test for the planning failure.
File summaries
| File | Description |
|---|---|
extract_leaf_expressions.rs |
Resolves merged pass-through columns against input schemas. |
struct.slt |
Tests the previously failing union query. |
Review details
- Files reviewed: 2/2 changed files
- Comments generated: 1
- Review effort level: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #25412 +/- ##
==========================================
- Coverage 82.84% 82.84% -0.01%
==========================================
Files 1149 1149
Lines 453357 453424 +67
Branches 453357 453424 +67
==========================================
+ Hits 375597 375650 +53
- Misses 54916 54921 +5
- Partials 22844 22853 +9 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
0bc684b to
61923e2
Compare
adriangb
left a comment
There was a problem hiding this comment.
Summary
The fix is correct and small. I verified all claims in the description. I have 3 non-blocking suggestions (inline).
Root cause
flowchart TD
A["Filter / Aggregate above<br/>needs: <code>env</code> (bare) or <code>samples.env</code>"] --> M{{"build_extraction_projection_impl<br/>merge into existing Projection"}}
P["Existing Projection: <code>v, s, env</code> (bare)<br/>input: <code>samples.v, samples.s, samples.env</code>"] --> M
M -->|main: compare raw Column values| X["<code>samples.env</code> != <code>env</code><br/>→ push again<br/>→ schema has <code>samples.env</code> AND <code>env</code><br/>❌ ambiguous"]
M -->|PR: resolve both sides against input| O["both → <code>samples.env</code><br/>→ match, no push<br/>✅ plans"]
Verification
| Check | Result |
|---|---|
cargo test -p datafusion-optimizer --lib extract_leaf_expressions on PR head (61923e2) |
✅ 57 passed |
| Same, with only the fix reverted (tests kept) | ❌ 2 failed: both new tests |
sqllogictests -- struct projection_pushdown on PR head |
✅ pass |
Simplified resolve_against (suggestion 1) |
✅ 57 passed |
Behavior change table
| Needed column | Input schema | main |
This PR |
|---|---|---|---|
bare c |
t.c |
pushes bare c |
pushes t.c (input spelling) |
t.c |
t.c, existing has bare c |
pushes t.c → ambiguous |
matched, no push |
t.c |
t.c, existing has t.c AS c |
pushes t.c → ambiguous |
matched, no push |
| any | column not in input (for example __common_expr_N) |
skipped | skipped (unchanged) |
Suggestions
| # | Where | Kind | Blocking |
|---|---|---|---|
| 1 | resolve_against |
Simplify to one lookup; fix doc claim about duplicate names | No |
| 2 | merge comment | Make shorter | No |
| 3 | struct.slt |
Add EXPLAIN to lock the plan shape |
No |
The doc on resolve_against says a duplicate name "resolves to nothing". That is not always true. qualified_field_with_unqualified_name returns the one unqualified field when the name is also held by qualified fields (see dfschema.rs). The behavior is fine; only the doc is wrong. Suggestion 1 fixes both.
|
@pepijnve if you are able to review this it would be great, we are hitting this in prod so pretty keen to fix it. I also think it's a good starting place to tackle the EPIC we have now. |
| } | ||
| } | ||
|
|
||
| /// The way `schema` names `col`, or `None` when `schema` does not hold it |
There was a problem hiding this comment.
nit: this sentence took me a couple of tries to grok. The function has an active name (an action), but the first sentence is describing a noun (a property). Might be better to write this in a 'Does computation xyz' style.
| // by alias expressions (e.g., CSE's __common_expr_N) exist in the output but | ||
| // not the input, and cannot be added as pass-through Column references. | ||
| // | ||
| // Compare both sides in the input's spelling (see `resolve_against`). |
There was a problem hiding this comment.
What are the 'sides' being referred to here? In the final sentence there's a reference to an empty union side. Makes this hard to interpret.
…puted same-name column (apache#25445) ## Which issue does this PR close? - Closes apache#25414. ## 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. ```sql create table t(a int, s struct<b varchar>) as values (1, {b: 'x'}), (2, {b: 'y'}); select a, s['b'] from (select -a as a, s from t where a > 0); ``` | Query | DataFusion on `main` | DuckDB and PostgreSQL | |---|---|---| | `select a, s['b'] from (select -a as a, s from t where a > 0)` | `1 x` / `2 y` | `-1 x` / `-2 y` | | `select a, s['b'] from (select -a as a, s from t limit 10)` | `1 x` / `2 y` | `-1 x` / `-2 y` | | `select a, s['b'] from (select a * 10 as a, s from t limit 10)` | `Internal error` | `10 x` / `20 y` | | `select a, s['b'] from (select -a as a, s from t) where a < 0` | `1 x` / `2 y` | `-1 x` / `-2 y` | | `select a, count(s['b']) from (select -a as a, s from t where a > 0) group by a` | groups `1` and `2` | groups `-1` and `-2` | The fourth query returns rows that fail its own filter. The third query stops the plan with this message: ``` Internal error: Assertion failed: compatible: Failed due to a difference in schemas: original schema: ... "a", data_type: Int64 ... new schema: ... "a", data_type: Int32 ... ``` The results are correct with `set datafusion.optimizer.enable_leaf_expression_pushdown = false;`. The cause is in `split_and_push_projection` in `datafusion/optimizer/src/extract_leaf_expressions.rs`. The rule pushes the extraction of `s['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: ```text Projection: (- t.a) AS a, t.s, get_field(t.s, "b") AS __datafusion_extracted_1 Filter: t.a > Int32(0) TableScan: t ``` the pushed plan holds the names `a`, `s` and `__datafusion_extracted_1` again, but its `a` is the table column `t.a`, not `- t.a`. The two sets are equal, the rule drops the recovery projection, and the computed column becomes its own input column: ```text Projection: a, __datafusion_extracted_1 AS t.s[b] Filter: t.a > Int32(0) Projection: get_field(t.s, "b") AS __datafusion_extracted_1, t.a, t.s TableScan: t ``` The same comparison ignores data types. That is why `a * 10 AS a` fails 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_column` gives that answer, and it already accepts a requalification such as `t.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 `SubqueryAlias` requalification behaviour does not change. The change is inside `split_and_push_projection` only. apache#25412 touches `build_extraction_projection_impl` in 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 of `datafusion/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 a `Filter`, through a `Limit`, a computed column with a different data type, an outer filter on the computed column, a group key, and through a `Sort`. One `EXPLAIN` shows that the projection that computes `-a` stays 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. The `Sort` shape was already correct and guards it. A unit test, `test_recovery_kept_for_same_name_computed_column` in `extract_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-optimizer` tests 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: apache#25459 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
…xtraction projection (apache#25412) A projection can list bare column names over a qualified input, which is what removing the empty side of a `UNION ALL` leaves. When `build_extraction_projection_impl` merged an extraction projection into it, a bare `c` and a qualified `t.c` did not match, so the pass-through column was added a second time and planning failed with "Schema contains qualified field name ... and unqualified field name ... which would be ambiguous". Carried from apache#25412 (open).
b1e3494 to
1e57bdf
Compare
…xtraction projection (apache#25412) Squash of apache#25412 for cherry-picking. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…er pushdown (apache#25453) ## Which issue does this PR close? <!-- No issue: this PR adds test coverage only. --> - Related to apache#25414, apache#25412 and apache#25268. ## Rationale for this change `ExtractLeafExpressions` and `PushDownLeafProjections` (the option `datafusion.optimizer.enable_leaf_expression_pushdown`, default `true`) move expressions such as `s['b']` out of a filter, sort, limit, aggregate or join, and push them towards the scan. The rules resolve columns by name. When a plan holds two columns with the same name, or a column that is computed from a column of the same name, the rules can drop a projection or merge the wrong pair. The user sees wrong rows or a planning error, with the default configuration. The rules had 10 bug fixes since February 2026. Users or an ad-hoc fuzzer found all of them. The 520 sqllogictest files found none, because they do not build these shapes. A differential test finds this class cheaply: run the same query with the option on and off, and compare the rows. This is a fixed-seed differential regression suite rather than a fuzzer that accumulates coverage; the seeds are the same on every run so a failure is reproducible with one command. Bugs of the class this test covers: - apache#25414: a computed column with the same name as its input is dropped. Wrong results. - apache#25412: pass-through columns merged in the wrong name space. Planning error. - apache#24678: a volatile expression duplicated by leaf extraction. Wrong results. - apache#25415: a filter on a volatile group key pushed below the aggregate. Wrong results. - apache#22895: `optimize_projections` fails with "No field named ..." when the join keys hold a `get_field`. Planning error. - apache#24241: `push_down_leaf_projections` fails for a self join. Planning error. The harness reproduces the two open bugs today. With `INCLUDE_SAME_NAME_ALIAS_SHAPES = true`, 526 of 3000 seeds fail (476 planning errors, 50 silent wrong results). With `INCLUDE_FALSE_BRANCH_UNION_SHAPES = true`, 404 of 3000 seeds fail. ## What changes are included in this PR? One new file, `datafusion/core/tests/fuzz_cases/leaf_pushdown_fuzz.rs`. The generator builds SQL over three wide tables and one narrow table. Every table has `Int32` columns, a `Utf8` column and two `Struct` columns. Two struct fields have the same name as a top level column. The generated shapes are: - `s['f']` in `SELECT`, `WHERE`, `ORDER BY`, `GROUP BY`, `HAVING`, `JOIN ON` and under `LIMIT`. - Derived tables, CTEs, `IN` and `EXISTS` subqueries, `UNION ALL`. - `named_struct(...)` built in a subquery and read above. - `arrow_field(a)` used bare and through `['f']` in the same query. - `random()` in a subquery projection, read two times above. The two runs cannot be compared with each other, so the query reports its own consistency in one boolean column. Each query runs in two `SessionContext`s built from the same batches. The rows are sorted and compared. An error on one side only is a failure. An error on both sides is a skip, and the test fails if more than 30% of the seeds skip. Three shapes fail on `main` today. Each one is behind a named `const` switch with the link to its issue, so this PR is green: - `INCLUDE_SAME_NAME_ALIAS_SHAPES` for apache#25414. - `INCLUDE_FALSE_BRANCH_UNION_SHAPES` for apache#25412. Flip the switch to `true` when the fix lands. ## What is the testing strategy for this PR? This PR is a test. Three test entry points: | test | cases | wall clock | | --- | --- | --- | | `leaf_pushdown_fuzz` | 250 | 3.9s | | `leaf_pushdown_parquet_fuzz` | 125 | included above | | `leaf_pushdown_parquet_schema_evolution` | 50 | included above | | `leaf_pushdown_fuzz_extended` | 5000 + 1000 | 87s | ```console cargo test --profile ci -p datafusion --features extended_tests --test fuzz -- leaf_pushdown ``` The whole `fuzz_cases` tree is already behind the `extended_tests` feature, so the short tests run in the extended job together with the long one. Every case comes from a seed. A failure prints the seed, the SQL and both result sets, and names the command that replays it: ```console LEAF_PUSHDOWN_FUZZ_SEED=<seed> LEAF_PUSHDOWN_FUZZ_CASES=1 \ cargo test --profile ci -p datafusion --features extended_tests \ --test fuzz -- leaf_pushdown --nocapture ``` ## Are there any user-facing changes? No. Update: the swap shape (`b AS a, a AS b`) fails for a different reason than the same-name computed column. It is apache#25446, which is open. It now has its own switch, `INCLUDE_SWAP_ALIAS_SHAPES`, so the fix for apache#25414 can be verified with `INCLUDE_SAME_NAME_ALIAS_SHAPES` alone. On a branch with apache#25445 and apache#25412 merged and both of those switches on, the only remaining failures are 24 of 250 seeds, all the swap shape with the ambiguity error. Known gaps from independent review, to be addressed in this PR: the generic generator only emits pass-through spellings (`c`, `q.c`, `q.c AS c`, `c AS c`), so renames and computed columns come only from the hardcoded same-name arm; `all_true` passes on an empty result; and the whole `fuzz_cases` tree runs only in the merge-queue `extended_tests` job, so the short run should move out from behind that gate. Part of the leaf-pushdown EPIC: apache#25459 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
…puted same-name column (apache#25445) ## Which issue does this PR close? - Closes apache#25414. ## 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. ```sql create table t(a int, s struct<b varchar>) as values (1, {b: 'x'}), (2, {b: 'y'}); select a, s['b'] from (select -a as a, s from t where a > 0); ``` | Query | DataFusion on `main` | DuckDB and PostgreSQL | |---|---|---| | `select a, s['b'] from (select -a as a, s from t where a > 0)` | `1 x` / `2 y` | `-1 x` / `-2 y` | | `select a, s['b'] from (select -a as a, s from t limit 10)` | `1 x` / `2 y` | `-1 x` / `-2 y` | | `select a, s['b'] from (select a * 10 as a, s from t limit 10)` | `Internal error` | `10 x` / `20 y` | | `select a, s['b'] from (select -a as a, s from t) where a < 0` | `1 x` / `2 y` | `-1 x` / `-2 y` | | `select a, count(s['b']) from (select -a as a, s from t where a > 0) group by a` | groups `1` and `2` | groups `-1` and `-2` | The fourth query returns rows that fail its own filter. The third query stops the plan with this message: ``` Internal error: Assertion failed: compatible: Failed due to a difference in schemas: original schema: ... "a", data_type: Int64 ... new schema: ... "a", data_type: Int32 ... ``` The results are correct with `set datafusion.optimizer.enable_leaf_expression_pushdown = false;`. The cause is in `split_and_push_projection` in `datafusion/optimizer/src/extract_leaf_expressions.rs`. The rule pushes the extraction of `s['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: ```text Projection: (- t.a) AS a, t.s, get_field(t.s, "b") AS __datafusion_extracted_1 Filter: t.a > Int32(0) TableScan: t ``` the pushed plan holds the names `a`, `s` and `__datafusion_extracted_1` again, but its `a` is the table column `t.a`, not `- t.a`. The two sets are equal, the rule drops the recovery projection, and the computed column becomes its own input column: ```text Projection: a, __datafusion_extracted_1 AS t.s[b] Filter: t.a > Int32(0) Projection: get_field(t.s, "b") AS __datafusion_extracted_1, t.a, t.s TableScan: t ``` The same comparison ignores data types. That is why `a * 10 AS a` fails 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_column` gives that answer, and it already accepts a requalification such as `t.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 `SubqueryAlias` requalification behaviour does not change. The change is inside `split_and_push_projection` only. apache#25412 touches `build_extraction_projection_impl` in 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 of `datafusion/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 a `Filter`, through a `Limit`, a computed column with a different data type, an outer filter on the computed column, a group key, and through a `Sort`. One `EXPLAIN` shows that the projection that computes `-a` stays 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. The `Sort` shape was already correct and guards it. A unit test, `test_recovery_kept_for_same_name_computed_column` in `extract_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-optimizer` tests 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: apache#25459 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
…er pushdown (apache#25453) ## Which issue does this PR close? <!-- No issue: this PR adds test coverage only. --> - Related to apache#25414, apache#25412 and apache#25268. ## Rationale for this change `ExtractLeafExpressions` and `PushDownLeafProjections` (the option `datafusion.optimizer.enable_leaf_expression_pushdown`, default `true`) move expressions such as `s['b']` out of a filter, sort, limit, aggregate or join, and push them towards the scan. The rules resolve columns by name. When a plan holds two columns with the same name, or a column that is computed from a column of the same name, the rules can drop a projection or merge the wrong pair. The user sees wrong rows or a planning error, with the default configuration. The rules had 10 bug fixes since February 2026. Users or an ad-hoc fuzzer found all of them. The 520 sqllogictest files found none, because they do not build these shapes. A differential test finds this class cheaply: run the same query with the option on and off, and compare the rows. This is a fixed-seed differential regression suite rather than a fuzzer that accumulates coverage; the seeds are the same on every run so a failure is reproducible with one command. Bugs of the class this test covers: - apache#25414: a computed column with the same name as its input is dropped. Wrong results. - apache#25412: pass-through columns merged in the wrong name space. Planning error. - apache#24678: a volatile expression duplicated by leaf extraction. Wrong results. - apache#25415: a filter on a volatile group key pushed below the aggregate. Wrong results. - apache#22895: `optimize_projections` fails with "No field named ..." when the join keys hold a `get_field`. Planning error. - apache#24241: `push_down_leaf_projections` fails for a self join. Planning error. The harness reproduces the two open bugs today. With `INCLUDE_SAME_NAME_ALIAS_SHAPES = true`, 526 of 3000 seeds fail (476 planning errors, 50 silent wrong results). With `INCLUDE_FALSE_BRANCH_UNION_SHAPES = true`, 404 of 3000 seeds fail. ## What changes are included in this PR? One new file, `datafusion/core/tests/fuzz_cases/leaf_pushdown_fuzz.rs`. The generator builds SQL over three wide tables and one narrow table. Every table has `Int32` columns, a `Utf8` column and two `Struct` columns. Two struct fields have the same name as a top level column. The generated shapes are: - `s['f']` in `SELECT`, `WHERE`, `ORDER BY`, `GROUP BY`, `HAVING`, `JOIN ON` and under `LIMIT`. - Derived tables, CTEs, `IN` and `EXISTS` subqueries, `UNION ALL`. - `named_struct(...)` built in a subquery and read above. - `arrow_field(a)` used bare and through `['f']` in the same query. - `random()` in a subquery projection, read two times above. The two runs cannot be compared with each other, so the query reports its own consistency in one boolean column. Each query runs in two `SessionContext`s built from the same batches. The rows are sorted and compared. An error on one side only is a failure. An error on both sides is a skip, and the test fails if more than 30% of the seeds skip. Three shapes fail on `main` today. Each one is behind a named `const` switch with the link to its issue, so this PR is green: - `INCLUDE_SAME_NAME_ALIAS_SHAPES` for apache#25414. - `INCLUDE_FALSE_BRANCH_UNION_SHAPES` for apache#25412. Flip the switch to `true` when the fix lands. ## What is the testing strategy for this PR? This PR is a test. Three test entry points: | test | cases | wall clock | | --- | --- | --- | | `leaf_pushdown_fuzz` | 250 | 3.9s | | `leaf_pushdown_parquet_fuzz` | 125 | included above | | `leaf_pushdown_parquet_schema_evolution` | 50 | included above | | `leaf_pushdown_fuzz_extended` | 5000 + 1000 | 87s | ```console cargo test --profile ci -p datafusion --features extended_tests --test fuzz -- leaf_pushdown ``` The whole `fuzz_cases` tree is already behind the `extended_tests` feature, so the short tests run in the extended job together with the long one. Every case comes from a seed. A failure prints the seed, the SQL and both result sets, and names the command that replays it: ```console LEAF_PUSHDOWN_FUZZ_SEED=<seed> LEAF_PUSHDOWN_FUZZ_CASES=1 \ cargo test --profile ci -p datafusion --features extended_tests \ --test fuzz -- leaf_pushdown --nocapture ``` ## Are there any user-facing changes? No. Update: the swap shape (`b AS a, a AS b`) fails for a different reason than the same-name computed column. It is apache#25446, which is open. It now has its own switch, `INCLUDE_SWAP_ALIAS_SHAPES`, so the fix for apache#25414 can be verified with `INCLUDE_SAME_NAME_ALIAS_SHAPES` alone. On a branch with apache#25445 and apache#25412 merged and both of those switches on, the only remaining failures are 24 of 250 seeds, all the swap shape with the ambiguity error. Known gaps from independent review, to be addressed in this PR: the generic generator only emits pass-through spellings (`c`, `q.c`, `q.c AS c`, `c AS c`), so renames and computed columns come only from the hardcoded same-name arm; `all_true` passes on an empty result; and the whole `fuzz_cases` tree runs only in the merge-queue `extended_tests` job, so the short run should move out from behind that gate. Part of the leaf-pushdown EPIC: apache#25459 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
…xtraction projection (apache#25412) A projection can list bare column names over a qualified input, which is what removing the empty side of a `UNION ALL` leaves. When `build_extraction_projection_impl` merged an extraction projection into it, a bare `c` and a qualified `t.c` did not match, so the pass-through column was added a second time and planning failed with "Schema contains qualified field name ... and unqualified field name ... which would be ambiguous". Carried from apache#25412 (open).
kosiew
left a comment
There was a problem hiding this comment.
Thanks for the fix. The schema-based column resolution addresses the qualified/unqualified ambiguity, and the same-name alias handling covers the earlier concern. The focused optimizer and SQL regression tests passed. I have one optional test coverage suggestion below, which is not a blocker.
| && input_schema.has_column(resolved_col) | ||
| && let Some(input_col) = resolve_against(input_schema, resolved_col) | ||
| && !existing_cols.contains(&input_col) | ||
| { |
There was a problem hiding this comment.
Could we add a test where the existing projection retains only test.a and columns_needed requests bare b? It would verify that the newly appended column is test.b, with qualifier test and name b, rather than unqualified b. The current tests cover deduplication but not this specific case. This is optional and could be done in a follow-up.
…xtraction projection (apache#25412) Squash of apache#25412 for cherry-picking. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
37cb051 to
0c0a388
Compare
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thanks @kosiew |
Which issue does this PR close?
None filed.
Rationale for this change
This query fails to plan on
main:enable_leaf_expression_pushdownis on by default, so this is a plain planningfailure for a valid statement. A
union allwhose one side is provably empty iswhat produces the shape, and query generators emit that: a dashboard panel adds a
second branch behind a comparison of two constants and the panel stops working.
What changes are included in this PR?
build_extraction_projection_implmerges an extraction projection into theprojection below it, and then adds the pass-through columns the merged projection
does not already carry. It compared the columns it was about to add against the
projection's own expressions without putting the two in the same space.
A projection can list bare column names over a qualified input. Removing the empty
side of the union leaves exactly that. The comparison then misses, the column is
added a second time under its bare name, and
Projection::try_newrejects a schemathat holds
samples.envand a bareenvtogether.This PR resolves both sides against the input schema before the comparison, and
pushes the column under the name the input gives it. A needed column that is not in
the input, or that is ambiguous there, is skipped. Before this change such a column
was added and
Projection::try_newfailed at the merge.A same-name alias such as
t.c AS ccounts as a pass-through on the existing side too, the same as a baret.c.Are these changes tested?
Yes. The statement above is added to
struct.slt, with a result check and anEXPLAINplan pin. It fails onmainwith the error above and passes with this change.Three unit tests in
extract_leaf_expressions.rscover the merge directly. All three fail onmain:test_merge_bare_column_into_qualified_projection: a filter namesaover a projection that outputstest.a.test_merge_into_projection_with_same_name_alias: the projection spells the pass-through astest.a AS a.test_merge_appends_qualified_column_for_bare_request: the projection keeps onlytest.aand the merge needs a bareb. The appended column must betest.b. Onmainit is a bareb.cargo test --workspace --exclude datafusion-sqllogictestpasses: 126 suites,12479 tests, 0 failures. The sqllogictest suite passes too: 520 files, 0
failures.
cargo clippy -p datafusion-optimizer --all-targetsandcargo fmt --all -- --checkare clean.Are there any user-facing changes?
A statement of this shape plans instead of failing. No API change.
Note on overlapping work
#25388 (draft) edits the same arm of
build_extraction_projection_impl, for a different problem: duplicatedevaluation of
KeepInPlaceexpressions, #25329.It does not touch the qualifier comparison this PR changes, so the two are
independent in behaviour, but whichever lands second will need a small rebase at
the tail of the
columns_neededloop.#25282 also appends to the end of
struct.slt, as this PR does. Keeping both blocks is the whole resolution.Part of the leaf-pushdown EPIC: #25459