Skip to content

refactor(physical-plan): Simplify ExecutionPlan API with replace_children - #23903

Merged
alamb merged 25 commits into
apache:mainfrom
JSOD11:jsod/replace-children-07-25-26
Aug 12, 2026
Merged

refactor(physical-plan): Simplify ExecutionPlan API with replace_children#23903
alamb merged 25 commits into
apache:mainfrom
JSOD11:jsod/replace-children-07-25-26

Conversation

@JSOD11

@JSOD11JSOD11 commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Which issue does this PR close?

User-facing changes: Deprecating with_new_children and with_new_children_and_same_properties in favor of replace_children

As noted here, 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

@github-actionsgithub-actionsBot added the physical-plan Changes to the physical-plan crate label Jul 26, 2026
@codecov-commenter

codecov-commenter commented Jul 26, 2026

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 (7e015b7) to head (71d7c92).
⚠️ Report is 2 commits behind head on main.

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 @@## main #23903 +/- ##
==========================================
- Coverage 81.29% 81.14% -0.15% 
==========================================
Files 1110 1110 Lines 385197 386132 +935 Branches 385197 386132 +935 ==========================================
+ Hits 313132 313323 +191 - Misses 53588 54341 +753 + Partials 18477 18468 -9 

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

@github-actionsgithub-actionsBot added the optimizer Optimizer rules label Jul 26, 2026
@JSOD11JSOD11 changed the title feat: replace_childrenrefactor(physical-plan): Simplify ExecutionPlan API with replace_childrenJul 27, 2026
@github-actionsgithub-actionsBot added core Core DataFusion crate catalog Related to the catalog crate proto Related to proto crate ffi Changes to the ffi crate labels Jul 28, 2026
@github-actionsgithub-actionsBot added datasource Changes to the datasource crate auto detected api change Auto detected API change labels Jul 28, 2026
@github-actionsgithub-actionsBot removed the auto detected api change Auto detected API change label Jul 29, 2026
@github-actions

github-actionsBot commented Jul 29, 2026

Copy link
Copy Markdown

Thank you for opening this pull request!

Reviewer note: cargo-semver-checks reported the current version number is not SemVer-compatible with the changes in this pull request (compared against the base branch).

Details
 Cloning apache/main
