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 this PR + */ +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 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 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 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 rules = Lists.newArrayList(new EliminateUnnecessaryProject().build()); + cascadesContext.topDownRewrite(rules); + + Plan actual = cascadesContext.getMemo().copyOut(); + Assertions.assertTrue(actual instanceof LogicalProject); + } +}