From 3c4234111b7790aab6d31faa9deb11b0fcd8e9a9 Mon Sep 17 00:00:00 2001 From: starocean999 <40539150+starocean999@users.noreply.github.com> Date: Wed, 13 Mar 2024 13:46:20 +0800 Subject: [PATCH] [fix](nereids)EliminateSemiJoin should consider empty table (#32107) * [fix](nereids)EliminateSemiJoin should consider empty table * update out file --- .../rules/rewrite/EliminateSemiJoin.java | 58 +++++++------- .../rules/rewrite/EliminateSemiJoinTest.java | 75 ++++++++++++++++--- .../eliminate_join_condition.out | 4 +- 3 files changed, 96 insertions(+), 41 deletions(-) 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 index 808dcd7b18..c15a8e169b 100644 --- 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 @@ -22,7 +22,6 @@ 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 org.apache.doris.nereids.trees.plans.logical.LogicalJoin; @@ -35,40 +34,43 @@ public class EliminateSemiJoin extends OneRewriteRuleFactory { @Override public Rule build() { return logicalJoin() - // right will be converted to left .whenNot(LogicalJoin::isMarkJoin) - .when(join -> join.getJoinType().isLeftSemiOrAntiJoin()) + .when(join -> join.getJoinType().isSemiOrAntiJoin()) .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; + if (otherJoinConjuncts.size() == 1 + && otherJoinConjuncts.get(0).equals(BooleanLiteral.FALSE)) { + switch (join.getJoinType()) { + case LEFT_SEMI_JOIN: + case RIGHT_SEMI_JOIN: { + return new LogicalEmptyRelation( + StatementScopeIdGenerator.newRelationId(), + join.getOutput()); + } + case NULL_AWARE_LEFT_ANTI_JOIN: + case LEFT_ANTI_JOIN: { + return join.left(); + } + case RIGHT_ANTI_JOIN: { + return join.right(); + } + default: + throw new IllegalStateException("Unexpected join type: " + join.getJoinType()); } } else { + /* + * A left semi join B on true, normally should output all rows from A + * A left anti join B on true, normally should output empty set + * but things are different if other side table is empty, examples: + * A left semi join B on true, if B is empty, should output empty set + * A left anti join B on true, if B is empty, should output all rows from A + * A right semi join B on true, if A is empty, should output empty set + * A right anti join B on true, if A is empty, should output all rows from B + * we can see even condition is true, the result depends on if other side table is empty + */ return null; } - if (joinType == JoinType.LEFT_SEMI_JOIN && condition) { - // the right table may be empty, we need keep plan unchanged - return null; - } else if (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); + }).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 index 61218ae6dc..f26a0d7721 100644 --- 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 @@ -42,12 +42,12 @@ class EliminateSemiJoinTest extends TestWithFeService implements MemoPatternMatc } @Test - void semiTrue() { + void leftSemiTrue() { String sql = "select * from t t1 left semi join t t2 on true"; PlanChecker.from(connectContext) .analyze(sql) - .rewrite() + .applyBottomUp(new EliminateSemiJoin()) .matches( logicalResultSink( logicalProject(logicalJoin()) @@ -56,41 +56,92 @@ class EliminateSemiJoinTest extends TestWithFeService implements MemoPatternMatc } @Test - void semiFalse() { + void leftSemiFalse() { String sql = "select * from t t1 left semi join t t2 on false"; PlanChecker.from(connectContext) .analyze(sql) - .rewrite() + .applyBottomUp(new EliminateSemiJoin()) .matches( logicalEmptyRelation() ); } @Test - void antiTrue() { + void leftAntiTrue() { String sql = "select * from t t1 left anti join t t2 on true"; PlanChecker.from(connectContext) .analyze(sql) - .rewrite() + .applyBottomUp(new EliminateSemiJoin()) + .matches( + logicalJoin(logicalSubQueryAlias(), logicalSubQueryAlias()) + ); + } + + @Test + void leftAntiFalse() { + String sql = "select * from t t1 left anti join t t2 on false"; + + PlanChecker.from(connectContext) + .analyze(sql) + .applyBottomUp(new EliminateSemiJoin()) + .matches( + logicalResultSink( + logicalProject(logicalSubQueryAlias(logicalOlapScan())) + ) + ); + } + + @Test + void rightSemiTrue() { + String sql = "select * from t t1 right semi join t t2 on true"; + + PlanChecker.from(connectContext) + .analyze(sql) + .applyBottomUp(new EliminateSemiJoin()) + .matches( + logicalResultSink( + logicalProject(logicalJoin()) + ) + ); + } + + @Test + void rightSemiFalse() { + String sql = "select * from t t1 right semi join t t2 on false"; + + PlanChecker.from(connectContext) + .analyze(sql) + .applyBottomUp(new EliminateSemiJoin()) .matches( logicalEmptyRelation() ); } @Test - void antiFalse() { - String sql = "select * from t t1 left anti join t t2 on false"; + void rightAntiTrue() { + String sql = "select * from t t1 right anti join t t2 on true"; PlanChecker.from(connectContext) .analyze(sql) - .rewrite() + .applyBottomUp(new EliminateSemiJoin()) .matches( - logicalResultSink( - logicalOlapScan() - ) + logicalJoin(logicalSubQueryAlias(), logicalSubQueryAlias()) ); } + @Test + void rightAntiFalse() { + String sql = "select * from t t1 right anti join t t2 on false"; + + PlanChecker.from(connectContext) + .analyze(sql) + .applyBottomUp(new EliminateSemiJoin()) + .matches( + logicalResultSink( + logicalProject(logicalSubQueryAlias(logicalOlapScan())) + ) + ); + } } diff --git a/regression-test/data/nereids_rules_p0/eliminate_join_condition/eliminate_join_condition.out b/regression-test/data/nereids_rules_p0/eliminate_join_condition/eliminate_join_condition.out index 571a81995e..0b8b381e1f 100644 --- a/regression-test/data/nereids_rules_p0/eliminate_join_condition/eliminate_join_condition.out +++ b/regression-test/data/nereids_rules_p0/eliminate_join_condition/eliminate_join_condition.out @@ -31,7 +31,9 @@ PhysicalResultSink -- !left_anti_join -- PhysicalResultSink ---PhysicalEmptyRelation +--NestedLoopJoin[LEFT_ANTI_JOIN] +----PhysicalOlapScan[t] +----PhysicalOlapScan[t] -- !right_semi_join -- PhysicalResultSink