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

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


The following commit(s) were added to refs/heads/branch-3.1 by this push:
     new f5b9fb587a4 branch-3.1: [fix](nereids) fix execute err which throw eq 
function not exist exception when join reorder #54953 (#55667)
f5b9fb587a4 is described below

commit f5b9fb587a421401d425a1f4b159d8584c40b7bb
Author: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Fri Sep 5 18:22:45 2025 +0800

    branch-3.1: [fix](nereids) fix execute err which throw eq function not 
exist exception when join reorder #54953 (#55667)
    
    Cherry-picked from #54953
    
    Co-authored-by: seawinde <[email protected]>
---
 .../rules/exploration/join/JoinCommute.java        |   8 +-
 .../exploration/join/OuterJoinAssocProject.java    |   3 +
 .../exploration/join/OuterJoinLAsscomProject.java  |   3 +
 .../nereids/rules/rewrite/AdjustNullable.java      |  50 ++++++++--
 .../org/apache/doris/nereids/util/JoinUtils.java   |  21 +++++
 .../rules/exploration/join/JoinCommuteTest.java    |  50 ++++++++++
 .../join/OuterJoinAsscomProjectTest.java           | 105 +++++++++++++++++++++
 .../join/OuterJoinLAsscomProjectTest.java          |  61 ++++++++++++
 8 files changed, 289 insertions(+), 12 deletions(-)

diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/join/JoinCommute.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/join/JoinCommute.java
index 73fb853082c..54d310adb6c 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/join/JoinCommute.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/join/JoinCommute.java
@@ -74,14 +74,16 @@ public class JoinCommute extends OneExplorationRuleFactory {
                         && 
NestedLoopJoinNode.canParallelize(JoinType.toJoinOperator(join.getJoinType()))
                         && 
!NestedLoopJoinNode.canParallelize(JoinType.toJoinOperator(join.getJoinType().swap())))
                 .then(join -> {
-                    LogicalJoin<Plan, Plan> newJoin = 
join.withTypeChildren(join.getJoinType().swap(),
-                            join.right(), join.left(), null);
+                    LogicalJoin<? extends Plan, ? extends Plan> newJoin = 
join.withTypeChildren(
+                            join.getJoinType().swap(), join.right(), 
join.left(), null);
                     
newJoin.getJoinReorderContext().copyFrom(join.getJoinReorderContext());
                     newJoin.getJoinReorderContext().setHasCommute(true);
                     if (swapType == SwapType.ZIG_ZAG && isNotBottomJoin(join)) 
{
                         
newJoin.getJoinReorderContext().setHasCommuteZigZag(true);
                     }
-
+                    if (newJoin.getJoinType().isOuterJoin()) {
+                        newJoin = 
JoinUtils.adjustJoinConjunctsNullable(newJoin);
+                    }
                     return newJoin;
                 }).toRule(RuleType.LOGICAL_JOIN_COMMUTE);
     }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/join/OuterJoinAssocProject.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/join/OuterJoinAssocProject.java
index af8f002afbd..0dcb3b8d344 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/join/OuterJoinAssocProject.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/join/OuterJoinAssocProject.java
@@ -32,6 +32,7 @@ import org.apache.doris.nereids.trees.plans.Plan;
 import org.apache.doris.nereids.trees.plans.logical.LogicalJoin;
 import org.apache.doris.nereids.trees.plans.logical.LogicalProject;
 import org.apache.doris.nereids.util.ExpressionUtils;
+import org.apache.doris.nereids.util.JoinUtils;
 import org.apache.doris.nereids.util.Utils;
 
 import com.google.common.collect.ImmutableSet;
