Skip to content

fix: preserve can_repartition in WindowAggExec::replace_children (#26626) - #26675

Open
XiaoMingFa wants to merge 3 commits into
apache:mainfrom
XiaoMingFa:fix/window-agg-exec-can-repartition
Open

XiaoMingFa wants to merge 3 commits into
apache:mainfrom
XiaoMingFa:fix/window-agg-exec-can-repartition

Conversation

@XiaoMingFa

Copy link
Copy Markdown

Which issue does this PR close?

Rationale for this change

When datafusion.execution.target_partitions = 1, the physical planner constructs WindowAggExec with can_repartition = false so that the window stage requires SinglePartition distribution and inserts a CoalescePartitionsExec.

During physical optimization, replacing children via replace_children under ChildrenPropertiesMode::Recompute rebuilt WindowAggExec with hardcoded can_repartition = true instead of propagating self.can_repartition. This flipped the distribution requirement from SinglePartition to KeyPartitioned, stripping the required coalesce and triggering a SanityCheckPlan distribution error.

BoundedWindowAggExec already correctly propagates self.can_repartition during replace_children. This change aligns WindowAggExec with BoundedWindowAggExec.

What changes are included in this PR?

  • Pass self.can_repartition instead of true in WindowAggExec::replace_children when ChildrenPropertiesMode::Recompute is used.
  • Added unit test test_window_agg_replace_children_preserves_can_repartition in window_agg_exec.rs verifying preservation for both false and true.
  • Added sqllogictest regression test in window.slt verifying query execution with target_partitions = 1.

Are these changes tested?

Yes:

  • Local unit test test_window_agg_replace_children_preserves_can_repartition passes.
  • Integration test in datafusion/sqllogictest/test_files/window.slt passes.

Are there any user-facing changes?

No.

…che#26626)

## Which issue does this PR close?

- Closes apache#26626.

## Rationale for this change

When `datafusion.execution.target_partitions = 1`, the physical planner constructs `WindowAggExec` with `can_repartition = false` so that the window stage requires `SinglePartition` distribution and inserts a `CoalescePartitionsExec`.

During physical optimization, replacing children via `replace_children` under `ChildrenPropertiesMode::Recompute` rebuilt `WindowAggExec` with hardcoded `can_repartition = true` instead of propagating `self.can_repartition`. This flipped the distribution requirement from `SinglePartition` to `KeyPartitioned`, stripping the required coalesce and triggering a `SanityCheckPlan` distribution error.

`BoundedWindowAggExec` already correctly propagates `self.can_repartition` during `replace_children`. This change aligns `WindowAggExec` with `BoundedWindowAggExec`.

## What changes are included in this PR?

- Pass `self.can_repartition` instead of `true` in `WindowAggExec::replace_children` when `ChildrenPropertiesMode::Recompute` is used.
- Added unit test `test_window_agg_replace_children_preserves_can_repartition` in `window_agg_exec.rs` verifying preservation for both `false` and `true`.
- Added sqllogictest regression test in `window.slt` verifying query execution with `target_partitions = 1`.

## Are these changes tested?

Yes:
- Local unit test `test_window_agg_replace_children_preserves_can_repartition` passed.
- Integration test added to `window.slt`.
@github-actions github-actions Bot added sqllogictest SQL Logic Tests (.slt) physical-plan Changes to the physical-plan crate labels Oct 23, 2026

@harsh-ande harsh-ande 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 @XiaoMingFa! I confirmed the bug on main: the new window.slt test case fails with SanityCheckPlan at target_partitions = 1. The fix makes WindowAggExec::replace_children keep self.can_repartition, similar to how BoundedWindowAggExec already does.

Also added another NIT comment.

Comment on lines +706 to +734
// can_repartition = false should be preserved after replace_children with Recompute
let window = Arc::new(WindowAggExec::try_new(
vec![window_expr.clone()],
Arc::clone(&input),
false,
)?);
assert!(!window.can_repartition);

let new_child: Arc<dyn ExecutionPlan> =
Arc::new(TestMemoryExec::try_new(&[], Arc::clone(&schema), None)?);
let replaced = window.clone().replace_children(
vec![new_child],
ReplaceChildrenOptions::new(ChildrenPropertiesMode::Recompute),
)?;
let replaced_window = replaced.downcast_ref::<WindowAggExec>().unwrap();
assert_eq!(replaced_window.can_repartition, false);

// can_repartition = true should also be preserved
let window_true =
Arc::new(WindowAggExec::try_new(vec![window_expr], input, true)?);
assert!(window_true.can_repartition);
let new_child2: Arc<dyn ExecutionPlan> =
Arc::new(TestMemoryExec::try_new(&[], Arc::clone(&schema), None)?);
let replaced_true = window_true.clone().replace_children(
vec![new_child2],
ReplaceChildrenOptions::new(ChildrenPropertiesMode::Recompute),
)?;
let replaced_window_true = replaced_true.downcast_ref::<WindowAggExec>().unwrap();
assert_eq!(replaced_window_true.can_repartition, true);

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: These 2 blocks of code differ only in the flag. So a for can_repartition in [false, true] loop would enhance readability and shorten the test.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Updated to iterate over [false, true] as suggested! Much cleaner and eliminates the duplicate block. Thanks @harsh-ande!

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

Labels

physical-plan Changes to the physical-plan crate sqllogictest SQL Logic Tests (.slt)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SanityCheckPlan error for two window functions with target_partitions = 1 (WindowAggExec loses can_repartition when its children are replaced)

2 participants