This is an automated email from the ASF dual-hosted git repository. morrysnow pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/master by this push: new 6f3db8b4b4 [enhancement](Nereids) add eliminate unnecessary project rule (#13886) 6f3db8b4b4 is described below commit 6f3db8b4b4947c110646f6755416993dca487522 Author: morrySnow <101034200+morrys...@users.noreply.github.com> AuthorDate: Wed Nov 2 14:16:03 2022 +0800 [enhancement](Nereids) add eliminate unnecessary project rule (#13886) This rule eliminate project that output set is same with its child. If the project is the root of plan, the elimination condition is project's output is exactly the same with its child. The reason to add this rule is when we do join reorder in optimization, the root of plan after transformed maybe a Project and its output set is same with the root of plan before transformed. If we had a Project on the top of the root and its output set is same with the root of plan too. We will have two exactly same projects in memo. One of them is the parent of the other. After MergeProject, we will get a new Project exactly same like the child and need to add to parent's group. The [...] ## for example: **BEFORE OPTIMIZATION** ``` LogicalProject1( projects=[c_custkey#0, c_name#1]) [GroupId#1] +--LogicalJoin(type=LEFT_SEMI_JOIN) [GroupId#2] |--LogicalProject(...) | +--LogicalJoin(type=INNER_JOIN) | ... +--LogicalOlapScan(...) ``` **AFTER APPLY RULE: LOGICAL_SEMI_JOIN_LOGICAL_JOIN_TRANSPOSE_PROJECT** ``` LogicalProject1( projects=[c_custkey#0, c_name#1]) [GroupId#1] +--LogicalProject2( projects=[c_custkey#0, c_name#1]) [GroupId#2] +--LogicalJoin(type=INNER_JOIN) [GroupId#10] |--LogicalProject(...) | +--LogicalJoin(type=LEFT_SEMI_JOIN) | ... +--LogicalOlapScan(...) ``` **AFTER APPLY RULE: MERGE_PROJECTS** ``` LogicalProject3( projects=[c_custkey#0, c_name#1]) [should be in GroupId#1, but in GroupId#2 in fact] +--LogicalJoin(type=INNER_JOIN) [GroupId#10] |--LogicalProject(...) | +--LogicalJoin(type=LEFT_SEMI_JOIN) | ... +--LogicalOlapScan(...) ``` Since we have exaclty GroupExpression(LogicalProject3 and LogicalProject2) in GroupId#1 and GroupId#2, we need to do MergeGroup(GroupId#1, GroupId#2). But we have child of GroupId#1 in GroupId#2. So the merge is denied. If the best GroupExpression in GroupId#2 is LogicalProject3, we will get two consecutive projects in the final plan. --- .../jobs/batch/NereidsRewriteJobExecutor.java | 4 + .../org/apache/doris/nereids/rules/RuleType.java | 1 + .../logical/EliminateUnnecessaryProject.java | 51 +++++++++++++ .../logical/EliminateUnnecessaryProjectTest.java | 85 ++++++++++++++++++++++ 4 files changed, 141 insertions(+) diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/jobs/batch/NereidsRewriteJobExecutor.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/jobs/batch/NereidsRewriteJobExecutor.java index 6fff27ede5..641f4f1742 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/jobs/batch/NereidsRewriteJobExecutor.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/jobs/batch/NereidsRewriteJobExecutor.java @@ -27,6 +27,7 @@ import org.apache.doris.nereids.rules.mv.SelectMaterializedIndexWithoutAggregate import org.apache.doris.nereids.rules.rewrite.logical.ColumnPruning; import org.apache.doris.nereids.rules.rewrite.logical.EliminateFilter; import org.apache.doris.nereids.rules.rewrite.logical.EliminateLimit; +import org.apache.doris.nereids.rules.rewrite.logical.EliminateUnnecessaryProject; import org.apache.doris.nereids.rules.rewrite.logical.ExtractSingleTableExpressionFromDisjunction; import org.apache.doris.nereids.rules.rewrite.logical.FindHashConditionForJoin; import org.apache.doris.nereids.rules.rewrite.logical.LimitPushDown; @@ -75,6 +76,9 @@ public class NereidsRewriteJobExecutor extends BatchRulesJob { .add(topDownBatch(ImmutableList.of(new PruneOlapScanPartition()))) .add(topDownBatch(ImmutableList.of(new SelectMaterializedIndexWithAggregate()))) .add(topDownBatch(ImmutableList.of(new SelectMaterializedIndexWithoutAggregate()))) + // we need to execute this rule at the end of rewrite + // to avoid two consecutive same project appear when we do optimization. + .add(topDownBatch(ImmutableList.of(new EliminateUnnecessaryProject()))) .build(); rulesJob.addAll(jobs); diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/RuleType.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/RuleType.java index 9728b49b38..2ee123c482 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/RuleType.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/RuleType.java @@ -58,6 +58,7 @@ public enum RuleType { NORMALIZE_AGGREGATE(RuleTypeClass.REWRITE), AGGREGATE_DISASSEMBLE(RuleTypeClass.REWRITE), COLUMN_PRUNE_PROJECTION(RuleTypeClass.REWRITE), + ELIMINATE_UNNECESSARY_PROJECT(RuleTypeClass.REWRITE), ELIMINATE_ALIAS_NODE(RuleTypeClass.REWRITE), PROJECT_ELIMINATE_ALIAS_NODE(RuleTypeClass.REWRITE), diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/logical/EliminateUnnecessaryProject.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/logical/EliminateUnnecessaryProject.java new file mode 100644 index 0000000000..675560fd7e --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/logical/EliminateUnnecessaryProject.java @@ -0,0 +1,51 @@ +// 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. + +package org.apache.doris.nereids.rules.rewrite.logical; + +import org.apache.doris.nereids.rules.Rule; +import org.apache.doris.nereids.rules.RuleType; +import org.apache.doris.nereids.rules.rewrite.OneRewriteRuleFactory; +import org.apache.doris.nereids.trees.plans.Plan; +import org.apache.doris.nereids.trees.plans.logical.LogicalProject; + +/** + * remove the project that output same with its child to avoid we get two consecutive projects in best plan. + * for more information, please see <a href="https://github.com/apache/doris/pull/13886">this PR</a> + */ +public class EliminateUnnecessaryProject extends OneRewriteRuleFactory { + + @Override + public Rule build() { + return logicalProject(any()) + .when(project -> project.getOutputSet().equals(project.child().getOutputSet())) + .thenApply(ctx -> { + int rootGroupId = ctx.cascadesContext.getMemo().getRoot().getGroupId().asInt(); + LogicalProject<Plan> project = ctx.root; + // if project is root, we need to ensure the output order is same. + if (project.getGroupExpression().get().getOwnerGroup().getGroupId().asInt() == rootGroupId) { + if (project.getOutput().equals(project.child().getOutput())) { + return project.child(); + } else { + return null; + } + } else { + return project.child(); + } + }).toRule(RuleType.ELIMINATE_UNNECESSARY_PROJECT); + } +} diff --git a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/logical/EliminateUnnecessaryProjectTest.java b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/logical/EliminateUnnecessaryProjectTest.java new file mode 100644 index 0000000000..babcd5caf6 --- /dev/null +++ b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/logical/EliminateUnnecessaryProjectTest.java @@ -0,0 +1,85 @@ +// 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. + +package org.apache.doris.nereids.rules.rewrite.logical; + +import org.apache.doris.nereids.CascadesContext; +import org.apache.doris.nereids.rules.Rule; +import org.apache.doris.nereids.trees.expressions.literal.BooleanLiteral; +import org.apache.doris.nereids.trees.plans.Plan; +import org.apache.doris.nereids.trees.plans.logical.LogicalOlapScan; +import org.apache.doris.nereids.trees.plans.logical.LogicalPlan; +import org.apache.doris.nereids.trees.plans.logical.LogicalProject; +import org.apache.doris.nereids.util.LogicalPlanBuilder; +import org.apache.doris.nereids.util.MemoTestUtils; +import org.apache.doris.nereids.util.PlanConstructor; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.List; + +/** + * test ELIMINATE_UNNECESSARY_PROJECT rule. + */ +public class EliminateUnnecessaryProjectTest { + + @Test + public void testEliminateNonTopUnnecessaryProject() { + LogicalPlan unnecessaryProject = new LogicalPlanBuilder(PlanConstructor.newLogicalOlapScan(0, "t1", 0)) + .project(ImmutableList.of(1, 0)) + .filter(BooleanLiteral.FALSE) + .build(); + + CascadesContext cascadesContext = MemoTestUtils.createCascadesContext(unnecessaryProject); + List<Rule> rules = Lists.newArrayList(new EliminateUnnecessaryProject().build()); + cascadesContext.topDownRewrite(rules); + + Plan actual = cascadesContext.getMemo().copyOut(); + Assertions.assertTrue(actual.child(0) instanceof LogicalOlapScan); + } + + @Test + public void testEliminateTopUnnecessaryProject() { + LogicalPlan unnecessaryProject = new LogicalPlanBuilder(PlanConstructor.newLogicalOlapScan(0, "t1", 0)) + .project(ImmutableList.of(0, 1)) + .build(); + + CascadesContext cascadesContext = MemoTestUtils.createCascadesContext(unnecessaryProject); + List<Rule> rules = Lists.newArrayList(new EliminateUnnecessaryProject().build()); + cascadesContext.topDownRewrite(rules); + + Plan actual = cascadesContext.getMemo().copyOut(); + Assertions.assertTrue(actual instanceof LogicalOlapScan); + } + + @Test + public void testNotEliminateTopProjectWhenOutputNotEquals() { + LogicalPlan unnecessaryProject = new LogicalPlanBuilder(PlanConstructor.newLogicalOlapScan(0, "t1", 0)) + .project(ImmutableList.of(1, 0)) + .build(); + + CascadesContext cascadesContext = MemoTestUtils.createCascadesContext(unnecessaryProject); + List<Rule> rules = Lists.newArrayList(new EliminateUnnecessaryProject().build()); + cascadesContext.topDownRewrite(rules); + + Plan actual = cascadesContext.getMemo().copyOut(); + Assertions.assertTrue(actual instanceof LogicalProject); + } +} --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@doris.apache.org For additional commands, e-mail: commits-h...@doris.apache.org