Skip to content

[branch-55] Backport of refactor(physical-plan): Simplify ExecutionPlan API with replace_children - #24296

Merged
alamb merged 1 commit into
apache:branch-55from
JSOD11:jsod/backport-branch-55-08-12-26
Aug 12, 2026
Merged

[branch-55] Backport of refactor(physical-plan): Simplify ExecutionPlan API with replace_children#24296
alamb merged 1 commit into
apache:branch-55from
JSOD11:jsod/backport-branch-55-08-12-26

Conversation

@JSOD11

Copy link
Copy Markdown
Contributor

Backport of #23903 to branch-55.

…hildren` (apache#23903)
## Which issue does this PR close?
- Closesapache#23441
## User-facing changes: Deprecating `with_new_children` and
`with_new_children_and_same_properties` in favor of `replace_children`
As noted
[here](apache#23332 (comment)),
while the addition of `with_new_children_and_same_properties` has the
benefit of skipping potentially expensive computation in the case that
replacement children have the same properties as the original children,
it widens the API surface area of `ExecutionPlan` in a way that could be
confusing for users.
Thus, to rectify this, we unify these methods by introducing
`replace_children`, and we shift towards
`with_new_children_if_necessary` as the universal entry point for
replacing the children of an `ExecutionPlan`. `replace_children`
simplifies the interface for users by taking an enum called
`ChildrenPropertiesHint` as an argument. The enum has two variants,
`SameProperties` and `Recompute`, which function as a hint to
`replace_children` from the caller as to whether or not the properties
need to be recomputed.
## Trait implementation migration
To migrate from `with_new_children` and
`with_new_children_and_same_properties` to `replace_children`, I went
through all 93 implementations of `with_new_children` and implemented
`replace_children` with a `match` statement matching on the
`ChildrenPropertiesHint`. In the case that the properties match,
`ChildrenPropertiesHint::SameProperties`, and we have an implementation
of `with_new_children_and_same_properties`, then we follow the body of
`with_new_children_and_same_properties`. In the case that the properties
do not match, `ChildrenPropertiesHint::Recompute`, we follow the body of
`with_new_children`. In the cases in which there was no implementation
of `with_new_children_and_same_properties`, I simply move the body of
`with_new_children` into `replace_children` and ignore the hint.
I mark `with_new_children` and `with_new_children_and_same_properties`
as deprecated with a migration note pointing to `replace_children`.
After a couple releases, we'll drop the deprecated methods.
## Example
For example, here is what the implementation looks like for `FilterExec`
after this change:
```
fn replace_children(
self: Arc<Self>,
mut children: Vec<Arc<dyn ExecutionPlan>>,
hint: ChildrenPropertiesHint,
) -> Result<Arc<dyn ExecutionPlan>> {
validate_child_count!(self, children);
match hint {
ChildrenPropertiesHint::SameProperties => Ok(Arc::new(Self {
input: children.swap_remove(0),
metrics: ExecutionPlanMetricsSet::new(),
..Self::clone(&*self)
})),
ChildrenPropertiesHint::Recompute => {
let new_input = children.swap_remove(0);
FilterExecBuilder::from(&*self)
.with_input(new_input)
.build()
.map(|e| Arc::new(e) as _)
}
}
}
```
We see here that in the case that the hint suggests the properties are
the same, we can simply swap the children without having to recompute
the properties. In the case that the properties are not the same, we
create a new node from scratch. We achieve this functionality by moving
the hint calculations definitively into `with_new_children_if_necessary`
rather than having them scattered around many methods. However, for this
to all work we must ensure that users actually do use
`with_new_children_if_necessary` by making it obvious to them somehow. I
feel `replace_children` is a step in the right direction, but it could
still be easy for a user to miss `with_new_children_if_necessary` and
just jump to using `replace_children` instead.
## Usage Migration
`replace_children` is called from `with_new_children_if_necessary`,
which is the standard entry point that should be used for replacing the
children of a node.
To model the intended behavior for our users, I took the time here to
migrate usages of `with_new_children` and
`with_children_and_same_properties` to `with_new_children_if_necessary`
where it made sense to do so, and I migrated
`with_new_children_if_necessary` to use `replace_children` with the
correct hint filled in at each branch.
## Testing
- `cargo fmt --all`
- `cargo check -p datafusion-physical-plan`
- `cargo check -p datafusion-physical-optimizer`
- `cargo check -p datafusion --lib`
- `cargo check -p datafusion-ffi`
- CI passing
---------
Co-authored-by: Andrew Lamb <andrew@nerdnetworks.org>
@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation optimizer Optimizer rules core Core DataFusion crate catalog Related to the catalog crate proto Related to proto crate datasource Changes to the datasource crate ffi Changes to the ffi crate physical-plan Changes to the physical-plan crate labels Aug 12, 2026
@JSOD11
JSOD11 marked this pull request as ready for review August 12, 2026 19:50
@alambalamb changed the title Backport of refactor(physical-plan): Simplify ExecutionPlan API with replace_children[branch-55] Backport of refactor(physical-plan): Simplify ExecutionPlan API with replace_childrenAug 12, 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.

I had claude code verify that this was a clean backport

Thank you @JSOD11

@codecov-commenter

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 39.02066% with 797 lines in your changes missing coverage. Please review.
✅ Project coverage is 81.14%. Comparing base (46bd88a) to head (811b8c8).
⚠️ Report is 1 commits behind head on branch-55.

Files with missing linesPatch %Lines
datafusion/physical-plan/src/test/exec.rs0.00%60 Missing ⚠️
datafusion/physical-plan/src/execution_plan.rs50.42%58 Missing ⚠️
datafusion/core/src/physical_planner.rs36.23%44 Missing ⚠️
...ysical-plan/src/joins/piecewise_merge_join/exec.rs32.00%34 Missing ⚠️
datafusion/physical-plan/src/aggregates/mod.rs47.36%30 Missing ⚠️
datafusion/physical-plan/src/sorts/partial_sort.rs0.00%27 Missing ⚠️
datafusion/physical-plan/src/buffer.rs0.00%26 Missing ⚠️
datafusion/physical-plan/src/async_func.rs0.00%25 Missing ⚠️
datafusion/physical-plan/src/limit.rs61.01%23 Missing ⚠️
datafusion/physical-plan/src/sorts/sort.rs14.81%22 Missing and 1 partial ⚠️
... and 41 more
Additional details and impacted files
@@ Coverage Diff @@## branch-55 #24296 +/- ##
=============================================
- Coverage 81.29% 81.14% -0.16% 
=============================================
Files 1110 1110 Lines 385336 386132 +796 Branches 385336 386132 +796 =============================================
+ Hits 313258 313318 +60 - Misses 53594 54346 +752 + Partials 18484 18468 -16 

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@alamb
alamb merged commit 83d3489 into apache:branch-55Aug 12, 2026
39 checks passed
@alamb

Copy link
Copy Markdown
Contributor

Thanks again @JSOD11

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

catalogRelated to the catalog cratecoreCore DataFusion cratedatasourceChanges to the datasource cratedocumentationImprovements or additions to documentationffiChanges to the ffi crateoptimizerOptimizer rulesphysical-planChanges to the physical-plan crateprotoRelated to proto crate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@JSOD11@codecov-commenter@alamb