This is an automated email from the ASF dual-hosted git repository.

morrysnow pushed a commit to branch branch-3.1
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/branch-3.1 by this push:
     new 8a6c93d150f branch-3.1: [fix](nereids) stop merge project when 
generating huge expression #55293 (#55519)
8a6c93d150f is described below

commit 8a6c93d150f8d442cf5b03497d76daa10dd0fb15
Author: yujun <[email protected]>
AuthorDate: Thu Sep 4 14:38:59 2025 +0800

    branch-3.1: [fix](nereids) stop merge project when generating huge 
expression #55293 (#55519)
    
    cherry pick from #55293
---
 .../nereids/exceptions/AnalysisException.java      |  34 ++++++-
 .../doris/nereids/exceptions/ParseException.java   |   2 +-
 .../processor/post/MergeProjectPostProcessor.java  |  11 ++-
 .../rules/rewrite/DeferMaterializeTopNResult.java  |  14 ++-
 .../doris/nereids/rules/rewrite/MergeProjects.java |  11 ++-
 .../nereids/trees/expressions/Expression.java      |  10 +-
 .../doris/nereids/trees/plans/algebra/Project.java |  22 +++--
 .../apache/doris/nereids/util/ExpressionUtils.java |   4 +-
 .../org/apache/doris/nereids/util/PlanUtils.java   |  23 ++++-
 .../merge_project/test_merge_project.out           | Bin 0 -> 346 bytes
 .../merge_project/test_merge_project.groovy        | 105 +++++++++++++++++++++
 11 files changed, 203 insertions(+), 33 deletions(-)

diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/exceptions/AnalysisException.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/exceptions/AnalysisException.java
index 18f89725e2f..e8b5d142f8f 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/exceptions/AnalysisException.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/exceptions/AnalysisException.java
@@ -23,35 +23,48 @@ import java.util.Optional;
 
 /** Nereids's AnalysisException. */
 public class AnalysisException extends RuntimeException {
+    private final ErrorCode errorCode;
     private final String message;
     private final Optional<Integer> line;
     private final Optional<Integer> startPosition;
     private final Optional<LogicalPlan> plan;
 
-    public AnalysisException(String message, Throwable cause, 
Optional<Integer> line,
+    /** Constructor of AnalysisException. */
+    public AnalysisException(ErrorCode errorCode, String message, Throwable 
cause, Optional<Integer> line,
             Optional<Integer> startPosition, Optional<LogicalPlan> plan) {
         super(message, cause);
+        this.errorCode = errorCode;
         this.message = message;
         this.line = line;
         this.startPosition = startPosition;
         this.plan = plan;
     }
 
-    public AnalysisException(String message, Optional<Integer> line,
+    /** Constructor of AnalysisException. */
+    public AnalysisException(ErrorCode errorCode, String message, 
Optional<Integer> line,
             Optional<Integer> startPosition, Optional<LogicalPlan> plan) {
         super(message);
+        this.errorCode = errorCode;
         this.message = message;
         this.line = line;
         this.startPosition = startPosition;
         this.plan = plan;
     }
 
+    public AnalysisException(ErrorCode errorCode, String message, Throwable 
cause) {
+        this(errorCode, message, cause, Optional.empty(), Optional.empty(), 
Optional.empty());
+    }
+
+    public AnalysisException(ErrorCode errorCode, String message) {
+        this(errorCode, message, Optional.empty(), Optional.empty(), 
Optional.empty());
+    }
+
     public AnalysisException(String message, Throwable cause) {
-        this(message, cause, Optional.empty(), Optional.empty(), 
Optional.empty());
+        this(ErrorCode.NONE, message, cause);
     }
 
     public AnalysisException(String message) {
-        this(message, Optional.empty(), Optional.empty(), Optional.empty());
+        this(ErrorCode.NONE, message);
     }
 
     @Override
@@ -70,5 +83,16 @@ public class AnalysisException extends RuntimeException {
         }
     }
 
-    // TODO: support ErrorCode
+    /** get error code.
+     */
+    public ErrorCode getErrorCode() {
+        return errorCode;
+    }
+
+    /** error code enum.
+     */
+    public enum ErrorCode {
+        NONE,
+        EXPRESSION_EXCEEDS_LIMIT,
+    }
 }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/exceptions/ParseException.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/exceptions/ParseException.java
index 697af739f89..e3c72f1d6e7 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/exceptions/ParseException.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/exceptions/ParseException.java
@@ -37,7 +37,7 @@ public class ParseException extends AnalysisException {
     }
 
     public ParseException(String message, Origin start, Optional<String> 
command) {
-        super(message, start.line, start.startPosition, Optional.empty());
+        super(ErrorCode.NONE, message, start.line, start.startPosition, 
Optional.empty());
         this.message = message;
         this.start = start;
         this.command = command;
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/processor/post/MergeProjectPostProcessor.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/processor/post/MergeProjectPostProcessor.java
index 7466889fde6..b1f4284fb42 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/processor/post/MergeProjectPostProcessor.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/processor/post/MergeProjectPostProcessor.java
@@ -23,6 +23,7 @@ import org.apache.doris.nereids.trees.plans.Plan;
 import org.apache.doris.nereids.trees.plans.physical.PhysicalProject;
 
 import java.util.List;
+import java.util.Optional;
 
 /**
  * merge consecutive projects
@@ -34,10 +35,12 @@ public class MergeProjectPostProcessor extends 
PlanPostProcessor {
         project = (PhysicalProject<? extends Plan>) super.visit(project, ctx);
         Plan child = project.child();
         if (child instanceof PhysicalProject && 
project.canMergeProjections((PhysicalProject) child)) {
-            List<NamedExpression> projections = 
project.mergeProjections((PhysicalProject) child);
-            return (PhysicalProject) project
-                    .withProjectionsAndChild(projections, child.child(0))
-                    .copyStatsAndGroupIdFrom(project);
+            Optional<List<NamedExpression>> projections = 
project.mergeProjections((PhysicalProject) child);
+            if (projections.isPresent()) {
+                return (PhysicalProject) project
+                        .withProjectionsAndChild(projections.get(), 
child.child(0))
+                        .copyStatsAndGroupIdFrom(project);
+            }
         }
         return project;
     }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/DeferMaterializeTopNResult.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/DeferMaterializeTopNResult.java
index 765081ae016..18ff8bfeb2f 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/DeferMaterializeTopNResult.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/DeferMaterializeTopNResult.java
@@ -187,8 +187,11 @@ public class DeferMaterializeTopNResult implements 
RewriteRuleFactory {
                         ).when(project -> 
project.canMergeProjections(project.child().child()))).then(r -> {
                             LogicalProject<?> upperProject = r.child();
                             LogicalProject<LogicalOlapScan> bottomProject = 
r.child().child().child();
-                            List<NamedExpression> projections = 
upperProject.mergeProjections(bottomProject);
-                            LogicalProject<?> project = 
upperProject.withProjects(projections);
+                            Optional<List<NamedExpression>> projections = 
upperProject.mergeProjections(bottomProject);
+                            if (!projections.isPresent()) {
+                                return null;
+                            }
+                            LogicalProject<?> project = 
upperProject.withProjects(projections.get());
                             return deferMaterialize(r, r.child().child(), 
Optional.of(project),
                                     Optional.empty(), bottomProject.child());
                         })
@@ -246,8 +249,11 @@ public class DeferMaterializeTopNResult implements 
RewriteRuleFactory {
                         ).when(project -> 
project.canMergeProjections(project.child().child()))).then(r -> {
                             LogicalProject<?> upperProject = r.child();
                             LogicalProject<LogicalFilter<LogicalOlapScan>> 
bottomProject = r.child().child().child();
-                            List<NamedExpression> projections = 
upperProject.mergeProjections(bottomProject);
-                            LogicalProject<?> project = 
upperProject.withProjects(projections);
+                            Optional<List<NamedExpression>> projections = 
upperProject.mergeProjections(bottomProject);
+                            if (!projections.isPresent()) {
+                                return null;
+                            }
+                            LogicalProject<?> project = 
upperProject.withProjects(projections.get());
                             LogicalFilter<LogicalOlapScan> filter = 
bottomProject.child();
                             return deferMaterialize(r, r.child().child(), 
Optional.of(project),
                                     Optional.of(filter), filter.child());
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/MergeProjects.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/MergeProjects.java
index bb6ca154136..572ff88ea59 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/MergeProjects.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/MergeProjects.java
@@ -24,6 +24,7 @@ import org.apache.doris.nereids.trees.plans.Plan;
 import org.apache.doris.nereids.trees.plans.logical.LogicalProject;
 
 import java.util.List;
+import java.util.Optional;
 
 /**
  * this rule aims to merge consecutive project. For example:
@@ -43,13 +44,17 @@ public class MergeProjects extends OneRewriteRuleFactory {
         // here we just don't merge two projects if there is any window 
function
         return logicalProject(logicalProject())
                 .when(project -> project.canMergeProjections(project.child()))
-                
.then(MergeProjects::mergeProjects).toRule(RuleType.MERGE_PROJECTS);
+                .then(MergeProjects::mergeProjects)
+                .toRule(RuleType.MERGE_PROJECTS);
     }
 
     /** merge projects */
     public static Plan mergeProjects(LogicalProject<?> project) {
         LogicalProject<? extends Plan> childProject = (LogicalProject<?>) 
project.child();
-        List<NamedExpression> projectExpressions = 
project.mergeProjections(childProject);
-        return project.withProjectsAndChild(projectExpressions, 
childProject.child(0));
+        Optional<List<NamedExpression>> projectExpressions = 
project.mergeProjections(childProject);
+        if (!projectExpressions.isPresent()) {
+            return project;
+        }
+        return project.withProjectsAndChild(projectExpressions.get(), 
childProject.child(0));
     }
 }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/Expression.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/Expression.java
index 90cdfda818d..707bf05bb8e 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/Expression.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/Expression.java
@@ -21,6 +21,7 @@ import org.apache.doris.common.Config;
 import org.apache.doris.nereids.analyzer.Unbound;
 import org.apache.doris.nereids.analyzer.UnboundVariable;
 import org.apache.doris.nereids.exceptions.AnalysisException;
+import org.apache.doris.nereids.exceptions.AnalysisException.ErrorCode;
 import org.apache.doris.nereids.exceptions.UnboundException;
 import org.apache.doris.nereids.trees.AbstractTreeNode;
 import 
org.apache.doris.nereids.trees.expressions.ArrayItemReference.ArrayItemSlot;
@@ -179,12 +180,13 @@ public abstract class Expression extends 
AbstractTreeNode<Expression> implements
 
     private void checkLimit() {
         if (depth > Config.expr_depth_limit) {
-            throw new AnalysisException(String.format("Exceeded the maximum 
depth of an "
-                    + "expression tree (%s).", Config.expr_depth_limit));
+            throw new AnalysisException(ErrorCode.EXPRESSION_EXCEEDS_LIMIT,
+                    String.format("Exceeded the maximum depth of an expression 
tree (%s).", Config.expr_depth_limit));
         }
         if (width > Config.expr_children_limit) {
-            throw new AnalysisException(String.format("Exceeded the maximum 
children of an "
-                    + "expression tree (%s).", Config.expr_children_limit));
+            throw new AnalysisException(ErrorCode.EXPRESSION_EXCEEDS_LIMIT,
+                    String.format("Exceeded the maximum children of an 
expression tree (%s).",
+                            Config.expr_children_limit));
         }
     }
 
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/algebra/Project.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/algebra/Project.java
index 33b351ba4b3..eeacd02468d 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/algebra/Project.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/algebra/Project.java
@@ -26,12 +26,13 @@ import 
org.apache.doris.nereids.trees.expressions.functions.NoneMovableFunction;
 import org.apache.doris.nereids.util.ExpressionUtils;
 import org.apache.doris.nereids.util.PlanUtils;
 
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 
-import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 
 /**
  * Common interface for logical/physical project.
@@ -55,23 +56,30 @@ public interface Project {
     }
 
     /**
-     * combine upper level and bottom level projections
+     * combine upper level and bottom level projections.
+     * if the combined expressions too huge, will return empty.
      * 1. alias combination, for example
      * proj(x as y, b) --> proj(a as x, b, c) =>(a as y, b)
      * 2. remove used projection in bottom project
      * @param childProject bottom project
      * @return project list for merged project
      */
-    default List<NamedExpression> mergeProjections(Project childProject) {
-        List<NamedExpression> projects = new ArrayList<>();
-        projects.addAll(PlanUtils.mergeProjections(childProject.getProjects(), 
getProjects()));
+    default Optional<List<NamedExpression>> mergeProjections(Project 
childProject) {
+        Optional<List<NamedExpression>> parentProjectsOpt
+                = PlanUtils.tryMergeProjections(childProject.getProjects(), 
getProjects());
+        if (!parentProjectsOpt.isPresent()) {
+            return Optional.empty();
+        }
+        ImmutableList.Builder<NamedExpression> projectsBuilder
+                = 
ImmutableList.builderWithExpectedSize(parentProjectsOpt.get().size());
+        projectsBuilder.addAll(parentProjectsOpt.get());
         for (NamedExpression expression : childProject.getProjects()) {
             // keep NoneMovableFunction for later use
             if (expression.containsType(NoneMovableFunction.class)) {
-                projects.add(expression);
+                projectsBuilder.add(expression);
             }
         }
-        return projects;
+        return Optional.of(projectsBuilder.build());
     }
 
     /** check can merge two projects */
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/util/ExpressionUtils.java 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/util/ExpressionUtils.java
index 5c5a4e8d060..f05060d58a7 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/util/ExpressionUtils.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/util/ExpressionUtils.java
@@ -368,7 +368,7 @@ public class ExpressionUtils {
     /**
      * Generate replaceMap Slot -> Expression from NamedExpression[Expression 
as name]
      */
-    public static Map<Slot, Expression> 
generateReplaceMap(List<NamedExpression> namedExpressions) {
+    public static Map<Slot, Expression> generateReplaceMap(List<? extends 
NamedExpression> namedExpressions) {
         Map<Slot, Expression> replaceMap = 
Maps.newLinkedHashMapWithExpectedSize(namedExpressions.size());
         for (NamedExpression namedExpression : namedExpressions) {
             if (namedExpression instanceof Alias) {
@@ -457,7 +457,7 @@ public class ExpressionUtils {
     /**
      * Replace expression node in the expression tree by `replaceMap` in 
top-down manner.
      */
-    public static List<NamedExpression> 
replaceNamedExpressions(List<NamedExpression> namedExpressions,
+    public static List<NamedExpression> replaceNamedExpressions(List<? extends 
NamedExpression> namedExpressions,
             Map<? extends Expression, ? extends Expression> replaceMap) {
         Builder<NamedExpression> replaceExprs = 
ImmutableList.builderWithExpectedSize(namedExpressions.size());
         for (NamedExpression namedExpression : namedExpressions) {
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/util/PlanUtils.java 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/util/PlanUtils.java
index 9ab1cf0ae1c..0e0a39d01c5 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/nereids/util/PlanUtils.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/util/PlanUtils.java
@@ -18,6 +18,7 @@
 package org.apache.doris.nereids.util;
 
 import org.apache.doris.catalog.TableIf;
+import org.apache.doris.nereids.exceptions.AnalysisException;
 import org.apache.doris.nereids.trees.expressions.ComparisonPredicate;
 import org.apache.doris.nereids.trees.expressions.Expression;
 import org.apache.doris.nereids.trees.expressions.NamedExpression;
@@ -120,10 +121,26 @@ public class PlanUtils {
     }
 
     /**
-     * merge childProjects with parentProjects
+     * try merge childProjects with parentProjects. if merged expression 
exceeds limit, return empty.
      */
-    public static List<NamedExpression> mergeProjections(List<NamedExpression> 
childProjects,
-            List<NamedExpression> parentProjects) {
+    public static Optional<List<NamedExpression>> tryMergeProjections(List<? 
extends NamedExpression> childProjects,
+            List<? extends NamedExpression> parentProjects) {
+        try {
+            return Optional.of(mergeProjections(childProjects, 
parentProjects));
+        } catch (AnalysisException e) {
+            if (e.getErrorCode() == 
AnalysisException.ErrorCode.EXPRESSION_EXCEEDS_LIMIT) {
+                return Optional.empty();
+            } else {
+                throw e;
+            }
+        }
+    }
+
+    /**
+     * merge childProjects with parentProjects. if merged expression exceeds 
limit, will throw AnalysisException.
+     */
+    public static List<NamedExpression> mergeProjections(List<? extends 
NamedExpression> childProjects,
+            List<? extends NamedExpression> parentProjects) {
         Map<Slot, Expression> replaceMap = 
ExpressionUtils.generateReplaceMap(childProjects);
         return ExpressionUtils.replaceNamedExpressions(parentProjects, 
replaceMap);
     }
diff --git 
a/regression-test/data/nereids_rules_p0/merge_project/test_merge_project.out 
b/regression-test/data/nereids_rules_p0/merge_project/test_merge_project.out
new file mode 100644
index 00000000000..56c097d00f8
Binary files /dev/null and 
b/regression-test/data/nereids_rules_p0/merge_project/test_merge_project.out 
differ
diff --git 
a/regression-test/suites/nereids_rules_p0/merge_project/test_merge_project.groovy
 
b/regression-test/suites/nereids_rules_p0/merge_project/test_merge_project.groovy
new file mode 100644
index 00000000000..f522dcdfa7d
--- /dev/null
+++ 
b/regression-test/suites/nereids_rules_p0/merge_project/test_merge_project.groovy
@@ -0,0 +1,105 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
+// OF ANY KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+suite('test_merge_project', 'nonConcurrent') {
+    setFeConfigTemporary([expr_children_limit : 200]) {
+        def tbl = 'tbl_test_merge_project'
+        multi_sql """
+            SET ignore_shape_nodes='PhysicalDistribute';
+            drop table if exists ${tbl} force;
+            create table ${tbl} (a int, b int) properties('replication_num' = 
'1');
+            insert into ${tbl} values (2, 1), (4, 3);
+            """
+
+        explainAndOrderResult 'exceeds_expression_limit', """
+            select
+                case
+                  when k15 > k16 then k15 + k16 + 1
+                  when k15 > k16 - 1 then k15 + k16 - 1
+                  when k15 > k16 - 2 then k15 + k16 - 2
+                  else 0
+                end as k17,
+                k15 + k16 as k18
+            from
+            (select
+                case
+                  when k13 > k14 then k13 + k14 + 1
+                  when k13 > k14 - 1 then k13 + k14 - 1
+                  when k13 > k14 - 2 then k13 + k14 - 2
+                  else 0
+                end as k15,
+                k13 + k14 as k16
+            from
+            (select
+                case
+                  when k11 > k12 then k11 + k12 + 1
+                  when k11 > k12 - 1 then k11 + k12 - 1
+                  when k11 > k12 - 2 then k11 + k12 - 2
+                  else 0
+                end as k13,
+                k11 + k12 as k14
+            from
+            (select
+                case
+                  when k9 > k10 then k9 + k10 + 1
+                  when k9 > k10 - 1 then k9 + k10 - 1
+                  when k9 > k10 - 2 then k9 + k10 - 2
+                  else 0
+                end as k11,
+                k9 + k10 as k12
+            from
+            (select
+                case
+                  when k7 > k8 then k7 + k8 + 1
+                  when k7 > k8 - 1 then k7 + k8 - 1
+                  when k7 > k8 - 2 then k7 + k8 - 2
+                  else 0
+                end as k9,
+                k7 + k8 as k10
+            from
+            (select
+               case
+                 when k5 > k6 then k5 + k6 + 1
+                 when k5 > k6 - 1 then k5 + k6 - 1
+                 when k5 > k6 - 2 then k5 + k6 - 2
+                 else 0
+               end as k7,
+               k5 + k6 as k8
+            from
+            (select
+               case
+                 when k3 > k4 then k3 + k4 + 1
+                 when k3 > k4 - 1 then k3 + k4 - 1
+                 when k3 > k4 - 2 then k3 + k4 - 2
+                 else 0
+               end as k5,
+               k3 + k4 as k6
+            from
+            (select
+                case
+                  when k1 > k2 then k1 + k2 + 1
+                  when k1 > k2 - 1 then k1 + k2 - 1
+                  when k1 > k2 - 2 then k1 + k2 - 2
+                  else 0
+                end as k3,
+                k1 + k2 as k4
+            from
+            (select a as k1, b as k2
+            from ${tbl}) t1) t2) t3) t4) t5) t6) t7) t8;
+            """
+    }
+}


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

Reply via email to