Skip to content

Make PushDownFilter and CommonSubexprEliminate aware of Expr::placement - #20239

Merged
adriangb merged 6 commits into
apache:mainfrom
pydantic:add-utils
Feb 9, 2026
Merged

Make PushDownFilter and CommonSubexprEliminate aware of Expr::placement#20239
adriangb merged 6 commits into
apache:mainfrom
pydantic:add-utils

Conversation

@adriangb

@adriangbadriangb commented Feb 9, 2026

Copy link
Copy Markdown
Contributor

Teaches PushDownFilter to not push down through ExpressionPlacement::MoveTowardsLeafNodes using the same approach already in place for volatile expressions.

Split out from #20117.

CopilotAI 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.

Pull request overview

Updates PushDownFilter’s projection-rewrite behavior to avoid re-inlining certain “move towards leaf nodes” expressions (e.g., get_field) back into filter predicates, preventing optimizer instability with the ExtractLeafExpressions pipeline mentioned in the linked work.

Changes:

  • Classifies non-volatile projection expressions and prevents pushing filters through MoveTowardsLeafNodes projection outputs.
  • Rewrites pushed predicates using a reduced replacement map that excludes MoveTowardsLeafNodes expressions.
  • Adds unit tests covering: non-push through MoveTowardsLeafNodes, push through regular projections, push through KeepInPlace, and mixed-predicate partial pushdown.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment threaddatafusion/optimizer/src/push_down_filter.rs Outdated
@adriangbadriangb changed the title Make PushDownFilter skip pushing down through Expr::placement()::should_push_towards_leaves() = trueMake PushDownFilter skip pushing down through MoveTowardsLeafNodes projectionsFeb 9, 2026
@adriangbadriangb changed the title Make PushDownFilter skip pushing down through MoveTowardsLeafNodes projectionsMake PushDownFilter aware of Expr::placementFeb 9, 2026
adriangb added a commit to pydantic/datafusion that referenced this pull request Feb 9, 2026
@adriangb
adriangb requested a review from alambFebruary 9, 2026 14:16
@adriangbadriangb changed the title Make PushDownFilter aware of Expr::placementMake PushDownFilter and CommonSubexprEliminate aware of Expr::placementFeb 9, 2026
@github-actionsgithub-actionsBot added the sqllogictest SQL Logic Tests (.slt) label Feb 9, 2026

@alambalamb 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.

Thanks @adriangb -- I think this looks good to me. I had some suggestions, but none are required to merge this PR in my opinion


/// Identical MoveTowardsLeafNodes expressions should NOT be deduplicated
/// by CSE — they are cheap (e.g. struct field access) and the extraction
/// rules deliberately duplicate them. Deduplicating causes optimizer

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.

What does "deduplicating causes optimizer instability" mean? Maybe we could make that more specific

// MoveTowardsLeafNodes expressions (like get_field) are cheap — no benefit to
// re-inlining them into filters, and it causes optimizer instability with
// ExtractLeafExpressions.
let (move_towards_leaves_map, pushable_map): (HashMap<_, _>, HashMap<_, _>) =

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.

Is there any reason to have two separate hash maps? It seems like a single one would work (and maybe rename from volatile_map to no_move_map or something ?)


/// Test that filters are NOT pushed through MoveTowardsLeafNodes projections.
/// These are cheap expressions (like get_field) where re-inlining into a filter
/// has no benefit and causes optimizer instability with ExtractLeafExpressions.

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.

same question here about what instablity is caused

}

/// A mock UDF that reports MoveTowardsLeafNodes placement (like get_field).
#[derive(Debug, PartialEq, Eq, Hash)]

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.

This is basically the same as in datafusion/optimizer/src/common_subexpr_eliminate.rs

I wonder if it would be better to put them in a common place (both to avoid code duplication, and make new tests easier to write)?

let udf = ScalarUDF::new_from_impl(LeafUDF {
signature: Signature::exact(vec![DataType::UInt32], Volatility::Immutable),
});
Expr::ScalarFunction(ScalarFunction::new_udf(Arc::new(udf), vec![arg]))

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.

I think you could do this via call 🤔

Suggested change
Expr::ScalarFunction(ScalarFunction::new_udf(Arc::new(udf),vec![arg]))
udf.call(vec![arg])

)
}

/// Test that filters ARE pushed through regular (Column/KeepInPlace) projections.

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.

this is likely already tested by other tests

One way you maybe could make it clear this is the same as above, only difference is the expression, might be with a builder type API

Something like

let udf_expr = leaf_udf().with_placement(ExpressionPlacement::KeepInPlace)// <----- Builder API to manually control udf).call(vec![col("a")]).alias("val"),

adriangb added a commit to pydantic/datafusion that referenced this pull request Feb 9, 2026
- Merge volatile_map + move_towards_leaves_map into single non_pushable_map
in rewrite_projection
- Deduplicate LeafUDF into shared PlacementTestUDF with builder API in
test/udfs.rs, using udf.call() instead of manual Expr construction
- Remove redundant tests (filter_pushed_through_regular_projection,
filter_pushed_through_keep_in_place_projection)
- Clarify "optimizer instability" comments to explain the infinite loop
mechanism consistently across CSE and push_down_filter
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
adriangband others added 4 commits February 9, 2026 13:15
…r instability
CSE was deduplicating cheap get_field() expressions, creating intermediate
projections that fought with ExtractLeafExpressions / PushDownLeafProjections.
The extraction rules deliberately duplicate get_field (one copy for a filter
predicate, another for an output column). CSE deduplicating them caused
alias counter inflation (__datafusion_extracted_5 instead of _1) and
unnecessary optimizer iterations.
The fix teaches CSE to skip MoveTowardsLeafNodes expressions (e.g. get_field)
since they are cheap struct field accesses with no benefit from deduplication.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Merge volatile_map + move_towards_leaves_map into single non_pushable_map
in rewrite_projection
- Deduplicate LeafUDF into shared PlacementTestUDF with builder API in
test/udfs.rs, using udf.call() instead of manual Expr construction
- Remove redundant tests (filter_pushed_through_regular_projection,
filter_pushed_through_keep_in_place_projection)
- Clarify "optimizer instability" comments to explain the infinite loop
mechanism consistently across CSE and push_down_filter
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@adriangb
adriangb added this pull request to the merge queueFeb 9, 2026
Merged via the queue into apache:main with commit 9333f74Feb 9, 2026
32 checks passed
github-merge-queueBot pushed a commit that referenced this pull request Feb 11, 2026
Followup to #20238 bringing in the shared test UDFs from #20239
de-bgunter pushed a commit to de-bgunter/datafusion that referenced this pull request Mar 24, 2026
…nt (apache#20239)
Teaches PushDownFilter to not push down through
`ExpressionPlacement::MoveTowardsLeafNodes` using the same approach
already in place for volatile expressions.
Split out from apache#20117.
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
de-bgunter pushed a commit to de-bgunter/datafusion that referenced this pull request Mar 24, 2026
Druva-D pushed a commit to Druva-D/datafusion that referenced this pull request May 4, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

optimizerOptimizer rulessqllogictestSQL Logic Tests (.slt)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@adriangb@alamb