This is an automated email from the ASF dual-hosted git repository.

yiguolei pushed a commit to branch branch-2.1
in repository https://gitbox.apache.org/repos/asf/doris.git

commit aff09fc9bcf8e8d71c1662622d86a75125891cdb
Author: 谢健 <jianx...@gmail.com>
AuthorDate: Thu Mar 7 19:50:02 2024 +0800

    [feature](Nereids) support make miss slot as null alias when converting 
anti join (#31854)
    
    transform
    
    project(A.*, B.slot)
      - filter(B.slot is null)
        - LeftOuterJoin(A, B)
    
    to
    
    project(A.*, null as B.slot)
      - LeftAntiJoin(A, B)
---
 .../rules/rewrite/ConvertOuterJoinToAntiJoin.java  | 76 ++++++++++------------
 .../rewrite/ConvertOuterJoinToAntiJoinTest.java    |  2 +-
 .../transform_outer_join_to_anti.groovy            | 20 ++++++
 3 files changed, 54 insertions(+), 44 deletions(-)

diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/ConvertOuterJoinToAntiJoin.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/ConvertOuterJoinToAntiJoin.java
index 0ebc840e7dc..60b55e0a18d 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/ConvertOuterJoinToAntiJoin.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/ConvertOuterJoinToAntiJoin.java
@@ -19,8 +19,10 @@ 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.Alias;
+import org.apache.doris.nereids.trees.expressions.NamedExpression;
 import org.apache.doris.nereids.trees.expressions.Slot;
+import org.apache.doris.nereids.trees.expressions.literal.NullLiteral;
 import org.apache.doris.nereids.trees.plans.JoinType;
 import org.apache.doris.nereids.trees.plans.Plan;
 import org.apache.doris.nereids.trees.plans.logical.LogicalFilter;
@@ -28,8 +30,7 @@ import 
org.apache.doris.nereids.trees.plans.logical.LogicalJoin;
 import org.apache.doris.nereids.trees.plans.logical.LogicalProject;
 import org.apache.doris.nereids.util.TypeUtils;
 
-import com.google.common.collect.ImmutableSet;
-
+import java.util.List;
 import java.util.Set;
 import java.util.stream.Collectors;
 
@@ -45,23 +46,15 @@ public class ConvertOuterJoinToAntiJoin extends 
OneRewriteRuleFactory {
 
     @Override
     public Rule build() {
-        return logicalProject(logicalFilter(logicalJoin()
-                .when(join -> join.getJoinType().isOuterJoin())))
+        return logicalFilter(logicalJoin()
+                .when(join -> join.getJoinType().isOuterJoin()))
                 .then(this::toAntiJoin)
         .toRule(RuleType.CONVERT_OUTER_JOIN_TO_ANTI);
     }
 
-    private Plan toAntiJoin(LogicalProject<LogicalFilter<LogicalJoin<Plan, 
Plan>>> project) {
-        LogicalFilter<LogicalJoin<Plan, Plan>> filter = project.child();
+    private Plan toAntiJoin(LogicalFilter<LogicalJoin<Plan, Plan>> filter) {
         LogicalJoin<Plan, Plan> join = filter.child();
 
-        boolean leftOutput = 
join.left().getOutputSet().containsAll(project.getInputSlots());
-        boolean rightOutput = 
join.right().getOutputSet().containsAll(project.getInputSlots());
-
-        if (!leftOutput && !rightOutput) {
-            return null;
-        }
-
         Set<Slot> alwaysNullSlots = filter.getConjuncts().stream()
                 .filter(p -> TypeUtils.isNull(p).isPresent())
                 .flatMap(p -> p.getInputSlots().stream())
@@ -73,36 +66,33 @@ public class ConvertOuterJoinToAntiJoin extends 
OneRewriteRuleFactory {
                 .filter(s -> alwaysNullSlots.contains(s) && !s.nullable())
                 .collect(Collectors.toSet());
 
-        Plan res = project;
-        if (join.getJoinType().isLeftOuterJoin() && 
!rightAlwaysNullSlots.isEmpty() && leftOutput) {
-            // When there is right slot always null, we can turn left outer 
join to left anti join
-            Set<Expression> predicates = filter.getExpressions().stream()
-                    .filter(p -> !(TypeUtils.isNull(p).isPresent()
-                            && 
rightAlwaysNullSlots.containsAll(p.getInputSlots())))
-                    .collect(ImmutableSet.toImmutableSet());
-            boolean containRightSlot = predicates.stream()
-                    .flatMap(p -> p.getInputSlots().stream())
-                    .anyMatch(join.right().getOutputSet()::contains);
-            if (!containRightSlot) {
-                res = join.withJoinType(JoinType.LEFT_ANTI_JOIN, 
join.getJoinReorderContext());
-                res = predicates.isEmpty() ? res : 
filter.withConjuncts(predicates).withChildren(res);
-                res = project.withChildren(res);
-            }
+        Plan newJoin = null;
+        if (join.getJoinType().isLeftOuterJoin() && 
!rightAlwaysNullSlots.isEmpty()) {
+            newJoin = join.withJoinType(JoinType.LEFT_ANTI_JOIN, 
join.getJoinReorderContext());
         }
-        if (join.getJoinType().isRightOuterJoin() && 
!leftAlwaysNullSlots.isEmpty() && rightOutput) {
-            Set<Expression> predicates = filter.getExpressions().stream()
-                    .filter(p -> !(TypeUtils.isNull(p).isPresent()
-                            && 
leftAlwaysNullSlots.containsAll(p.getInputSlots())))
-                    .collect(ImmutableSet.toImmutableSet());
-            boolean containLeftSlot = predicates.stream()
-                    .flatMap(p -> p.getInputSlots().stream())
-                    .anyMatch(join.left().getOutputSet()::contains);
-            if (!containLeftSlot) {
-                res = join.withJoinType(JoinType.RIGHT_ANTI_JOIN, 
join.getJoinReorderContext());
-                res = predicates.isEmpty() ? res : 
filter.withConjuncts(predicates).withChildren(res);
-                res = project.withChildren(res);
-            }
+        if (join.getJoinType().isRightOuterJoin() && 
!leftAlwaysNullSlots.isEmpty()) {
+            newJoin = join.withJoinType(JoinType.RIGHT_ANTI_JOIN, 
join.getJoinReorderContext());
+        }
+        if (newJoin == null) {
+            return null;
+        }
+
+        if (!newJoin.getOutputSet().containsAll(filter.getInputSlots())) {
+            // if there are slots that don't belong to join output, we use 
null alias to replace them
+            // such as:
+            //   project(A.id, null as B.id)
+            //       -  (A left anti join B)
+            Set<Slot> joinOutput = newJoin.getOutputSet();
+            List<NamedExpression> projects = filter.getOutput().stream()
+                    .map(s -> {
+                        if (joinOutput.contains(s)) {
+                            return s;
+                        } else {
+                            return new Alias(s.getExprId(), new 
NullLiteral(s.getDataType()), s.getName());
+                        }
+                    }).collect(Collectors.toList());
+            newJoin = new LogicalProject<>(projects, newJoin);
         }
-        return res;
+        return filter.withChildren(newJoin);
     }
 }
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/ConvertOuterJoinToAntiJoinTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/ConvertOuterJoinToAntiJoinTest.java
index 20b36d3272e..1159fc2a7ce 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/ConvertOuterJoinToAntiJoinTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/ConvertOuterJoinToAntiJoinTest.java
@@ -111,7 +111,7 @@ class ConvertOuterJoinToAntiJoinTest implements 
MemoPatternMatchSupported {
                 .applyTopDown(new InferFilterNotNull())
                 .applyTopDown(new ConvertOuterJoinToAntiJoin())
                 .printlnTree()
-                .matches(logicalJoin().when(join -> 
join.getJoinType().isLeftOuterJoin()));
+                .matches(logicalJoin().when(join -> 
join.getJoinType().isLeftAntiJoin()));
     }
 
     @Test
diff --git 
a/regression-test/suites/nereids_syntax_p0/transform_outer_join_to_anti.groovy 
b/regression-test/suites/nereids_syntax_p0/transform_outer_join_to_anti.groovy
index 06f87359d92..3628063f43e 100644
--- 
a/regression-test/suites/nereids_syntax_p0/transform_outer_join_to_anti.groovy
+++ 
b/regression-test/suites/nereids_syntax_p0/transform_outer_join_to_anti.groovy
@@ -62,5 +62,25 @@ suite("transform_outer_join_to_anti") {
         sql("select eliminate_outer_join_B.* from eliminate_outer_join_A right 
outer join eliminate_outer_join_B on eliminate_outer_join_B.b = 
eliminate_outer_join_A.a where eliminate_outer_join_A.null_a is null")
         contains "OUTER JOIN"
     }
+
+    explain {
+        sql("select eliminate_outer_join_A.* from eliminate_outer_join_A left 
outer join eliminate_outer_join_B on eliminate_outer_join_B.b = 
eliminate_outer_join_A.a where eliminate_outer_join_B.b is null or 
eliminate_outer_join_A.null_a is null")
+        contains "OUTER JOIN"
+    }
+
+    explain {
+        sql("select * from eliminate_outer_join_A left outer join 
eliminate_outer_join_B on eliminate_outer_join_B.b = eliminate_outer_join_A.a 
where eliminate_outer_join_B.b is null and eliminate_outer_join_A.null_a is 
null")
+        contains "ANTI JOIN"
+    }
+
+    explain {
+        sql("select * from eliminate_outer_join_A left outer join 
eliminate_outer_join_B on eliminate_outer_join_B.b = eliminate_outer_join_A.a 
where eliminate_outer_join_B.b is null and eliminate_outer_join_B.null_b is 
null")
+        contains "ANTI JOIN"
+    }
+
+    explain {
+        sql("select * from eliminate_outer_join_A right outer join 
eliminate_outer_join_B on eliminate_outer_join_B.b = eliminate_outer_join_A.a 
where eliminate_outer_join_A.a is null and eliminate_outer_join_B.null_b is 
null and eliminate_outer_join_A.null_a is null")
+        contains "ANTI JOIN"
+    }
 }
 


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscr...@doris.apache.org
For additional commands, e-mail: commits-h...@doris.apache.org

Reply via email to