From 4c6cfaee9bc076a399789e5fa5fca211e71469ff Mon Sep 17 00:00:00 2001
From: englefly
Date: Thu, 25 Jun 2026 16:11:34 +0800
Subject: [PATCH 01/23] feat: eliminate FD-redundant group-by keys via
ANY_VALUE wrapping
When a group-by key is functionally dependent on another key
(e.g. s_suppkey -> s_name via PK) but required in output,
remove it from GROUP BY and wrap with ANY_VALUE().
Previously EliminateGroupByKey kept such keys in GROUP BY to
preserve SQL semantics. Now they are replaced with ANY_VALUE
wrappers in the output, allowing the group-by set to be
minimized while keeping the column in SELECT.
Public findCanBeRemovedExpressions() API preserved for backward
compatibility. Internal logic split into FindResult with separate
removeExpression and wrapWithAnyValue sets.
Test: testEliminateByPkWithOutputNeeded verifies ANY_VALUE wrapping
when SELECT contains an FD-redundant group-by key.
---
.../doris/nereids/jobs/executor/Rewriter.java | 2 +-
.../doris/nereids/properties/FuncDeps.java | 5 +-
.../rules/rewrite/EliminateGroupByKey.java | 198 +++++++++++++-----
.../rewrite/EliminateGroupByKeyTest.java | 25 ++-
4 files changed, 178 insertions(+), 52 deletions(-)
diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/jobs/executor/Rewriter.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/jobs/executor/Rewriter.java
index e651a9e8fae583..4aa05cceda10cd 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/nereids/jobs/executor/Rewriter.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/jobs/executor/Rewriter.java
@@ -678,7 +678,7 @@ public class Rewriter extends AbstractBatchJobExecutor {
cascadesContext -> cascadesContext.rewritePlanContainsTypes(LogicalAggregate.class)
|| cascadesContext.rewritePlanContainsTypes(LogicalJoin.class)
|| cascadesContext.rewritePlanContainsTypes(LogicalUnion.class),
- topDown(new EliminateGroupByKey()),
+ custom(RuleType.ELIMINATE_GROUP_BY_KEY, EliminateGroupByKey::new),
topDown(new PushDownAggThroughJoinOnPkFk()),
topDown(new PullUpJoinFromUnionAll())
),
diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/properties/FuncDeps.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/properties/FuncDeps.java
index 879b2de9fe6468..3553b2deb8a89f 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/nereids/properties/FuncDeps.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/properties/FuncDeps.java
@@ -146,7 +146,7 @@ private Set findValidItems(Set requireOutputs) {
* Given:
* - Initial slots: {{A}, {B}, {C}, {D}, {E}}
* - Required outputs: {}
- * - validItems: {A} -> {B}, {B} -> {C}, {C} -> {D}, {D} -> {A}, {A} -> {E}
+ * - validItems: {A} -> {B}, {B} -> {C}, {C} -> {D}, {D} -> {E}, {A} -> {E}
*
* Process:
* 1. Start with minSlotSet = {{A}, {B}, {C}, {D}, {E}}
@@ -163,7 +163,8 @@ private Set findValidItems(Set requireOutputs) {
*
*
* @param slots the initial set of slot sets to be reduced
- * @param requireOutputs the set of slots that must be preserved in the output
+ * @param requireOutputs output-required slots; used in circular-dependency
+ * resolution to avoid eliminating FD edges that originate from these slots
* @return the minimal set of slot sets after applying all possible reductions
*/
public Set> eliminateDeps(Set> slots, Set requireOutputs) {
diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/EliminateGroupByKey.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/EliminateGroupByKey.java
index 4e1b3117ab53ff..f6200012fdc102 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/EliminateGroupByKey.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/EliminateGroupByKey.java
@@ -17,18 +17,22 @@
package org.apache.doris.nereids.rules.rewrite;
-import org.apache.doris.nereids.annotation.DependsRules;
+import org.apache.doris.nereids.jobs.JobContext;
import org.apache.doris.nereids.properties.DataTrait;
import org.apache.doris.nereids.properties.FuncDeps;
-import org.apache.doris.nereids.rules.Rule;
-import org.apache.doris.nereids.rules.RuleType;
+import org.apache.doris.nereids.trees.expressions.Alias;
+import org.apache.doris.nereids.trees.expressions.ExprId;
import org.apache.doris.nereids.trees.expressions.Expression;
import org.apache.doris.nereids.trees.expressions.NamedExpression;
import org.apache.doris.nereids.trees.expressions.Slot;
+import org.apache.doris.nereids.trees.expressions.functions.agg.AnyValue;
import org.apache.doris.nereids.trees.plans.Plan;
+import org.apache.doris.nereids.trees.plans.algebra.Aggregate;
import org.apache.doris.nereids.trees.plans.logical.LogicalAggregate;
-
-import com.google.common.collect.ImmutableList;
+import org.apache.doris.nereids.trees.plans.logical.LogicalFilter;
+import org.apache.doris.nereids.trees.plans.logical.LogicalProject;
+import org.apache.doris.nereids.trees.plans.visitor.CustomRewriter;
+import org.apache.doris.nereids.trees.plans.visitor.DefaultPlanRewriter;
import java.util.ArrayList;
import java.util.HashMap;
@@ -38,69 +42,162 @@
import java.util.Map.Entry;
import java.util.Set;
-
/**
* Eliminate group by key based on fd item information.
* such as:
* for a -> b, we can get:
* group by a, b, c => group by a, c
+ *
+ * When a group-by key is FD-redundant but still needed in the output,
+ * it is wrapped with any_value() and assigned a fresh ExprId.
+ * Upper plan references are rewritten via ExprIdRewriter so that
+ * all ancestor nodes see the new ExprIds.
*/
-@DependsRules({EliminateGroupBy.class, ColumnPruning.class})
-public class EliminateGroupByKey implements RewriteRuleFactory {
+public class EliminateGroupByKey extends DefaultPlanRewriter