Building datafusion v54.1.0 (current)
Built [ 73.446s] (current)
Parsing datafusion v54.1.0 (current)
Parsed [ 0.026s] (current)
Building datafusion v54.1.0 (baseline)
Built [ 74.536s] (baseline)
Parsing datafusion v54.1.0 (baseline)
Parsed [ 0.026s] (baseline)
Checking datafusion v54.1.0 -> v54.1.0 (no change; assume patch)
Checked [ 0.775s] 223 checks: 223 pass, 31 skip
Summary no semver update required
Finished [ 151.244s] datafusion
Building datafusion-catalog v54.1.0 (current)
Built [ 30.318s] (current)
Parsing datafusion-catalog v54.1.0 (current)
Parsed [ 0.019s] (current)
Building datafusion-catalog v54.1.0 (baseline)
Built [ 29.426s] (baseline)
Parsing datafusion-catalog v54.1.0 (baseline)
Parsed [ 0.019s] (baseline)
Checking datafusion-catalog v54.1.0 -> v54.1.0 (no change; assume patch)
Checked [ 0.135s] 223 checks: 223 pass, 31 skip
Summary no semver update required
Finished [ 61.112s] datafusion-catalog
Building datafusion-datasource v54.1.0 (current)
Built [ 31.262s] (current)
Parsing datafusion-datasource v54.1.0 (current)
Parsed [ 0.024s] (current)
Building datafusion-datasource v54.1.0 (baseline)
Built [ 31.497s] (baseline)
Parsing datafusion-datasource v54.1.0 (baseline)
Parsed [ 0.024s] (baseline)
Checking datafusion-datasource v54.1.0 -> v54.1.0 (no change; assume patch)
Checked [ 0.320s] 223 checks: 223 pass, 31 skip
Summary no semver update required
Finished [ 64.286s] datafusion-datasource
Building datafusion-ffi v54.1.0 (current)
Built [ 42.872s] (current)
Parsing datafusion-ffi v54.1.0 (current)
Parsed [ 0.047s] (current)
Building datafusion-ffi v54.1.0 (baseline)
Built [ 44.637s] (baseline)
Parsing datafusion-ffi v54.1.0 (baseline)
Parsed [ 0.047s] (baseline)
Checking datafusion-ffi v54.1.0 -> v54.1.0 (no change; assume patch)
Checked [ 0.315s] 223 checks: 223 pass, 31 skip
Summary no semver update required
Finished [ 89.072s] datafusion-ffi
Building datafusion-physical-optimizer v54.1.0 (current)
Built [ 30.453s] (current)
Parsing datafusion-physical-optimizer v54.1.0 (current)
Parsed [ 0.017s] (current)
Building datafusion-physical-optimizer v54.1.0 (baseline)
Built [ 30.413s] (baseline)
Parsing datafusion-physical-optimizer v54.1.0 (baseline)
Parsed [ 0.017s] (baseline)
Checking datafusion-physical-optimizer v54.1.0 -> v54.1.0 (no change; assume patch)
Checked [ 0.137s] 223 checks: 223 pass, 31 skip
Summary no semver update required
Finished [ 61.965s] datafusion-physical-optimizer
Building datafusion-physical-plan v54.1.0 (current)
Built [ 27.976s] (current)
Parsing datafusion-physical-plan v54.1.0 (current)
Parsed [ 0.120s] (current)
Building datafusion-physical-plan v54.1.0 (baseline)
Built [ 27.588s] (baseline)
Parsing datafusion-physical-plan v54.1.0 (baseline)
Parsed [ 0.105s] (baseline)
Checking datafusion-physical-plan v54.1.0 -> v54.1.0 (no change; assume patch)
Checked [ 0.848s] 223 checks: 221 pass, 2 fail, 0 warn, 31 skip
--- failure function_marked_deprecated: function #[deprecated] added ---
Description:
A function is now #[deprecated]. Downstream crates will get a compiler warning when using this function.
ref: https://doc.rust-lang.org/reference/attributes/diagnostics.html#the-deprecated-attribute
impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.50.0/src/lints/function_marked_deprecated.ron
Failed in:
function datafusion_physical_plan::execution_plan::with_new_children_if_necessary in /home/runner/work/datafusion/datafusion/datafusion/physical-plan/src/execution_plan.rs:1735
function datafusion_physical_plan::with_new_children_if_necessary in /home/runner/work/datafusion/datafusion/datafusion/physical-plan/src/execution_plan.rs:1735
--- failure trait_method_marked_deprecated: trait method #[deprecated] added ---
Description:
A trait method is now #[deprecated]. Downstream crates will get a compiler warning when using this method.
ref: https://doc.rust-lang.org/reference/attributes/diagnostics.html#the-deprecated-attribute
impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.50.0/src/lints/trait_method_marked_deprecated.ron
Failed in:
method with_new_children in trait datafusion_physical_plan::execution_plan::ExecutionPlan in /home/runner/work/datafusion/datafusion/datafusion/physical-plan/src/execution_plan.rs:102
method with_new_children_and_same_properties in trait datafusion_physical_plan::execution_plan::ExecutionPlan in /home/runner/work/datafusion/datafusion/datafusion/physical-plan/src/execution_plan.rs:102
method with_new_children in trait datafusion_physical_plan::ExecutionPlan in /home/runner/work/datafusion/datafusion/datafusion/physical-plan/src/execution_plan.rs:102
method with_new_children_and_same_properties in trait datafusion_physical_plan::ExecutionPlan in /home/runner/work/datafusion/datafusion/datafusion/physical-plan/src/execution_plan.rs:102
Summary semver requires new minor version: 0 major and 2 minor checks failed
Finished [ 58.144s] datafusion-physical-plan
Building datafusion-proto v54.1.0 (current)
Built [ 45.168s] (current)
Parsing datafusion-proto v54.1.0 (current)
Parsed [ 0.014s] (current)
Building datafusion-proto v54.1.0 (baseline)
Built [ 44.064s] (baseline)
Parsing datafusion-proto v54.1.0 (baseline)
Parsed [ 0.015s] (baseline)
Checking datafusion-proto v54.1.0 -> v54.1.0 (no change; assume patch)
Checked [ 0.145s] 223 checks: 223 pass, 31 skip
Summary no semver update required
Finished [ 90.722s] datafusion-proto

@github-actionsgithub-actionsBot added the auto detected api change Auto detected API change label Jul 29, 2026
@github-actionsgithub-actionsBot added the documentation Improvements or additions to documentation label Jul 29, 2026
@JSOD11
JSOD11 marked this pull request as ready for review July 30, 2026 17:30
@JSOD11

