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]