@@ -99,6 +100,7 @@ public class OuterJoinAssocProject extends 
OneExplorationRuleFactory {
                     /* ********** new Plan ********** */
                     LogicalJoin newBottomJoin = 
topJoin.withChildrenNoContext(b, c, null);
                     
newBottomJoin.getJoinReorderContext().copyFrom(bottomJoin.getJoinReorderContext());
+                    newBottomJoin = 
JoinUtils.adjustJoinConjunctsNullable(newBottomJoin);
 
                     Set<ExprId> topUsedExprIds = new HashSet<>();
                     topProject.getProjects().forEach(expr -> 
topUsedExprIds.addAll(expr.getInputSlotExprIds()));
@@ -110,6 +112,7 @@ public class OuterJoinAssocProject extends 
OneExplorationRuleFactory {
                     LogicalJoin newTopJoin = 
bottomJoin.withChildrenNoContext(left, right, null);
                     
newTopJoin.getJoinReorderContext().copyFrom(topJoin.getJoinReorderContext());
                     setReorderContext(newTopJoin, newBottomJoin);
+                    newTopJoin = 
JoinUtils.adjustJoinConjunctsNullable(newTopJoin);
 
                     return topProject.withChildren(newTopJoin);
                 }).toRule(RuleType.LOGICAL_OUTER_JOIN_ASSOC_PROJECT);
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/join/OuterJoinLAsscomProject.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/join/OuterJoinLAsscomProject.java
index b8948b22bde..ac9787bed48 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/join/OuterJoinLAsscomProject.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/join/OuterJoinLAsscomProject.java
@@ -29,6 +29,7 @@ import org.apache.doris.nereids.trees.plans.JoinType;
 import org.apache.doris.nereids.trees.plans.Plan;
 import org.apache.doris.nereids.trees.plans.logical.LogicalJoin;
 import org.apache.doris.nereids.trees.plans.logical.LogicalProject;
+import org.apache.doris.nereids.util.JoinUtils;
 import org.apache.doris.nereids.util.Utils;
 
 import com.google.common.collect.ImmutableSet;
