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


##########
fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/SimplifyAggGroupBy.java:
##########
@@ -19,118 +19,54 @@
 
 import org.apache.doris.nereids.rules.Rule;
 import org.apache.doris.nereids.rules.RuleType;
-import org.apache.doris.nereids.trees.expressions.Add;
-import org.apache.doris.nereids.trees.expressions.BinaryArithmetic;
-import org.apache.doris.nereids.trees.expressions.Cast;
-import org.apache.doris.nereids.trees.expressions.Divide;
 import org.apache.doris.nereids.trees.expressions.Expression;
-import org.apache.doris.nereids.trees.expressions.Multiply;
 import org.apache.doris.nereids.trees.expressions.Slot;
-import org.apache.doris.nereids.trees.expressions.Subtract;
-import org.apache.doris.nereids.trees.expressions.literal.Literal;
-import org.apache.doris.nereids.util.ExpressionUtils;
-import org.apache.doris.nereids.util.Utils;
 
 import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Set;
 
 /**
- * Simplify Aggregate group by Multiple to One. For example
+ * Remove deterministic grouping expressions whose inputs are already bare 
grouping slots.
  * <p>
- * GROUP BY ClientIP, ClientIP - 1, ClientIP - 2, ClientIP - 3
+ * GROUP BY ClientIP, ClientIP + 1, ClientIP + 2
  * -->
  * GROUP BY ClientIP
+ *
+ * <p>Retain existing bare slots so aggregate outputs can still reference 
them. A cast group key
+ * cannot generally provide its original slot to those outputs. Never 
synthesize a slot from
+ * derived keys, since doing so can split groups formed by non-injective 
expressions.</p>
  */
 public class SimplifyAggGroupBy extends OneRewriteRuleFactory {
-    private static final ImmutableSet<Class<? extends Expression>> 
supportedFunctions
-            = ImmutableSet.of(Add.class, Subtract.class, Multiply.class, 
Divide.class);
-
     @Override
     public Rule build() {
         return logicalAggregate()
-                .when(agg -> agg.getGroupByExpressions().size() > 1
-                        && 
ExpressionUtils.allMatch(agg.getGroupByExpressions(),
-                        SimplifyAggGroupBy::isBinaryArithmeticSlot))
+                .when(agg -> agg.getGroupByExpressions().size() > 1)
                 .then(agg -> {
-                    List<Expression> groupByExpressions = 
agg.getGroupByExpressions();
-                    ImmutableSet.Builder<Expression> inputSlots
-                            = 
ImmutableSet.builderWithExpectedSize(groupByExpressions.size());
-                    for (Expression groupByExpression : groupByExpressions) {
-                        inputSlots.addAll(groupByExpression.getInputSlots());
-                    }
-                    Set<Expression> slots = inputSlots.build();
-                    if (slots.size() != 1) {
+                    List<Expression> simplified = 
simplifyGroupBy(agg.getGroupByExpressions());
+                    if (simplified == null) {
                         return null;
                     }
-                    return 
agg.withGroupByAndOutput(Utils.fastToImmutableList(slots), 
agg.getOutputExpressions());
+                    return agg.withGroupByAndOutput(simplified, 
agg.getOutputExpressions());
                 })
                 .toRule(RuleType.SIMPLIFY_AGG_GROUP_BY);
     }
 
     @VisibleForTesting
-    protected static boolean isBinaryArithmeticSlot(Expression expr) {
-        if (expr instanceof Slot) {
-            return true;
-        }
-        if (!(expr instanceof BinaryArithmetic)) {
-            return false;
-        }
-        if (!supportedFunctions.contains(expr.getClass())) {
-            return false;
-        }
-
-        // Float/double arithmetic: precision loss for all operations
-        if (expr.child(0).getDataType().isFloatLikeType()
-                || expr.child(1).getDataType().isFloatLikeType()) {
-            return false;
+    protected static List<Expression> simplifyGroupBy(List<Expression> 
groupByExpressions) {
+        Set<Expression> distinctGroupBy = new 
LinkedHashSet<>(groupByExpressions);
+        Set<Expression> determinants = distinctGroupBy.stream()
+                
.filter(Slot.class::isInstance).collect(ImmutableSet.toImmutableSet());
+        // Keep at least one key: removing all constant keys changes the 
result for empty input.
+        if (!determinants.isEmpty()) {
+            distinctGroupBy.removeIf(expression -> !(expression instanceof 
Slot)
+                    && !expression.containsVolatileExpression()

Review Comment:
   [P1] Preserve keys with observable evaluation or validation
   
   Consider the analyzed tree `Aggregate(count(*), groupBy=[x, assert_true(x > 
0, 'bad')]) -> Scan(x)`. This predicate removes `assert_true` because it is 
non-volatile and its input is the bare key `x`, producing `Aggregate(count(*), 
groupBy=[x]) -> Scan(x)` and suppressing the required `InvalidArgument` for a 
row with `x <= 0`. Before this change, normalization materialized the assertion 
below the aggregate, where `LogicalProject.pruneOutputs` deliberately preserves 
`NoneMovableFunction` evaluation even if later FD elimination drops its key.
   
   The same check also erases `score()` from `GROUP BY id, score()`: `score()` 
is non-deterministic but non-volatile and has no input slots, so the later 
`CheckScoreUsage` validator can no longer reject the invalid aggregate use. 
Please fence both non-movable and non-deterministic expressions (using the 
existing combined non-movable helper where applicable) and add negative 
coverage for both cases.



##########
fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/SimplifyAggGroupBy.java:
##########
@@ -19,118 +19,54 @@
 
 import org.apache.doris.nereids.rules.Rule;
 import org.apache.doris.nereids.rules.RuleType;
-import org.apache.doris.nereids.trees.expressions.Add;
-import org.apache.doris.nereids.trees.expressions.BinaryArithmetic;
-import org.apache.doris.nereids.trees.expressions.Cast;
-import org.apache.doris.nereids.trees.expressions.Divide;
 import org.apache.doris.nereids.trees.expressions.Expression;
-import org.apache.doris.nereids.trees.expressions.Multiply;
 import org.apache.doris.nereids.trees.expressions.Slot;
-import org.apache.doris.nereids.trees.expressions.Subtract;
-import org.apache.doris.nereids.trees.expressions.literal.Literal;
-import org.apache.doris.nereids.util.ExpressionUtils;
-import org.apache.doris.nereids.util.Utils;
 
 import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Set;
 
 /**
- * Simplify Aggregate group by Multiple to One. For example
+ * Remove deterministic grouping expressions whose inputs are already bare 
grouping slots.
  * <p>
- * GROUP BY ClientIP, ClientIP - 1, ClientIP - 2, ClientIP - 3
+ * GROUP BY ClientIP, ClientIP + 1, ClientIP + 2
  * -->
  * GROUP BY ClientIP
+ *
+ * <p>Retain existing bare slots so aggregate outputs can still reference 
them. A cast group key
+ * cannot generally provide its original slot to those outputs. Never 
synthesize a slot from
+ * derived keys, since doing so can split groups formed by non-injective 
expressions.</p>
  */
 public class SimplifyAggGroupBy extends OneRewriteRuleFactory {
-    private static final ImmutableSet<Class<? extends Expression>> 
supportedFunctions
-            = ImmutableSet.of(Add.class, Subtract.class, Multiply.class, 
Divide.class);
-
     @Override
     public Rule build() {
         return logicalAggregate()
-                .when(agg -> agg.getGroupByExpressions().size() > 1
-                        && 
ExpressionUtils.allMatch(agg.getGroupByExpressions(),
-                        SimplifyAggGroupBy::isBinaryArithmeticSlot))
+                .when(agg -> agg.getGroupByExpressions().size() > 1)
                 .then(agg -> {
-                    List<Expression> groupByExpressions = 
agg.getGroupByExpressions();
-                    ImmutableSet.Builder<Expression> inputSlots
-                            = 
ImmutableSet.builderWithExpectedSize(groupByExpressions.size());
-                    for (Expression groupByExpression : groupByExpressions) {
-                        inputSlots.addAll(groupByExpression.getInputSlots());
-                    }
-                    Set<Expression> slots = inputSlots.build();
-                    if (slots.size() != 1) {
+                    List<Expression> simplified = 
simplifyGroupBy(agg.getGroupByExpressions());
+                    if (simplified == null) {
                         return null;
                     }
-                    return 
agg.withGroupByAndOutput(Utils.fastToImmutableList(slots), 
agg.getOutputExpressions());
+                    return agg.withGroupByAndOutput(simplified, 
agg.getOutputExpressions());
                 })
                 .toRule(RuleType.SIMPLIFY_AGG_GROUP_BY);
     }
 
     @VisibleForTesting
-    protected static boolean isBinaryArithmeticSlot(Expression expr) {
-        if (expr instanceof Slot) {
-            return true;
-        }
-        if (!(expr instanceof BinaryArithmetic)) {
-            return false;
-        }
-        if (!supportedFunctions.contains(expr.getClass())) {
-            return false;
-        }
-
-        // Float/double arithmetic: precision loss for all operations
-        if (expr.child(0).getDataType().isFloatLikeType()
-                || expr.child(1).getDataType().isFloatLikeType()) {
-            return false;
+    protected static List<Expression> simplifyGroupBy(List<Expression> 
groupByExpressions) {
+        Set<Expression> distinctGroupBy = new 
LinkedHashSet<>(groupByExpressions);
+        Set<Expression> determinants = distinctGroupBy.stream()
+                
.filter(Slot.class::isInstance).collect(ImmutableSet.toImmutableSet());
+        // Keep at least one key: removing all constant keys changes the 
result for empty input.
+        if (!determinants.isEmpty()) {
+            distinctGroupBy.removeIf(expression -> !(expression instanceof 
Slot)
+                    && !expression.containsVolatileExpression()
+                    && determinants.containsAll(expression.getInputSlots()));

Review Comment:
   [P1] Require dependency under grouping equality
   
   With DOUBLE rows `x = +0.0` and `x = -0.0` (and `ELIMINATE_GROUP_BY_KEY` 
disabled), `GROUP BY x, signbit(x)` must retain two composite keys: 
`NormalizeAggregate` computes `signbit(x)` in the lower project while the signs 
are still observable, then the aggregation sink canonicalizes the `x` key, 
yielding `(+0,false)` and `(+0,true)`. This rule removes `signbit(x)` solely 
because its input slot is `x`; the sink then canonicalizes both remaining `x` 
values to `+0`, changing two count-1 groups into one count-2 group. The 
previous allowlist did not match `signbit`.
   
   Slot containment proves dependence on the input representation, not 
constancy over Doris's grouping-equality classes. Please restrict removal to 
expression/type families proven congruent with grouping equality (or retain a 
conservative allowlist) and cover signed zero in a regression.



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