Copy link
Copy Markdown
ContributorAuthor

cc @zhuqi-lucas

Took a stab at this, let me know what you think!

@zhuqi-lucas

Copy link
Copy Markdown
Contributor

run benchmarks

@adriangbot

Copy link
Copy Markdown

🤖 Benchmark running (GKE) | trigger
Instance:c4a-highmem-16 (12 vCPU / 65 GiB) | Linux bench-c5139175106-1320-pzj5k 6.12.85+ #1 SMP Mon May 11 08:17:35 UTC 2026 aarch64 GNU/Linux

CPU Details (lscpu)
Architecture: aarch64
CPU op-mode(s): 64-bit
Byte Order: Little Endian
CPU(s): 16
On-line CPU(s) list: 0-15
Vendor ID: ARM
Model name: Neoverse-V2
Model: 1
Thread(s) per core: 1
Core(s) per cluster: 16
Socket(s): -
Cluster(s): 1
Stepping: r0p1
BogoMIPS: 2000.00
Flags: fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm jscvt fcma lrcpc dcpop sha3 sm3 sm4 asimddp sha512 sve asimdfhm dit uscat ilrcpc flagm sb paca pacg dcpodp sve2 sveaes svepmull svebitperm svesha3 svesm4 flagm2 frint svei8mm svebf16 i8mm bf16 dgh rng bti
L1d cache: 1 MiB (16 instances)
L1i cache: 1 MiB (16 instances)
L2 cache: 32 MiB (16 instances)
L3 cache: 80 MiB (1 instance)
NUMA node(s): 1
NUMA node0 CPU(s): 0-15
Vulnerability Gather data sampling: Not affected
Vulnerability Indirect target selection: Not affected
Vulnerability Itlb multihit: Not affected
Vulnerability L1tf: Not affected
Vulnerability Mds: Not affected
Vulnerability Meltdown: Not affected
Vulnerability Mmio stale data: Not affected
Vulnerability Reg file data sampling: Not affected
Vulnerability Retbleed: Not affected
Vulnerability Spec rstack overflow: Not affected
Vulnerability Spec store bypass: Mitigation; Speculative Store Bypass disabled via prctl
Vulnerability Spectre v1: Mitigation; __user pointer sanitization
Vulnerability Spectre v2: Mitigation; CSV2, BHB
Vulnerability Srbds: Not affected
Vulnerability Tsa: Not affected
Vulnerability Tsx async abort: Not affected
Vulnerability Vmscape: Not affected

Comparing jsod/replace-children-07-25-26 (a2ccc1a) to 88365dd (merge-base) diff using: tpch
Results will be posted here when complete


File an issue against this benchmark runner

@adriangbot

Copy link
Copy Markdown

🤖 Benchmark running (GKE) | trigger
Instance:c4a-highmem-16 (12 vCPU / 65 GiB) | Linux bench-c5139175106-1318-9mbtt 6.12.85+ #1 SMP Mon May 11 08:17:35 UTC 2026 aarch64 GNU/Linux

CPU Details (lscpu)
Architecture: aarch64
CPU op-mode(s): 64-bit
Byte Order: Little Endian
CPU(s): 16
On-line CPU(s) list: 0-15
Vendor ID: ARM
Model name: Neoverse-V2
Model: 1
Thread(s) per core: 1
Core(s) per cluster: 16
Socket(s): -
Cluster(s): 1
Stepping: r0p1
BogoMIPS: 2000.00
Flags: fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm jscvt fcma lrcpc dcpop sha3 sm3 sm4 asimddp sha512 sve asimdfhm dit uscat ilrcpc flagm sb paca pacg dcpodp sve2 sveaes svepmull svebitperm svesha3 svesm4 flagm2 frint svei8mm svebf16 i8mm bf16 dgh rng bti
L1d cache: 1 MiB (16 instances)
L1i cache: 1 MiB (16 instances)
L2 cache: 32 MiB (16 instances)
L3 cache: 80 MiB (1 instance)
NUMA node(s): 1
NUMA node0 CPU(s): 0-15
Vulnerability Gather data sampling: Not affected
Vulnerability Indirect target selection: Not affected
Vulnerability Itlb multihit: Not affected
Vulnerability L1tf: Not affected
Vulnerability Mds: Not affected
Vulnerability Meltdown: Not affected
Vulnerability Mmio stale data: Not affected
Vulnerability Reg file data sampling: Not affected
Vulnerability Retbleed: Not affected
Vulnerability Spec rstack overflow: Not affected
Vulnerability Spec store bypass: Mitigation; Speculative Store Bypass disabled via prctl
Vulnerability Spectre v1: Mitigation; __user pointer sanitization
Vulnerability Spectre v2: Mitigation; CSV2, BHB
Vulnerability Srbds: Not affected
Vulnerability Tsa: Not affected
Vulnerability Tsx async abort: Not affected
Vulnerability Vmscape: Not affected

