fix: do not duplicate volatile expressions when extracting leaf expressions - #24720
Open
jaideeppyne wants to merge 1 commit into
Open
fix: do not duplicate volatile expressions when extracting leaf expressions#24720jaideeppyne wants to merge 1 commit into
jaideeppyne wants to merge 1 commit into
Conversation
…ssions
`extract_leaf_expressions` / `push_down_leaf_projections` build an extraction
projection by *merging* into the input projection, which inlines the definition
of every column the extracted expression references. When that definition is
volatile, the inlined copy is an independent evaluation:
SELECT s, s['a'] AS field
FROM (SELECT named_struct('a', random()) AS s FROM generate_series(1, 3));
planned to
Projection: named_struct(Utf8("a"), random()) AS s, random() AS field
so `field` disagreed with `s['a']` on every row. Extracting out of a `Filter`
is worse: `WHERE s['a'] > 0.5` tested a second draw, and rows whose `s['a']`
was <= 0.5 were returned.
Skip the extraction when a referenced column's definition is volatile. This is
the same invariant `FileScanConfig::try_swapping_with_projection` already
enforces for the physical projection-pushdown path (apache#23220).
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 column alias denotes one value per row, and
WHERE pmay only return rows for whichpheld for that row. Today the leaf-expression extraction passes break both:s['a']fieldfieldis defined ass['a']but differs from it on every row, because the plan isThe
Filterform is worse — it returns rows that fail their own predicate:Setting
datafusion.optimizer.enable_leaf_expression_pushdown = falsereturns the correct answer in both cases, so the rewrite alone changes the meaning of the query.Root cause.
build_extraction_projection_implmerges an extraction into the input projection by resolving column references throughbuild_projection_replace_map, i.e. by inlining each referenced column's defining expression. Inlining a volatile definition produces a second, independent evaluation. There was no volatility check in the file.This is the same invariant
FileScanConfig::try_swapping_with_projectionalready enforces for the physical projection-pushdown path viawould_duplicate_costly_exprs(#23220) — the logical extraction path was missing it.What changes are included in this PR?
volatile_output_columns()— a projection's output columns whose definition is volatile.would_duplicate_volatile()— true when an extraction references one of them.extract_from_plan(pass 1: Filter/Sort/Limit/Aggregate/Join),split_and_push_projection(pass 2), andtry_push_into_inputs(multi-input/Union routing). Each already had a "leave the plan alone" return path.The guard is targeted rather than blanket: for
ORDER BY s['a']the extraction still happens, stacked above the volatile projection instead of merged into it, so the optimization is kept and the result is correct.Relationship to #23691
@fornwall wondered on the issue whether #23691 already covers this. I checked out that branch and ran both shapes against it:
SELECT s, s['a']is incidentally fixed there, butWHERE s['a'] > 0.5still duplicatesrandom()and still returns rows failing the predicate. #23691 guardsKeepInPlacecompute cost insplit_and_push_projectiononly; volatility is a separate concern (one duplication is already wrong, regardless of cost or placement) and pass 1 is a different code path. The two changes look independent to me and I believe they compose, but I'd appreciate a second opinion on that from whoever reviews #23691.Are these changes tested?
Yes.
datafusion/sqllogictest/test_files/projection_pushdown.slt— a new section beside the existing Projection pushdown into file scan duplicates non-deterministic functions (regression in 52.0.0) #23220 volatile section: twoEXPLAINs pinningrandom()to a single occurrence, and two deterministicbool_and(...)correctness queries. All four fail onmainand pass here.extract_leaf_expressions.rscovering the pass-2 projection merge and the pass-1Filterextraction, using a new test-onlyPlacementTestUDF::with_volatility().cargo test -p datafusion-optimizer(796),cargo test -p datafusion --lib --tests(2049) and the full 504-file sqllogictest suite all pass;cargo fmt --allandcargo clippy --all-targets --all-features -- -D warningsare clean.Are there any user-facing changes?
Queries that were silently returning wrong results now return correct ones. No API change. In the affected shapes the extraction is skipped, which can cost a small amount of column pruning — only when the referenced column is defined by a volatile expression.
Per the ASF generative-tooling policy and DataFusion's AI-assisted contribution guidance: this patch was prepared with AI assistance. The core idea is the one described above — the merge path inlines a referenced column's defining expression, which duplicates a volatile definition — and the open question about how this composes with #23691 is flagged deliberately rather than glossed over.