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

Reply via email to