Comparing jsod/replace-children-07-25-26 (a2ccc1a) to 88365dd (merge-base) diff using: clickbench_partitioned
Results will be posted here when complete


File an issue against this benchmark runner

@adriangbot

Copy link
Copy Markdown

🤖 Benchmark running (GKE) | trigger
Instance:c4a-highmem-16 (12 vCPU / 65 GiB) | Linux bench-c5139175106-1319-xpfnf 6.12.85+ #1 SMP Mon May 11 08:17:35 UTC 2026 aarch64 GNU/Linux

CPU Details (lscpu)
Architecture: aarch64
CPU op-mode(s): 64-bit
Byte Order: Little Endian
CPU(s): 16
On-line CPU(s) list: 0-15
Vendor ID: ARM
Model name: Neoverse-V2
Model: 1
Thread(s) per core: 1
Core(s) per cluster: 16
Socket(s): -
Cluster(s): 1
Stepping: r0p1
BogoMIPS: 2000.00
Flags: fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm jscvt fcma lrcpc dcpop sha3 sm3 sm4 asimddp sha512 sve asimdfhm dit uscat ilrcpc flagm sb paca pacg dcpodp sve2 sveaes svepmull svebitperm svesha3 svesm4 flagm2 frint svei8mm svebf16 i8mm bf16 dgh rng bti
L1d cache: 1 MiB (16 instances)
L1i cache: 1 MiB (16 instances)
L2 cache: 32 MiB (16 instances)
L3 cache: 80 MiB (1 instance)
NUMA node(s): 1
NUMA node0 CPU(s): 0-15
Vulnerability Gather data sampling: Not affected
Vulnerability Indirect target selection: Not affected
Vulnerability Itlb multihit: Not affected
Vulnerability L1tf: Not affected
Vulnerability Mds: Not affected
Vulnerability Meltdown: Not affected
Vulnerability Mmio stale data: Not affected
Vulnerability Reg file data sampling: Not affected
Vulnerability Retbleed: Not affected
Vulnerability Spec rstack overflow: Not affected
Vulnerability Spec store bypass: Mitigation; Speculative Store Bypass disabled via prctl
Vulnerability Spectre v1: Mitigation; __user pointer sanitization
Vulnerability Spectre v2: Mitigation; CSV2, BHB
Vulnerability Srbds: Not affected
Vulnerability Tsa: Not affected
Vulnerability Tsx async abort: Not affected
Vulnerability Vmscape: Not affected

Comparing jsod/replace-children-07-25-26 (a2ccc1a) to 88365dd (merge-base) diff using: tpcds
Results will be posted here when complete


File an issue against this benchmark runner

@alamb

alamb commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

(the performance benchmarks also look good -- thanks for running those @zhuqi-lucas )

@zhuqi-lucaszhuqi-lucas 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.

LGTM now, thanks @JSOD11 , cc @askalt@alamb for double check

@zhuqi-lucas

Copy link
Copy Markdown
Contributor

run benchmark sql_planner

