zhuqi-lucas commented on code in PR #23948:
URL: https://github.com/apache/datafusion/pull/23948#discussion_r3671288691


##########
datafusion/core/tests/physical_optimizer/ensure_requirements.rs:
##########
@@ -1252,3 +1267,112 @@ fn test_idempotent_union_projection_sort() {
 
     assert_idempotent(plan);
 }
+
+/// A `CollectLeft` `HashJoinExec` requires `Distribution::SinglePartition` on 
its build
+/// (left) child, so `EnsureRequirements` puts a `CoalescePartitionsExec` on 
top of a
+/// multi-partition build side. Its sort-parallelization phase must not take 
that coalesce
+/// back out again.
+///
+/// The phase descends into a node when *any* of its children is linked to a
+/// `CoalescePartitionsExec` below (`update_coalesce_ctx_children`), so a 
connected probe
+/// side is enough to reach the join, and the removal itself used to look only 
at
+/// `children[0]` without consulting the join's own distribution requirement. 
The result was
+/// a build side left multi-partition with nothing to re-enforce distribution 
afterwards,
+/// which `SanityCheckPlan` then rejected with "does not satisfy distribution 
requirements:
+/// SinglePartition".
+#[test]
+fn test_collect_left_join_keeps_build_side_coalesce() {

Review Comment:
   I verified this locally and I'm afraid these two unit tests don't actually 
cover the regression: with the fix commit reverted (and `cargo clean -p 
datafusion-physical-optimizer` to force a rebuild), **both tests still pass**. 
I then instrumented the pre-fix `children[0]`-coalesce-removal branch with an 
`eprintln!` probe — it never fires for these mock plans, so they never reach 
the buggy path at all. The snapshots are identical pre/post fix.
   
   (The sqllogictest is fine — on the reverted code it fails with exactly the 
reported `does not satisfy distribution requirements: SinglePartition` error, 
and passes with the fix.)
   
   My guess at the cause: by the time `parallelize_sorts` runs, the 
distribution phase has already restructured the probe side, so the coalesce 
link that makes the traversal descend into the join in the real reproducer 
isn't there in this mock shape. Could you either make these construct the 
pre-phase-3a plan shape directly (so the removal branch demonstrably fires — an 
`eprintln` probe on `main` is a quick way to confirm), or drop them and let the 
sqllogictest carry the regression? As-is they'd stay green if the bug were 
reintroduced.



##########
datafusion/physical-optimizer/src/ensure_requirements/enforce_sorting/mod.rs:
##########
@@ -627,9 +656,10 @@ fn remove_bottleneck_in_subplan(
         requirements.children = requirements
             .children
             .into_iter()
-            .map(|node| {
-                if node.data {
-                    remove_bottleneck_in_subplan(node)
+            .enumerate()
+            .map(|(idx, node)| {
+                if node.data && removable(idx) {

Review Comment:
   Worth a short comment that this is deliberately conservative: skipping the 
descent entirely also skips legitimate cleanups *below* the protected child 
(e.g. a redundant second coalesce underneath the load-bearing one). 
Correctness-first is the right call for a fix — just flagging that the gate 
could later be narrowed to "descend, but protect only the top-level coalesce" 
if anyone cares about the missed optimization.



##########
datafusion/physical-optimizer/src/ensure_requirements/enforce_sorting/mod.rs:
##########
@@ -609,11 +609,40 @@ fn adjust_window_sort_removal(
 /// the plan, some of the remaining `RepartitionExec`s might become 
unnecessary.
 /// Removes such `RepartitionExec`s from the plan as well.
 fn remove_bottleneck_in_subplan(
+    requirements: PlanWithCorrespondingCoalescePartitions,
+) -> Result<PlanWithCorrespondingCoalescePartitions> {
+    // The root is the node `parallelize_sorts` is rewriting (a `SortExec`,
+    // `SortPreservingMergeExec` or `CoalescePartitionsExec`). Its own 
distribution
+    // requirement does not constrain the removal, because the caller drops 
the node and
+    // rebuilds the cascade around the result.
+    remove_bottleneck_in_subplan_impl(requirements, true)
+}
+
+fn remove_bottleneck_in_subplan_impl(
     mut requirements: PlanWithCorrespondingCoalescePartitions,
+    is_root: bool,
 ) -> Result<PlanWithCorrespondingCoalescePartitions> {
     let plan = &requirements.plan;
+    // Below the root, a `CoalescePartitionsExec` feeding a child that requires
+    // `Distribution::SinglePartition` is not an avoidable bottleneck: it is 
what satisfies
+    // that requirement. Removing it leaves the parent with a multi-partition 
input it cannot
+    // accept, and nothing re-runs distribution enforcement afterwards, so the 
plan reaches
+    // `SanityCheckPlan` invalid. The traversal reaches such a node because
+    // `update_coalesce_ctx_children` marks a node as connected when *any* 
child qualifies:
+    // a `CollectLeft` `HashJoinExec` whose probe side is connected is 
descended into even
+    // though its build side must stay single-partition.
+    let removable = |idx: usize| {
+        is_root
+            || !matches!(
+                plan.input_distribution_requirements()
+                    .child_distribution(idx),
+                Some(Distribution::SinglePartition)
+            )

Review Comment:
   Question (non-blocking): should this also protect 
`Distribution::HashPartitioned` children? A single-partition input trivially 
satisfies a hash requirement, so a coalesce sitting under a hash-requiring 
child is load-bearing in the same way — removing it exposes whatever 
multi-partitioning is below, which may not be hashed on the right keys. I don't 
think `EnsureRequirements` itself ever places a coalesce there (hash 
requirements get a `RepartitionExec`), so it's probably unreachable via this 
rule's own output — but a plan built by other rules or by hand could hit it. 
Fine to leave as-is if you agree it's unreachable; a one-line comment saying so 
would help the next reader.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to