@@ -84,6 +85,7 @@ public class OuterJoinLAsscomProject extends 
OneExplorationRuleFactory {
                     
newBottomJoin.getJoinReorderContext().copyFrom(bottomJoin.getJoinReorderContext());
                     newBottomJoin.getJoinReorderContext().setHasLAsscom(false);
                     newBottomJoin.getJoinReorderContext().setHasCommute(false);
+                    newBottomJoin = 
JoinUtils.adjustJoinConjunctsNullable(newBottomJoin);
 
                     Set<ExprId> topUsedExprIds = new HashSet<>();
                     topProject.getProjects().forEach(expr -> 
topUsedExprIds.addAll(expr.getInputSlotExprIds()));
@@ -95,6 +97,7 @@ public class OuterJoinLAsscomProject extends 
OneExplorationRuleFactory {
                     LogicalJoin newTopJoin = 
bottomJoin.withChildrenNoContext(left, right, null);
                     
newTopJoin.getJoinReorderContext().copyFrom(topJoin.getJoinReorderContext());
                     newTopJoin.getJoinReorderContext().setHasLAsscom(true);
+                    newTopJoin = 
JoinUtils.adjustJoinConjunctsNullable(newTopJoin);
 
                     return topProject.withChildren(newTopJoin);
                 }).toRule(RuleType.LOGICAL_OUTER_JOIN_LASSCOM_PROJECT);
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/AdjustNullable.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/AdjustNullable.java
index df711a921e9..60a908739e2 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/AdjustNullable.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/AdjustNullable.java
@@ -152,13 +152,22 @@ public class AdjustNullable extends 
DefaultPlanRewriter<Map<ExprId, Slot>> imple
     @Override
     public Plan visitLogicalJoin(LogicalJoin<? extends Plan, ? extends Plan> 
join, Map<ExprId, Slot> replaceMap) {
         join = (LogicalJoin<? extends Plan, ? extends Plan>) super.visit(join, 
replaceMap);
-        Optional<List<Expression>> hashConjuncts = 
updateExpressions(join.getHashJoinConjuncts(), replaceMap, true);
+        return doVisitLogicalJoin(join, replaceMap, isAnalyzedPhase, 
!isAnalyzedPhase);
+    }
+
+    /**
+     * Adjust join nullable attribute of slot reference in expression.
+     */
+    public static LogicalJoin<? extends Plan, ? extends Plan> 
doVisitLogicalJoin(
+            LogicalJoin<? extends Plan, ? extends Plan> join,
+            Map<ExprId, Slot> replaceMap, boolean isAnalyzedPhase, boolean 
check) {
+        Optional<List<Expression>> hashConjuncts = 
doUpdateExpressions(join.getHashJoinConjuncts(), replaceMap, check);
         Optional<List<Expression>> markConjuncts = Optional.empty();
         boolean hadUpdatedMarkConjuncts = false;
         if (isAnalyzedPhase || join.getHashJoinConjuncts().isEmpty()) {
             // if hashConjuncts is empty, mark join conjuncts may use to build 
hash table
             // so need call updateExpressions for mark join conjuncts before 
adjust nullable by output slot
-            markConjuncts = updateExpressions(join.getMarkJoinConjuncts(), 
replaceMap, true);
+            markConjuncts = doUpdateExpressions(join.getMarkJoinConjuncts(), 
replaceMap, check);
             hadUpdatedMarkConjuncts = true;
         }
         // in fact, otherConjuncts shouldn't use join output nullable 
attribute,
@@ -172,21 +181,21 @@ public class AdjustNullable extends 
DefaultPlanRewriter<Map<ExprId, Slot>> imple
         //    Just change it to be consistent with BE.
         Optional<List<Expression>> otherConjuncts = Optional.empty();
         if (isAnalyzedPhase) {
-            otherConjuncts = updateExpressions(join.getOtherJoinConjuncts(), 
replaceMap, true);
+            otherConjuncts = doUpdateExpressions(join.getOtherJoinConjuncts(), 
replaceMap, false);
         }
         for (Slot slot : join.getOutput()) {
             replaceMap.put(slot.getExprId(), slot);
         }
         if (!hadUpdatedMarkConjuncts) {
-            markConjuncts = updateExpressions(join.getMarkJoinConjuncts(), 
replaceMap, false);
+            markConjuncts = doUpdateExpressions(join.getMarkJoinConjuncts(), 
replaceMap, false);
         }
         if (!isAnalyzedPhase) {
-            otherConjuncts = updateExpressions(join.getOtherJoinConjuncts(), 
replaceMap, false);
+            otherConjuncts = doUpdateExpressions(join.getOtherJoinConjuncts(), 
replaceMap, false);
         }
         if (!hashConjuncts.isPresent() && !markConjuncts.isPresent() && 
!otherConjuncts.isPresent()) {
             return join;
         }
-        return join.withJoinConjuncts(
+        return (LogicalJoin<? extends Plan, ? extends Plan>) 
join.withJoinConjuncts(
                 hashConjuncts.orElse(join.getHashJoinConjuncts()),
                 otherConjuncts.orElse(join.getOtherJoinConjuncts()),
                 markConjuncts.orElse(join.getMarkJoinConjuncts()),
@@ -414,7 +423,14 @@ public class AdjustNullable extends 
DefaultPlanRewriter<Map<ExprId, Slot>> imple
     private <T extends Expression> Optional<T> updateExpression(T input,
             Map<ExprId, Slot> replaceMap, boolean debugCheck) {
         AtomicBoolean changed = new AtomicBoolean(false);
-        Expression replaced = input.rewriteDownShortCircuit(e -> {
+        Expression replaced = doUpdateExpression(changed, input, replaceMap, 
!isAnalyzedPhase && debugCheck);
+        return changed.get() ? Optional.of((T) replaced) : Optional.empty();
+    }
+
+    // !isAnalyzedPhase && debugCheck
+    private static Expression doUpdateExpression(AtomicBoolean changed, 
Expression input,
+            Map<ExprId, Slot> replaceMap, boolean check) {
+        return input.rewriteDownShortCircuit(e -> {
             if (e instanceof SlotReference) {
                 SlotReference slotReference = (SlotReference) e;
                 Slot newSlotReference = slotReference;
@@ -443,7 +459,7 @@ public class AdjustNullable extends 
DefaultPlanRewriter<Map<ExprId, Slot>> imple
                 // so analyzed phase don't assert not-nullable -> nullable, 
otherwise adjust plan above
                 // repeat may check fail.
                 if (!slotReference.nullable() && newSlotReference.nullable()
-                        && !isAnalyzedPhase && debugCheck && 
ConnectContext.get() != null) {
+                        && check && ConnectContext.get() != null) {
                     if (ConnectContext.get().getSessionVariable().feDebug) {
                         throw new AnalysisException("AdjustNullable convert 
slot " + slotReference
                                 + " from not-nullable to nullable. You can 
disable check by set fe_debug = false.");
@@ -458,7 +474,6 @@ public class AdjustNullable extends 
DefaultPlanRewriter<Map<ExprId, Slot>> imple
                 return e;
             }
         });
-        return changed.get() ? Optional.of((T) replaced) : Optional.empty();
     }
 
     private <T extends Expression> Optional<List<T>> updateExpressions(List<T> 
inputs,
@@ -484,4 +499,21 @@ public class AdjustNullable extends 
DefaultPlanRewriter<Map<ExprId, Slot>> imple
         }
         return changed ? Optional.of(result.build()) : Optional.empty();
     }
+
+    /**
+     * Static methods cannot use generics, so the following method was added
+     */
+    private static Optional<List<Expression>> 
doUpdateExpressions(List<Expression> inputs,
+            Map<ExprId, Slot> replaceMap, boolean check) {
+        ImmutableList.Builder<Expression> result = 
ImmutableList.builderWithExpectedSize(inputs.size());
+        boolean changed = false;
+        for (Expression input : inputs) {
+            AtomicBoolean eachChanged = new AtomicBoolean(false);
+            Expression newInput = doUpdateExpression(
+                    eachChanged, input, replaceMap, check);
+            changed |= eachChanged.get();
+            result.add(eachChanged.get() ? newInput : input);
+        }
+        return changed ? Optional.of(result.build()) : Optional.empty();
+    }
 }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/util/JoinUtils.java 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/util/JoinUtils.java
index 4de6696bca1..be66fb7cc37 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/nereids/util/JoinUtils.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/util/JoinUtils.java
@@ -26,6 +26,7 @@ import 
org.apache.doris.nereids.properties.DistributionSpecHash;
 import org.apache.doris.nereids.properties.DistributionSpecHash.ShuffleType;
 import org.apache.doris.nereids.properties.DistributionSpecReplicated;
 import org.apache.doris.nereids.properties.PhysicalProperties;
+import org.apache.doris.nereids.rules.rewrite.AdjustNullable;
 import org.apache.doris.nereids.rules.rewrite.ForeignKeyContext;
 import org.apache.doris.nereids.trees.expressions.EqualPredicate;
 import org.apache.doris.nereids.trees.expressions.ExprId;
@@ -53,6 +54,7 @@ import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
 
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
@@ -468,4 +470,23 @@ public class JoinUtils {
         markSlots.retainAll(bottom.getOutputSet());
         return markSlots.isEmpty();
     }
+
+    /**
+     * Use the children nullable property of the join to adjust the slot used 
by the join conjuncts.
+     * Such as 'a left join b left join c where b.condition > 1'
+     * if the join change to '(b left join c) right a where b.condition > 1',
+     * the nullable property of b.condition should be false
+     */
+    public static LogicalJoin<? extends Plan, ? extends Plan> 
adjustJoinConjunctsNullable(
+            LogicalJoin<? extends Plan, ? extends Plan> join) {
+        Map<ExprId, Slot> equalConjunctsSlotMap = new HashMap<>();
+        for (Plan child : join.children()) {
+            for (Slot slot : child.getOutput()) {
+                equalConjunctsSlotMap.put(slot.getExprId(), slot);
+            }
+        }
+        // other conjuncts should use join output slot
+        return AdjustNullable.doVisitLogicalJoin(
+                join, equalConjunctsSlotMap, false, false);
+    }
 }
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/exploration/join/JoinCommuteTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/exploration/join/JoinCommuteTest.java
index 18235b3ce4c..f0774d9b5e6 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/exploration/join/JoinCommuteTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/exploration/join/JoinCommuteTest.java
@@ -18,8 +18,12 @@
 package org.apache.doris.nereids.rules.exploration.join;
 
 import org.apache.doris.common.Pair;
+import org.apache.doris.nereids.trees.expressions.EqualTo;
+import org.apache.doris.nereids.trees.expressions.Expression;
 import org.apache.doris.nereids.trees.expressions.GreaterThan;
+import org.apache.doris.nereids.trees.expressions.SlotReference;
 import org.apache.doris.nereids.trees.plans.JoinType;
+import org.apache.doris.nereids.trees.plans.Plan;
 import org.apache.doris.nereids.trees.plans.logical.LogicalJoin;
 import org.apache.doris.nereids.trees.plans.logical.LogicalOlapScan;
 import org.apache.doris.nereids.trees.plans.logical.LogicalPlan;
@@ -33,6 +37,9 @@ import com.google.common.collect.ImmutableList;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
 
+import java.util.List;
+import java.util.stream.Collectors;
+
 public class JoinCommuteTest implements MemoPatternMatchSupported {
     @Test
     void testInnerJoinCommute() {
@@ -73,4 +80,47 @@ public class JoinCommuteTest implements 
MemoPatternMatchSupported {
                 .applyExploration(JoinCommute.BUSHY.build())
                 .getAllPlan().size());
     }
+
+    @Test
+    void testJoinConjunctNullableWhenCommute() {
+        LogicalOlapScan scan1 = PlanConstructor.newLogicalOlapScan(0, "t1", 0);
+        LogicalOlapScan scan2 = PlanConstructor.newLogicalOlapScan(1, "t2", 0);
+
+        LogicalJoin<?, ?> join = (LogicalJoin<?, ?>) new 
LogicalPlanBuilder(scan1)
+                .join(scan2, JoinType.LEFT_OUTER_JOIN, Pair.of(0, 0))
+                .build();
+        join = join.withJoinConjuncts(
+                ImmutableList.of(new 
EqualTo(scan1.getOutput().get(0).withNullable(true),
+                        scan2.getOutput().get(0))),
+                ImmutableList.of(new 
GreaterThan(scan1.getOutput().get(0).withNullable(true),
+                        scan2.getOutput().get(0))),
+                join.getJoinReorderContext());
+
+        List<Plan> allPlan = 
PlanChecker.from(MemoTestUtils.createConnectContext(), join)
+                .applyExploration(JoinCommute.BUSHY.build())
+                .getAllPlan()
+                .stream()
+                .filter(plan -> plan instanceof LogicalJoin
+                        && ((LogicalJoin<?, ?>) plan).getJoinType() == 
JoinType.RIGHT_OUTER_JOIN)
+                .collect(Collectors.toList());
+        Assertions.assertEquals(1, allPlan.size());
+        // the input slot of join conjuncts should be nullable false after 
commute
+        LogicalJoin<?, ?> newJoin = (LogicalJoin<?, ?>) allPlan.get(0);
+        for (Expression expr : newJoin.getHashJoinConjuncts()) {
+            expr.collectToSet(SlotReference.class::isInstance)
+                    .forEach(slot -> Assertions.assertFalse(((SlotReference) 
slot).nullable()));
+        }
+        for (Expression expr : newJoin.getOtherJoinConjuncts()) {
+            expr.collectToSet(SlotReference.class::isInstance)
+                    .forEach(slot -> {
+                        if (slot.equals(scan1.getOutput().get(0))) {
+                            // the slot from scan1 should be nullable true
+                            Assertions.assertFalse(((SlotReference) 
slot).nullable());
+                        } else {
+                            // the slot from scan2 should be nullable false
+                            Assertions.assertTrue(((SlotReference) 
slot).nullable());
+                        }
+                    });
+        }
+    }
 }
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/exploration/join/OuterJoinAsscomProjectTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/exploration/join/OuterJoinAsscomProjectTest.java
new file mode 100644
index 00000000000..c5d805953b8
--- /dev/null
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/exploration/join/OuterJoinAsscomProjectTest.java
@@ -0,0 +1,105 @@
+// 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.exploration.join;
+
+import org.apache.doris.nereids.trees.expressions.EqualTo;
+import org.apache.doris.nereids.trees.expressions.Expression;
+import org.apache.doris.nereids.trees.expressions.GreaterThan;
+import org.apache.doris.nereids.trees.expressions.NamedExpression;
+import org.apache.doris.nereids.trees.expressions.SlotReference;
+import org.apache.doris.nereids.trees.plans.JoinType;
+import org.apache.doris.nereids.trees.plans.Plan;
+import org.apache.doris.nereids.trees.plans.logical.LogicalJoin;
+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.PlanChecker;
+import org.apache.doris.nereids.util.PlanConstructor;
+
+import com.google.common.collect.ImmutableList;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+public class OuterJoinAsscomProjectTest {
+
+    private final LogicalOlapScan scan1 = 
PlanConstructor.newLogicalOlapScan(0, "t1", 0);
+    private final LogicalOlapScan scan2 = 
PlanConstructor.newLogicalOlapScan(1, "t2", 0);
+    private final LogicalOlapScan scan3 = 
PlanConstructor.newLogicalOlapScan(2, "t3", 0);
+
+    @Test
+    public void testJoinConjunctNullableWhenAssociate() {
+        // t1 left outer join t2
+        List<Expression> bottomHashJoinConjunct = ImmutableList.of(
+                new EqualTo(scan1.getOutput().get(0), 
scan2.getOutput().get(0)));
+        List<Expression> bottomOtherJoinConjunct = ImmutableList.of(
+                new GreaterThan(scan1.getOutput().get(1), 
scan2.getOutput().get(1)));
+        LogicalPlan bottomJoin = new LogicalPlanBuilder(scan1)
+                .join(scan2, JoinType.LEFT_OUTER_JOIN, bottomHashJoinConjunct, 
bottomOtherJoinConjunct)
+                .build();
+        LogicalPlan bottomProject = new LogicalProject<>(
+                
bottomJoin.getOutput().stream().map(NamedExpression.class::cast).collect(Collectors.toList()),
+                bottomJoin);
+
+        // t2 left outer join t3
+        List<Expression> topHashJoinConjunct = ImmutableList.of(
+                new 
EqualTo(bottomProject.getOutput().get(2).withNullable(true), 
scan3.getOutput().get(0)));
+        List<Expression> topOtherJoinConjunct = ImmutableList.of(
+                new 
GreaterThan(bottomProject.getOutput().get(3).withNullable(true), 
scan3.getOutput().get(1)));
+        LogicalPlan topJoin = new LogicalPlanBuilder(bottomProject)
+                .join(scan3, JoinType.LEFT_OUTER_JOIN, topHashJoinConjunct, 
topOtherJoinConjunct)
+                .build();
+        LogicalPlan plan = new LogicalProject<>(
+                
topJoin.getOutput().stream().map(NamedExpression.class::cast).collect(
+                Collectors.toList()), topJoin);
+
+        List<Plan> allPlan = 
PlanChecker.from(MemoTestUtils.createConnectContext(), plan)
+                .printlnOrigin()
+                .applyExploration(OuterJoinAssocProject.INSTANCE.build())
+                .getAllPlan();
+        Assertions.assertEquals(2, allPlan.size());
+
+        // check optimized join plan conjuncts null property, should be false
+        Set<LogicalJoin<Plan, Plan>> joinSet = 
allPlan.get(1).collect(LogicalJoin.class::isInstance);
+        for (LogicalJoin<Plan, Plan> newJoin : joinSet) {
+            Plan child0 = newJoin.child(0);
+            Plan child1 = newJoin.child(1);
+            if ((child0 instanceof LogicalOlapScan && ((LogicalOlapScan) 
child0).getTable().getName().equals("t3"))
+                    || (child1 instanceof LogicalOlapScan
+                    && ((LogicalOlapScan) 
child1).getTable().getName().equals("t3"))) {
+                // equal conjuncts nullable should be same with the join input 
slot
+                for (Expression expr : newJoin.getHashJoinConjuncts()) {
+                    expr.collectToSet(SlotReference.class::isInstance)
+                            .forEach(slot -> 
Assertions.assertFalse(((SlotReference) slot).nullable()));
+                }
+                // other conjuncts nullable should be same with the join 
output slot
+                for (Expression expr : newJoin.getOtherJoinConjuncts()) {
+                    if (expr instanceof GreaterThan) {
+                        Assertions.assertFalse(expr.child(0).nullable());
+                        Assertions.assertTrue(expr.child(1).nullable());
+                    }
+                }
+            }
+        }
+    }
+}
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/exploration/join/OuterJoinLAsscomProjectTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/exploration/join/OuterJoinLAsscomProjectTest.java
index 523a85db64b..e44d8a19762 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/exploration/join/OuterJoinLAsscomProjectTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/exploration/join/OuterJoinLAsscomProjectTest.java
@@ -22,9 +22,14 @@ import 
org.apache.doris.nereids.rules.rewrite.PushDownAliasThroughJoin;
 import org.apache.doris.nereids.trees.expressions.EqualTo;
 import org.apache.doris.nereids.trees.expressions.Expression;
 import org.apache.doris.nereids.trees.expressions.GreaterThan;
+import org.apache.doris.nereids.trees.expressions.NamedExpression;
+import org.apache.doris.nereids.trees.expressions.SlotReference;
 import org.apache.doris.nereids.trees.plans.JoinType;
+import org.apache.doris.nereids.trees.plans.Plan;
+import org.apache.doris.nereids.trees.plans.logical.LogicalJoin;
 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.MemoPatternMatchSupported;
 import org.apache.doris.nereids.util.MemoTestUtils;
@@ -36,6 +41,8 @@ import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
 
 import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
 
 class OuterJoinLAsscomProjectTest implements MemoPatternMatchSupported {
 
@@ -160,4 +167,58 @@ class OuterJoinLAsscomProjectTest implements 
MemoPatternMatchSupported {
                     Assertions.assertEquals(1, 
memo.getRoot().getLogicalExpressions().size());
                 });
     }
+
+    @Test
+    public void testJoinConjunctNullableWhenLAssociate() {
+        // t1 left outer join t2
+        List<Expression> bottomHashJoinConjunct = ImmutableList.of(
+                new EqualTo(scan1.getOutput().get(0), 
scan2.getOutput().get(0)));
+        List<Expression> bottomOtherJoinConjunct = ImmutableList.of(
+                new GreaterThan(scan1.getOutput().get(1), 
scan2.getOutput().get(1)));
+        LogicalPlan bottomJoin = new LogicalPlanBuilder(scan1)
+                .join(scan2, JoinType.LEFT_OUTER_JOIN, bottomHashJoinConjunct, 
bottomOtherJoinConjunct)
+                .build();
+        LogicalPlan bottomProject = new LogicalProject<>(
+                
bottomJoin.getOutput().stream().map(NamedExpression.class::cast).collect(Collectors.toList()),
+                bottomJoin);
+
+        // t1 left outer join t3
+        List<Expression> topHashJoinConjunct = ImmutableList.of(
+                new EqualTo(bottomProject.getOutput().get(0), 
scan3.getOutput().get(0).withNullable(true)));
+        List<Expression> topOtherJoinConjunct = ImmutableList.of(
+                new GreaterThan(bottomProject.getOutput().get(1), 
scan3.getOutput().get(1)));
+        LogicalPlan topJoin = new LogicalPlanBuilder(bottomProject)
+                .join(scan3, JoinType.LEFT_OUTER_JOIN, topHashJoinConjunct, 
topOtherJoinConjunct)
+                .build();
+
+        LogicalPlan plan = new LogicalProject<>(
+                
topJoin.getOutput().stream().map(NamedExpression.class::cast).collect(
+                        Collectors.toList()), topJoin);
+
+        List<Plan> allPlan = 
PlanChecker.from(MemoTestUtils.createConnectContext(), plan)
+                .printlnOrigin()
+                .applyExploration(OuterJoinLAsscomProject.INSTANCE.build())
+                .getAllPlan();
+
+        Assertions.assertEquals(2, allPlan.size());
+        Set<LogicalJoin<Plan, Plan>> joinSet = 
allPlan.get(1).collect(LogicalJoin.class::isInstance);
+        for (LogicalJoin<Plan, Plan> newJoin : joinSet) {
+            Plan child0 = newJoin.child(0);
+            Plan child1 = newJoin.child(1);
+            if ((child0 instanceof LogicalOlapScan && ((LogicalOlapScan) 
child0).getTable().getName().equals("t3"))
+                    || (child1 instanceof LogicalOlapScan
+                    && ((LogicalOlapScan) 
child1).getTable().getName().equals("t3"))) {
+                for (Expression expr : newJoin.getHashJoinConjuncts()) {
+                    expr.collectToSet(SlotReference.class::isInstance)
+                            .forEach(slot -> 
Assertions.assertFalse(((SlotReference) slot).nullable()));
+                }
+                for (Expression expr : newJoin.getOtherJoinConjuncts()) {
+                    if (expr instanceof GreaterThan) {
+                        Assertions.assertFalse(expr.child(0).nullable());
+                        Assertions.assertTrue(expr.child(1).nullable());
+                    }
+                }
+            }
+        }
+    }
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to