From 5da1845ad825cc0423a0171d0ba5023f88d2c8cb Mon Sep 17 00:00:00 2001 From: jackwener Date: Mon, 18 Dec 2023 16:24:24 +0800 Subject: [PATCH] [feature](Nereids): eliminate semi join --- .../doris/nereids/jobs/executor/Rewriter.java | 4 +- .../apache/doris/nereids/rules/RuleType.java | 1 + .../rules/rewrite/EliminateSemiJoin.java | 70 ++++++++++++++ .../rules/rewrite/EliminateSemiJoinTest.java | 96 +++++++++++++++++++ 4 files changed, 170 insertions(+), 1 deletion(-) create mode 100644 fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/EliminateSemiJoin.java create mode 100644 fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/EliminateSemiJoinTest.java diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/jobs/executor/Rewriter.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/jobs/executor/Rewriter.java index 9104869387e9c4..16ad9ffd82d093 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/jobs/executor/Rewriter.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/jobs/executor/Rewriter.java @@ -60,6 +60,7 @@ import org.apache.doris.nereids.rules.rewrite.EliminateNotNull; import org.apache.doris.nereids.rules.rewrite.EliminateNullAwareLeftAntiJoin; import org.apache.doris.nereids.rules.rewrite.EliminateOrderByConstant; +import org.apache.doris.nereids.rules.rewrite.EliminateSemiJoin; import org.apache.doris.nereids.rules.rewrite.EliminateSort; import org.apache.doris.nereids.rules.rewrite.EliminateSortUnderSubquery; import org.apache.doris.nereids.rules.rewrite.EliminateUnnecessaryProject; @@ -184,7 +185,8 @@ public class Rewriter extends AbstractBatchJobExecutor { new EliminateFilter(), new EliminateAggregate(), new EliminateJoinCondition(), - new EliminateAssertNumRows() + new EliminateAssertNumRows(), + new EliminateSemiJoin() ) ), // please note: this rule must run before NormalizeAggregate 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 41db8bb5dff899..3a9b7cdce5c76a 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 @@ -202,6 +202,7 @@ public enum RuleType { ELIMINATE_JOIN(RuleTypeClass.REWRITE), ELIMINATE_JOIN_CONDITION(RuleTypeClass.REWRITE), ELIMINATE_FILTER_ON_ONE_RELATION(RuleTypeClass.REWRITE), + ELIMINATE_SEMI_JOIN(RuleTypeClass.REWRITE), ELIMINATE_NOT_NULL(RuleTypeClass.REWRITE), ELIMINATE_UNNECESSARY_PROJECT(RuleTypeClass.REWRITE), ELIMINATE_OUTER_JOIN(RuleTypeClass.REWRITE), diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/EliminateSemiJoin.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/EliminateSemiJoin.java new file mode 100644 index 00000000000000..3c9f09e9316d24 --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/EliminateSemiJoin.java @@ -0,0 +1,70 @@ +// 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; + +import org.apache.doris.nereids.rules.Rule; +import org.apache.doris.nereids.rules.RuleType; +import org.apache.doris.nereids.trees.expressions.Expression; +import org.apache.doris.nereids.trees.expressions.StatementScopeIdGenerator; +import org.apache.doris.nereids.trees.expressions.literal.BooleanLiteral; +import org.apache.doris.nereids.trees.plans.JoinType; +import org.apache.doris.nereids.trees.plans.logical.LogicalEmptyRelation; + +import java.util.List; + +/** + * Eliminate Semi/Anti Join which is FALSE or TRUE. + */ +public class EliminateSemiJoin extends OneRewriteRuleFactory { + @Override + public Rule build() { + return logicalJoin() + // right will be converted to left + .when(join -> join.getJoinType().isLeftSemiOrAntiJoin()) + .when(join -> join.getHashJoinConjuncts().isEmpty()) + .then(join -> { + List otherJoinConjuncts = join.getOtherJoinConjuncts(); + JoinType joinType = join.getJoinType(); + + boolean condition; + if (otherJoinConjuncts.isEmpty()) { + condition = true; + } else if (otherJoinConjuncts.size() == 1) { + if (otherJoinConjuncts.get(0).equals(BooleanLiteral.TRUE)) { + condition = true; + } else if (otherJoinConjuncts.get(0).equals(BooleanLiteral.FALSE)) { + condition = false; + } else { + return null; + } + } else { + return null; + } + if (joinType == JoinType.LEFT_SEMI_JOIN && condition + || (joinType == JoinType.LEFT_ANTI_JOIN && !condition)) { + return join.left(); + } else if (joinType == JoinType.LEFT_SEMI_JOIN && !condition + || (joinType == JoinType.LEFT_ANTI_JOIN && condition)) { + return new LogicalEmptyRelation(StatementScopeIdGenerator.newRelationId(), join.getOutput()); + } else { + throw new IllegalStateException("Unexpected join type: " + joinType); + } + }) + .toRule(RuleType.ELIMINATE_SEMI_JOIN); + } +} diff --git a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/EliminateSemiJoinTest.java b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/EliminateSemiJoinTest.java new file mode 100644 index 00000000000000..69c1f41d90bb80 --- /dev/null +++ b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/EliminateSemiJoinTest.java @@ -0,0 +1,96 @@ +// 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; + +import org.apache.doris.nereids.util.MemoPatternMatchSupported; +import org.apache.doris.nereids.util.PlanChecker; +import org.apache.doris.utframe.TestWithFeService; + +import org.junit.jupiter.api.Test; + +class EliminateSemiJoinTest extends TestWithFeService implements MemoPatternMatchSupported { + + @Override + protected void runBeforeAll() throws Exception { + createDatabase("test"); + + connectContext.setDatabase("test"); + + createTable("CREATE TABLE t (" + + "id int not null" + + ")\n" + + "DISTRIBUTED BY HASH(id)\n" + + "BUCKETS 1\n" + + "PROPERTIES(\n" + + " \"replication_num\"=\"1\"\n" + + ");"); + } + + @Test + void semiTrue() { + String sql = "select * from t t1 left semi join t t2 on true"; + + PlanChecker.from(connectContext) + .analyze(sql) + .rewrite() + .matches( + logicalResultSink( + logicalOlapScan() + ) + ); + } + + @Test + void semiFalse() { + String sql = "select * from t t1 left semi join t t2 on false"; + + PlanChecker.from(connectContext) + .analyze(sql) + .rewrite() + .matches( + logicalEmptyRelation() + ); + } + + @Test + void antiTrue() { + String sql = "select * from t t1 left anti join t t2 on true"; + + PlanChecker.from(connectContext) + .analyze(sql) + .rewrite() + .matches( + logicalEmptyRelation() + ); + } + + @Test + void antiFalse() { + String sql = "select * from t t1 left anti join t t2 on false"; + + PlanChecker.from(connectContext) + .analyze(sql) + .rewrite() + .matches( + logicalResultSink( + logicalOlapScan() + ) + ); + } + +}