fn replace_children(
self: Arc<Self>,
children: Vec<Arc<dyn ExecutionPlan>>,
hint: ChildrenPropertiesHint,

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.

Would it make sense to pass a ReplaceChildrenHints structure here (that includes ChildrenPropertiesHint as a member)? That could make the API easier to extend in the future.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah this is a good point, see comment below.

fn reset_state(self: Arc<Self>) -> Result<Arc<dyn ExecutionPlan>> {
let children = self.children().into_iter().cloned().collect();
self.with_new_children(children)
self.replace_children(children, ChildrenPropertiesHint::Recompute)

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.

Why does reset_state require recomputing properties? It seems to me that resetting runtime state should not affect the plan properties.

For performance, it would be nice to avoid recomputing properties in reset_plan_states, especially for plans without an explicit reset_state override. For example, a simple filter and projection should not need to recompute anything when their state is reset.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call, agreed. Just pushed a commit swapping this to SameProperties.

/// A hint from `replace_children_if_necessary` to `replace_children` indicating
/// whether the properties of the new children must be recomputed.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ChildrenPropertiesHint {

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.

A "hint" suggests that its absence should not logically break anything. However, if children are not recomputed when required, it could lead to bugs.

Would ChildrenPropertiesRequirement be a better name?

@JSOD11JSOD11Aug 9, 2026

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a good point, how about a design like this to tie in with your comment above?

pub struct ReplaceChildrenOptions {
pub children_properties: ChildrenPropertiesMode,
}
pub enum ChildrenPropertiesMode {
SameProperties,
Recompute,
}

Which gives us something like this:

 fn replace_children(
self: Arc<Self>,
children: Vec<Arc<dyn ExecutionPlan>>,
options: ReplaceChildrenOptions,
) -> Result<Arc<dyn ExecutionPlan>> {
match options.children_properties {
ChildrenPropertiesMode::SameProperties => {
self.with_new_children_and_same_properties(children)
}
ChildrenPropertiesMode::Recompute => self.with_new_children(children),
}
}

Interested in hearing what everyone thinks. If we agree on this design, I'll go ahead and swap all the implementations and call sites. cc @askalt@zhuqi-lucas@alamb

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see a thumbs up, so I went ahead and pushed a new commit moving us to this shape. Looking forward to hearing all of your thoughts.

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.

looks good to me

@alamb

Copy link
Copy Markdown
Contributor

I merged up from main

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

Thank you @JSOD11@zhuqi-lucas and @askalt

I think this looks good to me.

I took the liberty of pushing some additional documentation changes to the ExecutionPlan trait to make it clearer what was going on here and what users should do.

the properties do not match the children, `ChildrenPropertiesMode::Recompute`,
follow the body of `with_new_children`.

For example, take a look at the implementation for `FilterExec`:

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.

👍

/// A hint from `replace_children_if_necessary` to `replace_children` indicating
/// whether the properties of the new children must be recomputed.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ChildrenPropertiesHint {

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.

looks good to me

}

#[deprecated(since = "55.0.0", note = "Use `replace_children_if_necessary`")]
pub fn with_new_children_if_necessary(

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.

💯 for keeping the old stub

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

Overall LGTM! Thank you!

Comment on lines +479 to +481
ReplaceChildrenOptions {
children_properties: ChildrenPropertiesMode::SameProperties,
},

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.

nit: we can add a constructor to reduce this a bit, e.g.

ReplaceChildrenOptions::new(ChildrenPropertiesMode::SameProperties)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good to me, added.

pub enum ChildrenPropertiesMode {
/// The plan properties of the new children are identical to the properties
/// of the existing children, so we can skip recomputation.
SameProperties,

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.

nit: to be consistent with the second variant:

Suggested change
SameProperties,
Keep,

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this name, matches the other variant better. Just changed.

@JSOD11

Copy link
Copy Markdown
ContributorAuthor

Friendly bump on this one before finalizing the release, think all that's left is to hit the merge button.

cc @alamb@zhuqi-lucas

@alamb

Copy link
Copy Markdown
Contributor

I think this one missed the 55 branch cut: #22393 (comment)

We can potentially open a proposed backport on the branch-55 line

It would be good to put a note on #22393 to coordinate with @timsaucer

@timsaucer

Copy link
Copy Markdown
Member

I need to make another RC anyways, so after you merge this into main if you make a PR targeting branch-55 we should be able to include it.

@alamb

Copy link
Copy Markdown
Contributor

Merging to main@

@alamb
alamb added this pull request to the merge queueAug 12, 2026
Merged via the queue into apache:main with commit caa3cb4Aug 12, 2026
41 checks passed
alamb added a commit that referenced this pull request Aug 12, 2026
…lan` API with `replace_children` (#24296)
Backport of #23903 to
`branch-55`.
Co-authored-by: Andrew Lamb <andrew@nerdnetworks.org>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

auto detected api changeAuto detected API changecatalogRelated 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.

refactor(physical-plan): simplify ExecutionPlan children-replacement API

7 participants

@JSOD11@codecov-commenter@zhuqi-lucas@adriangbot@alamb@timsaucer@askalt