This is an automated email from the ASF dual-hosted git repository. dataroaring pushed a commit to branch branch-3.0 in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/branch-3.0 by this push: new b0158d4c34e branch-3.0: [fix](nereids) fix merge project contains non foldable expression #48321 (#48365) b0158d4c34e is described below commit b0158d4c34e9ba4906e8c2dcf6aaf3b73699c864 Author: yujun <yu...@selectdb.com> AuthorDate: Thu Feb 27 18:05:47 2025 +0800 branch-3.0: [fix](nereids) fix merge project contains non foldable expression #48321 (#48365) cherry pick from #48321 --- .../processor/post/MergeProjectPostProcessor.java | 2 +- .../doris/nereids/processor/post/Validator.java | 7 -- .../rules/exploration/MergeProjectsCBO.java | 1 + .../doris/nereids/rules/rewrite/MergeProjects.java | 5 +- .../doris/nereids/trees/plans/algebra/Project.java | 10 +++ .../trees/plans/physical/PhysicalProject.java | 19 +++++ .../org/apache/doris/nereids/util/PlanUtils.java | 24 +++++++ .../java/org/apache/doris/qe/SessionVariable.java | 19 +++++ .../data/nereids_rules_p0/test_nonfoldable.out | Bin 0 -> 2819 bytes .../nereids_rules_p0/test_nonfoldable.groovy | 77 +++++++++++++++++++++ 10 files changed, 153 insertions(+), 11 deletions(-) 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 d900d207b2f..7466889fde6 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 @@ -33,7 +33,7 @@ public class MergeProjectPostProcessor extends PlanPostProcessor { public PhysicalProject visitPhysicalProject(PhysicalProject<? extends Plan> project, CascadesContext ctx) { project = (PhysicalProject<? extends Plan>) super.visit(project, ctx); Plan child = project.child(); - if (child instanceof PhysicalProject) { + if (child instanceof PhysicalProject && project.canMergeProjections((PhysicalProject) child)) { List<NamedExpression> projections = project.mergeProjections((PhysicalProject) child); return (PhysicalProject) project .withProjectionsAndChild(projections, child.child(0)) diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/processor/post/Validator.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/processor/post/Validator.java index 6fe5f0e1c3e..1240c68c1e0 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/processor/post/Validator.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/processor/post/Validator.java @@ -44,13 +44,6 @@ public class Validator extends PlanPostProcessor { @Override public Plan visitPhysicalProject(PhysicalProject<? extends Plan> project, CascadesContext context) { Preconditions.checkArgument(!project.getProjects().isEmpty(), "Project list can't be empty"); - - Plan child = project.child(); - // Forbidden project-project, we must merge project. - if (child instanceof PhysicalProject) { - throw new AnalysisException("Nereids must merge a project-project plan"); - } - return visit(project, context); } diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/MergeProjectsCBO.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/MergeProjectsCBO.java index ff55596722e..4637425b55d 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/MergeProjectsCBO.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/MergeProjectsCBO.java @@ -28,6 +28,7 @@ public class MergeProjectsCBO extends OneExplorationRuleFactory { @Override public Rule build() { return logicalProject(logicalProject()) + .when(project -> project.canMergeProjections(project.child())) .then(project -> MergeProjects.mergeProjects(project)) .toRule(RuleType.MERGE_PROJECTS); } 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 77784253440..bb6ca154136 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 @@ -22,7 +22,6 @@ import org.apache.doris.nereids.rules.RuleType; import org.apache.doris.nereids.trees.expressions.NamedExpression; import org.apache.doris.nereids.trees.plans.Plan; import org.apache.doris.nereids.trees.plans.logical.LogicalProject; -import org.apache.doris.nereids.util.ExpressionUtils; import java.util.List; @@ -43,11 +42,11 @@ public class MergeProjects extends OneRewriteRuleFactory { // TODO modify ExtractAndNormalizeWindowExpression to handle nested window functions // here we just don't merge two projects if there is any window function return logicalProject(logicalProject()) - .whenNot(project -> ExpressionUtils.containsWindowExpression(project.getProjects()) - && ExpressionUtils.containsWindowExpression(project.child().getProjects())) + .when(project -> project.canMergeProjections(project.child())) .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); 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 a5d15f1d515..33b351ba4b3 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 @@ -74,6 +74,16 @@ public interface Project { return projects; } + /** check can merge two projects */ + default boolean canMergeProjections(Project childProject) { + if (ExpressionUtils.containsWindowExpression(getProjects()) + && ExpressionUtils.containsWindowExpression(childProject.getProjects())) { + return false; + } + + return PlanUtils.canReplaceWithProjections(childProject.getProjects(), getProjects()); + } + /** * find projects, if not found the slot, then throw AnalysisException */ diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/physical/PhysicalProject.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/physical/PhysicalProject.java index 0e7bdf36e8f..883fa49f8ba 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/physical/PhysicalProject.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/physical/PhysicalProject.java @@ -31,6 +31,7 @@ import org.apache.doris.nereids.trees.plans.PlanType; import org.apache.doris.nereids.trees.plans.algebra.Project; import org.apache.doris.nereids.trees.plans.visitor.PlanVisitor; import org.apache.doris.nereids.util.Utils; +import org.apache.doris.qe.ConnectContext; import org.apache.doris.statistics.Statistics; import com.google.common.base.Preconditions; @@ -44,6 +45,7 @@ import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.Set; +import java.util.stream.Collectors; /** * Physical project plan. @@ -95,6 +97,23 @@ public class PhysicalProject<CHILD_TYPE extends Plan> extends PhysicalUnary<CHIL ); } + @Override + public String shapeInfo() { + ConnectContext context = ConnectContext.get(); + if (context != null + && context.getSessionVariable().getDetailShapePlanNodesSet().contains(getClass().getSimpleName())) { + StringBuilder builder = new StringBuilder(); + builder.append(getClass().getSimpleName()); + // the internal project list's order may be unstable, especial for join tables, + // so sort the projects to make it stable + builder.append(projects.stream().map(Expression::shapeInfo).sorted() + .collect(Collectors.joining(", ", "[", "]"))); + return builder.toString(); + } else { + return super.shapeInfo(); + } + } + @Override public boolean equals(Object o) { if (this == o) { 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 0076c232340..c4beb90594a 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 @@ -45,6 +45,7 @@ import com.google.common.collect.Sets; import java.util.Collection; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; @@ -133,6 +134,29 @@ public class PlanUtils { return ExpressionUtils.replace(targetExpression, replaceMap); } + /** + * replace targetExpressions with project. + * if the target expression contains a slot which is an alias and its origin expression contains + * non-foldable expression and the slot exits multiple times, then can not replace. + * for example, target expressions: [a, a + 10], child project: [ t + random() as a ], + * if replace with the projects, then result expressions: [ t + random(), t + random() + 10 ], + * it will calculate random two times, this is error. + */ + public static boolean canReplaceWithProjections(List<NamedExpression> childProjects, + List<? extends Expression> targetExpressions) { + Set<Slot> nonfoldableSlots = ExpressionUtils.generateReplaceMap(childProjects).entrySet().stream() + .filter(entry -> entry.getValue().containsNonfoldable()) + .map(Entry::getKey) + .collect(Collectors.toSet()); + if (nonfoldableSlots.isEmpty()) { + return true; + } + + Set<Slot> counterSet = Sets.newHashSet(); + return targetExpressions.stream().noneMatch(target -> target.anyMatch( + e -> (e instanceof Slot) && nonfoldableSlots.contains(e) && !counterSet.add((Slot) e))); + } + public static Plan skipProjectFilterLimit(Plan plan) { if (plan instanceof LogicalProject && ((LogicalProject<?>) plan).isAllSlots() || plan instanceof LogicalFilter || plan instanceof LogicalLimit) { diff --git a/fe/fe-core/src/main/java/org/apache/doris/qe/SessionVariable.java b/fe/fe-core/src/main/java/org/apache/doris/qe/SessionVariable.java index 0c303526cec..f1b2181d1d1 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/qe/SessionVariable.java +++ b/fe/fe-core/src/main/java/org/apache/doris/qe/SessionVariable.java @@ -2135,6 +2135,8 @@ public class SessionVariable implements Serializable, Writable { public static final String IGNORE_SHAPE_NODE = "ignore_shape_nodes"; + public static final String DETAIL_SHAPE_NODES = "detail_shape_nodes"; + public static final String ENABLE_SEGMENT_CACHE = "enable_segment_cache"; public Set<String> getIgnoreShapePlanNodes() { @@ -2150,6 +2152,23 @@ public class SessionVariable implements Serializable, Writable { "the plan node type which is ignored in 'explain shape plan' command"}) public String ignoreShapePlanNodes = ""; + @VariableMgr.VarAttr(name = DETAIL_SHAPE_NODES, needForward = true, setter = "setDetailShapePlanNodes", + description = {"'explain shape plan' 命令中显示详细信息的PlanNode 类型", + "the plan node type show detail in 'explain shape plan' command"}) + public String detailShapePlanNodes = ""; + + private Set<String> detailShapePlanNodesSet = ImmutableSet.of(); + + public Set<String> getDetailShapePlanNodesSet() { + return detailShapePlanNodesSet; + } + + public void setDetailShapePlanNodes(String detailShapePlanNodes) { + this.detailShapePlanNodesSet = Arrays.stream(detailShapePlanNodes.split(",[\\s]*")) + .collect(ImmutableSet.toImmutableSet()); + this.detailShapePlanNodes = detailShapePlanNodes; + } + @VariableMgr.VarAttr(name = ENABLE_DECIMAL256, needForward = true, description = { "控制是否在计算过程中使用Decimal256类型", "Set to true to enable Decimal256 type" }) public boolean enableDecimal256 = false; diff --git a/regression-test/data/nereids_rules_p0/test_nonfoldable.out b/regression-test/data/nereids_rules_p0/test_nonfoldable.out new file mode 100644 index 00000000000..3c96406efb6 Binary files /dev/null and b/regression-test/data/nereids_rules_p0/test_nonfoldable.out differ diff --git a/regression-test/suites/nereids_rules_p0/test_nonfoldable.groovy b/regression-test/suites/nereids_rules_p0/test_nonfoldable.groovy new file mode 100644 index 00000000000..f48d93f986e --- /dev/null +++ b/regression-test/suites/nereids_rules_p0/test_nonfoldable.groovy @@ -0,0 +1,77 @@ +// 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_nonfoldable') { + sql 'SET enable_nereids_planner=true' + sql 'SET runtime_filter_mode=OFF' + sql 'SET enable_fallback_to_original_planner=false' + sql "SET ignore_shape_nodes='PhysicalDistribute'" + sql "SET detail_shape_nodes='PhysicalProject'" + sql 'SET disable_nereids_rules=PRUNE_EMPTY_PARTITION' + + qt_filter_through_project_1 ''' + explain shape plan select * from (select id + 100 as a, id + 200 as b, id + 300 as c from t1) t where a > 999 and b > 999 + ''' + + qt_filter_through_project_2 ''' + explain shape plan select * from (select id + random(1, 10) + 100 as a, id + 200 as b, id + 300 as c from t1) t where a > 999 and b > 999 + ''' + + qt_filter_through_project_3 ''' + explain shape plan select * from (select id + random(1, 10) + 100 as a, id + random(1, 10) + 200 as b, id + random(1, 10) + 300 as c from t1) t where a > 999 and b > 999 + ''' + + qt_filter_through_project_4 ''' + explain shape plan select * from (select id + 100 as a, id + 200 as b, id + 300 as c from t1) t where a + random(1, 10) > 999 and b + random(1, 10) > 999 + ''' + + qt_filter_through_project_5 ''' + explain shape plan select * from (select id + 100 as a, id + 200 as b, id + 300 as c from t1) t where a > 999 and b > 999 limit 10 + ''' + + qt_filter_through_project_6 ''' + explain shape plan select * from (select id + random(1, 10) + 100 as a, id + 200 as b, id + 300 as c from t1) t where a > 999 and b > 999 limit 10 + ''' + + qt_filter_through_project_7 ''' + explain shape plan select * from (select id + random(1, 10) + 100 as a, id + random(1, 10) + 200 as b, id + random(1, 10) + 300 as c from t1) t where a > 999 and b > 999 limit 10 + ''' + + qt_filter_through_project_8 ''' + explain shape plan select * from (select id + 100 as a, id + 200 as b, id + 300 as c from t1) t where a + random(1, 10) > 999 and b + random(1, 10) > 999 limit 10 + ''' + + qt_merge_project_1 ''' + explain shape plan select a as b, a as c from (select id + 100 as a from t1) t + ''' + + qt_merge_project_2 ''' + explain shape plan select a as b, a as c from (select id + random(1, 10) as a from t1) t + ''' + + qt_merge_project_3 ''' + explain shape plan select a as b from (select id + random(1, 10) as a from t1) t + ''' + + qt_merge_project_4 ''' + explain shape plan select a + 10 + a as b from (select id + random(1, 10) as a from t1) t + ''' + + qt_merge_project_5 ''' + explain shape plan select a as b, a + 10 as c from (select id + random(1, 10) as a from t1) t + ''' +} --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@doris.apache.org For additional commands, e-mail: commits-h...@doris.apache.org