github-actions[bot] commented on code in PR #66570:
URL: https://github.com/apache/doris/pull/66570#discussion_r3746550178


##########
fe/fe-core/src/test/java/org/apache/doris/qe/LocalExchangePlannerTest.java:
##########
@@ -163,6 +163,61 @@ public void testAggWithoutKeyTwoPhase() throws Exception {
                                                         olapScan("t1")))))));
     }
 
+    @Test
+    public void testCountDistinctNoGroupByRequiresHashBeforeAgg() throws 
Exception {
+        // count(distinct k2) without group-by: the finalize merge agg emits 
per-instance
+        // scalar values that the parent sums 
(sum0(multi_distinct_count(...))), so its
+        // input must be hash-partitioned by the distinct key. With the 
broadcast-join
+        // probe forced to PASSTHROUGH, rows are scattered below the join — 
the agg must
+        // still get a LOCAL_HASH exchange directly beneath it. Pre-fix this 
agg received
+        // NoRequire (no hash LE) and the parent double-counted overlapping 
keys.
+        // agg_phase=1 forces the multi_distinct_count two-phase shape (the 
default
+        // strategy splits into a group-by + count shape which is already 
safe).
+        setupLocalShuffleSession(sv -> {
+            sv.enableBroadcastJoinForcePassthrough = true;
+            sv.aggPhase = 1;
+        });
+        assertHasLocalExchangeOfType("select count(distinct a.k2) from test.t1 
a "

Review Comment:
   [P2] Assert the distinct key, not just the HASH type
   
   assertHasLocalExchangeOfType flattens every local exchange in every fragment 
to an enum set, discarding both its plan-tree location and distributeExprLists. 
The correctness invariant here is that the exchange directly below the scalar 
multi_distinct_count finalize aggregate is keyed by a.k2; a keyless or 
wrong-key LOCAL_EXECUTION_HASH_SHUFFLE still passes this assertion (and the new 
mock fixture currently accepts a zero-key HASH node). Please use the existing 
structural plan-shape traversal, or an equivalent targeted walk, to assert that 
aggregate-to-exchange edge and its partition expressions.



##########
fe/fe-core/src/test/java/org/apache/doris/qe/LocalExchangePlannerTest.java:
##########
@@ -163,6 +163,61 @@ public void testAggWithoutKeyTwoPhase() throws Exception {
                                                         olapScan("t1")))))));
     }
 
+    @Test
+    public void testCountDistinctNoGroupByRequiresHashBeforeAgg() throws 
Exception {
+        // count(distinct k2) without group-by: the finalize merge agg emits 
per-instance
+        // scalar values that the parent sums 
(sum0(multi_distinct_count(...))), so its
+        // input must be hash-partitioned by the distinct key. With the 
broadcast-join
+        // probe forced to PASSTHROUGH, rows are scattered below the join — 
the agg must
+        // still get a LOCAL_HASH exchange directly beneath it. Pre-fix this 
agg received
+        // NoRequire (no hash LE) and the parent double-counted overlapping 
keys.
+        // agg_phase=1 forces the multi_distinct_count two-phase shape (the 
default
+        // strategy splits into a group-by + count shape which is already 
safe).
+        setupLocalShuffleSession(sv -> {
+            sv.enableBroadcastJoinForcePassthrough = true;
+            sv.aggPhase = 1;
+        });
+        assertHasLocalExchangeOfType("select count(distinct a.k2) from test.t1 
a "
+                        + "left join [shuffle] test.t2 b on a.k2 = b.k2 "
+                        + "left join [broadcast] test.t2 c on b.k1 = c.k1",
+                LocalExchangeType.LOCAL_EXECUTION_HASH_SHUFFLE);
+
+    }
+
+    @Test
+    public void 
testCountDistinctNoGroupByRequiresHashWithoutForcePassthrough() throws 
Exception {
+        // Same as above without the broadcast-join force-passthrough: the 
join output is
+        // hash-partitioned by the probe key, and the satisfy() check lets the 
agg keep
+        // that distribution without inserting a redundant LE — the hash 
demand must
+        // still be recognized (a plain hash join probe output satisfies it).
+        setupLocalShuffleSession(null);

Review Comment:
   [P2] Configure and verify the no-force scenario explicitly
   
   This call resets neither aggPhase nor enableBroadcastJoinForcePassthrough. 
Because the test class keeps one ConnectContext, it may inherit aggPhase=1 and 
force-passthrough=true from the other new test; in a clean session it instead 
uses the default aggregate strategy that the preceding comment says is already 
safe. In either case, anyTree(agg()) is true for every successful 
count(distinct ...) plan and cannot show that a compatible child hash avoided a 
redundant LE. Set aggPhase=1 and force-passthrough=false here (and reset shared 
state in setup), then assert the claimed aggregate-child distribution.



##########
fe/fe-core/src/main/java/org/apache/doris/planner/AggregationNode.java:
##########
@@ -335,7 +335,13 @@ public Pair<PlanNode, LocalExchangeType> 
enforceAndDeriveLocalExchange(
             // early return also catches FIRST_MERGE, dropping the HASH 
requirement and
             // causing wrong-result (e.g. PASSTHROUGH over serial child breaks 
the
             // group-by-key invariant — DORIS-25413).
-            if (!hasKeys) {
+            if (!hasPartitionRequirement()) {

Review Comment:
   [P1] Do not create a zero-key HASH exchange for direct multi-distinct calls
   
   This predicate also matches a directly called scalar 
multi_distinct_count(col). That function has isDistinct=false, so 
SplitAggWithoutDistinct builds a two-phase LOCAL aggregate with no 
partitionExpressions; over a non-hash scan both the child distribution list and 
grouping list are empty. With the default LE setting this branch now reaches 
requireHash(), and getLocalExchangeDistributeExprs() supplies zero expressions. 
BE's hash partitioner initializes every row to hash 0, so the resulting 
FE-planned HASH exchange sends the whole input to channel 0, collapsing the 
partial aggregate to one task per BE and concentrating its distinct state in 
one task. BE's own AggSinkOperatorX sees empty _partition_exprs on this path 
and uses the base requirement instead. Please base the LOCAL decision on a 
non-empty effective key (while retaining the keyed finalize fix), and add a 
direct-function plan case that rejects a zero-expression HASH exchange.



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