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


##########
fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/SetPreAggStatus.java:
##########
@@ -402,6 +407,10 @@ private PreAggStatus 
checkAggWithKeyAndValueSlots(AggregateFunction aggFunc,
                 // currently, only IF and CASE WHEN are supported
                 returnExps.add(removeCast(child));
             }
+            if (conditionExps.isEmpty()) {

Review Comment:
   [P1] Validate every DISTINCT argument before using the unary helper
   
   This guard does not reject a legal variadic shape when the first child is 
`IF`/`CASE`:
   
   ```text
   LogicalAggregate(count(DISTINCT if(k1 > 0, k2, 0), v7))
     LogicalOlapScan(preagg_t1 AGG_KEYS)
   ```
   
   `splitKeyValueSlots` sees keys plus `v7`, but `checkAggWithKeyAndValueSlots` 
reads only `child(0)`. The `IF` supplies a nonempty key-only condition and 
key/zero returns, so `visitCount` returns ON without ever checking the later 
`v7`. With partial rows for one full key carrying `v7=1` and `v7=2`, ON exposes 
two distinct tuples, whereas OFF merges them to the logical `v7=3` tuple and 
returns one. Please keep this helper unary (or validate every argument 
independently) and add IF/CASE-first negative cases with a later value column.



##########
fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/SetPreAggStatus.java:
##########
@@ -326,28 +326,33 @@ private PreAggStatus 
checkAggregateFunctions(Set<AggregateFunction> aggregateFun
             }
             PreAggStatus preAggStatus = PreAggStatus.on();
             for (AggregateFunction aggFunc : aggregateFuncs) {
-                if (aggFunc.children().isEmpty()) {
+                Set<Slot> aggSlots = aggFunc.getInputSlots();
+                if (aggSlots.isEmpty()) {
                     preAggStatus = PreAggStatus.off(
                             String.format("can't turn preAgg on for aggregate 
function %s", aggFunc));
-                } else if (aggFunc.children().size() == 1 && aggFunc.child(0) 
instanceof Slot) {
-                    Slot aggSlot = (Slot) aggFunc.child(0);
-                    if (aggSlot instanceof SlotReference
-                            && ((SlotReference) 
aggSlot).getOriginalColumn().isPresent()) {
-                        if (((SlotReference) 
aggSlot).getOriginalColumn().get().isKey()) {
-                            preAggStatus = 
OneKeySlotAggChecker.INSTANCE.check(aggFunc);
+                } else {
+                    Pair<Set<SlotReference>, Set<SlotReference>> splitSlots = 
splitKeyValueSlots(aggSlots);
+                    if (splitSlots.first.isEmpty()) {
+                        // only value slots
+                        if (aggFunc.children().size() == 1 && aggFunc.child(0) 
instanceof SlotReference) {
+                            SlotReference slotRef = (SlotReference) 
aggFunc.child(0);
+                            if (slotRef.getOriginalColumn().isPresent()) {
+                                preAggStatus = 
OneValueSlotAggChecker.INSTANCE.check(aggFunc,
+                                        
slotRef.getOriginalColumn().get().getAggregationType());
+                            } else {
+                                preAggStatus = PreAggStatus.off(
+                                        String.format("can't turn preAgg on 
for aggregate function %s", aggFunc));
+                            }
                         } else {
-                            preAggStatus = 
OneValueSlotAggChecker.INSTANCE.check(aggFunc,
-                                    ((SlotReference) 
aggSlot).getOriginalColumn().get().getAggregationType());
+                            preAggStatus = PreAggStatus.off(
+                                    String.format("can't turn preAgg on for 
aggregate function %s", aggFunc));
                         }
+                    } else if (splitSlots.second.isEmpty()) {
+                        // only key slots
+                        preAggStatus = 
KeySlotAggChecker.INSTANCE.check(aggFunc);

Review Comment:
   [P1] Keep volatile key expressions out of the key-only ON path
   
   The input-slot set is not enough to prove that the aggregate argument is 
stable:
   
   ```text
   LogicalAggregate(count(DISTINCT k6 + random()))
     LogicalOlapScan(preagg_t1 AGG_KEYS)
   ```
   
   Aggregate normalization may put the expression in a project, but 
`PreAggInfoContext` substitutes its producer back for classification, so 
`getInputSlots()` contains only `k6` and this branch reaches 
`KeySlotAggChecker`. Its generic DISTINCT handling returns ON without checking 
volatility, while the project still evaluates above the scan on whatever rows 
storage returns. ON may therefore evaluate `random()` once per partial row 
instead of once per fully merged logical row and change the distinct count; 
`max/min(k6 + random())` have the same issue. Please reject 
volatile/non-row-stable expressions before enabling this path and add negative 
EXPLAIN coverage.



##########
fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/SetPreAggStatus.java:
##########
@@ -326,28 +326,33 @@ private PreAggStatus 
checkAggregateFunctions(Set<AggregateFunction> aggregateFun
             }
             PreAggStatus preAggStatus = PreAggStatus.on();
             for (AggregateFunction aggFunc : aggregateFuncs) {
-                if (aggFunc.children().isEmpty()) {
+                Set<Slot> aggSlots = aggFunc.getInputSlots();
+                if (aggSlots.isEmpty()) {
                     preAggStatus = PreAggStatus.off(
                             String.format("can't turn preAgg on for aggregate 
function %s", aggFunc));
-                } else if (aggFunc.children().size() == 1 && aggFunc.child(0) 
instanceof Slot) {
-                    Slot aggSlot = (Slot) aggFunc.child(0);
-                    if (aggSlot instanceof SlotReference
-                            && ((SlotReference) 
aggSlot).getOriginalColumn().isPresent()) {
-                        if (((SlotReference) 
aggSlot).getOriginalColumn().get().isKey()) {
-                            preAggStatus = 
OneKeySlotAggChecker.INSTANCE.check(aggFunc);
+                } else {
+                    Pair<Set<SlotReference>, Set<SlotReference>> splitSlots = 
splitKeyValueSlots(aggSlots);
+                    if (splitSlots.first.isEmpty()) {
+                        // only value slots
+                        if (aggFunc.children().size() == 1 && aggFunc.child(0) 
instanceof SlotReference) {
+                            SlotReference slotRef = (SlotReference) 
aggFunc.child(0);
+                            if (slotRef.getOriginalColumn().isPresent()) {
+                                preAggStatus = 
OneValueSlotAggChecker.INSTANCE.check(aggFunc,
+                                        
slotRef.getOriginalColumn().get().getAggregationType());
+                            } else {
+                                preAggStatus = PreAggStatus.off(
+                                        String.format("can't turn preAgg on 
for aggregate function %s", aggFunc));
+                            }
                         } else {
-                            preAggStatus = 
OneValueSlotAggChecker.INSTANCE.check(aggFunc,
-                                    ((SlotReference) 
aggSlot).getOriginalColumn().get().getAggregationType());
+                            preAggStatus = PreAggStatus.off(
+                                    String.format("can't turn preAgg on for 
aggregate function %s", aggFunc));
                         }
+                    } else if (splitSlots.second.isEmpty()) {

Review Comment:
   [P1] Reject unclassified slots before taking the key-only branch
   
   `splitKeyValueSlots` can omit a computed alias, so `valueSlots.isEmpty()` 
does not prove that all inputs are keys. For example:
   
   ```text
   LogicalAggregate(count(DISTINCT a))
     Project(l.k1 + l.x AS a)
       Join(l.k1 = r.rk)
         Project(t1.k1, t1.v7 + 1 AS x) -> Scan(t1 AGG_KEYS)
         Project(abs(t2.k1) AS rk)       -> Scan(t2 AGG_KEYS)
   ```
   
   The join children share one `PreAggInfoContext`; the right project 
overwrites the left `x -> v7 + 1` map, and the upper project restores `k1 + x` 
without recovering `x`'s producer. Since computed `x` has no `OriginalColumn`, 
the aggregate reaches this branch with `k1` classified as key and `x` silently 
dropped, enabling ON and exposing partial `v7` values before the project. 
Please require every aggregate slot to be classified (for example, return OFF 
unless `aggSlots.size() == keySlots.size() + valueSlots.size()`). 
Scoping/composing project maps can additionally recover eligible ON cases. Add 
a two-project join regression using a derived value alias.



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