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

mrhhsg pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/master by this push:
     new fea01e63555 [fix](join) Eliminate joins with never-true conditions 
(#66806)
fea01e63555 is described below

commit fea01e635555c42e9090931ed654dc7afb6895e9
Author: Jerry Hu <[email protected]>
AuthorDate: Mon Aug 31 15:36:14 2026 +0800

    [fix](join) Eliminate joins with never-true conditions (#66806)
    
    ### What problem does this PR solve?
    
    Issue Number: None
    
    Related PR: None
    
    Problem Summary:
    
    A one-sided outer join whose condition folds to `FALSE` or `NULL` still
    remained in the logical plan. Although the nullable side can only
    produce NULL-padded columns, the optimizer did not expose those columns
    as uniform NULL values. Consequently, a later null-rejecting inner-join
    predicate could fail to fold to an empty relation and execute very large
    nested-loop intermediates for a query whose result is already known to
    be empty.
    
    This change:
    
    - replaces constant-false/NULL inner and cross joins with an empty
    relation;
    - replaces constant-false/NULL left and right outer joins with a
    projection over the preserved child plus typed NULL aliases, while
    preserving output expression IDs, names, qualifiers, types, and order;
    - reapplies join-condition elimination immediately after constant
    propagation rewrites join predicates.
    
    Mark joins, semi/anti joins, full outer joins, and ASOF joins retain
    their existing behavior.
    
    ### Release note
    
    Improve query performance by eliminating joins whose conditions are
    constant `FALSE` or `NULL`.
    
    ### Check List (For Author)
    
    - Test:
    - Unit Test: `./run-fe-ut.sh --run
    org.apache.doris.nereids.rules.rewrite.EliminateJoinConditionTest` (5
    tests passed)
    - Regression test:
    `nereids_rules_p0/constant_propagation/constant_propagation`
    (regenerated with `-forceGenOut`, then passed without `-forceGenOut`)
    - Behavior changed: Yes (semantically redundant joins are removed; query
    results are unchanged)
    - Does this need documentation: No
---
 .../nereids/rules/rewrite/ConstantPropagation.java |   4 +-
 .../rewrite/EliminateConstHashJoinCondition.java   |  20 +--
 .../rules/rewrite/EliminateJoinCondition.java      |  89 +++++++++---
 .../rules/rewrite/EliminateJoinConditionTest.java  | 158 +++++++++++++++++++++
 .../nereids/rules/rewrite/InferPredicatesTest.java |  23 ++-
 .../constant_propagation/constant_propagation.out  |   6 +-
 .../test_null_uniform_join.out                     |  18 +++
 .../test_null_uniform_join.groovy                  |  70 +++++++++
 8 files changed, 349 insertions(+), 39 deletions(-)

diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/ConstantPropagation.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/ConstantPropagation.java
index 1d77eb02b73..12570f95035 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/ConstantPropagation.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/ConstantPropagation.java
@@ -277,13 +277,15 @@ public class ConstantPropagation extends 
DefaultPlanRewriter<CascadesContext> im
             joinType = JoinType.INNER_JOIN;
         }
 
-        return new LogicalJoin<>(joinType,
+        LogicalJoin<Plan, Plan> rewrittenJoin = new LogicalJoin<>(joinType,
                 newHashJoinConjuncts,
                 newOtherJoinConjuncts,
                 join.getMarkJoinConjuncts(),
                 join.getDistributeHint(),
                 join.getMarkJoinSlotReference(),
                 join.children(), join.getJoinReorderContext());
+        Plan eliminatedJoin = 
EliminateJoinCondition.eliminateJoinCondition(rewrittenJoin);
+        return eliminatedJoin == null ? rewrittenJoin : eliminatedJoin;
     }
 
     @Override
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/EliminateConstHashJoinCondition.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/EliminateConstHashJoinCondition.java
index 5aef6d1e947..6bd007b253b 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/EliminateConstHashJoinCondition.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/EliminateConstHashJoinCondition.java
@@ -63,18 +63,20 @@ public class EliminateConstHashJoinCondition extends 
OneRewriteRuleFactory {
                         && ((EqualTo) expr).right() instanceof SlotReference) {
                     EqualTo equal = (EqualTo) 
JoinUtils.swapEqualToForChildrenOrder((EqualTo) expr,
                             join.left().getOutputSet());
+                    Slot leftSlot = (Slot) equal.left();
+                    Slot rightSlot = (Slot) equal.right();
                     Optional<Expression> leftValue = 
join.left().getLogicalProperties()
-                            .getTrait().getUniformValue((Slot) equal.left());
+                            .getTrait().getUniformValue(leftSlot);
 
                     Optional<Expression> rightValue = 
join.right().getLogicalProperties()
-                            .getTrait().getUniformValue((Slot) equal.right());
-                    if (leftValue != null && rightValue != null) {
-                        if (leftValue.isPresent() && rightValue.isPresent()) {
-                            if (leftValue.get().equals(rightValue.get())) {
-                                eliminate = true;
-                                changed = true;
-                            }
-                        }
+                            .getTrait().getUniformValue(rightSlot);
+                    if (leftValue.isPresent() && rightValue.isPresent()
+                            && 
join.left().getLogicalProperties().getTrait().isUniformAndNotNull(leftSlot)
+                            && 
join.right().getLogicalProperties().getTrait().isUniformAndNotNull(rightSlot)
+                            && leftValue.get().equals(rightValue.get())) {
+                        // Ordinary equality can be removed only when both 
uniform values cannot be NULL.
+                        eliminate = true;
+                        changed = true;
                     }
                 }
                 if (!eliminate) {
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/EliminateJoinCondition.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/EliminateJoinCondition.java
index 23625afb583..2e84dce0f2e 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/EliminateJoinCondition.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/EliminateJoinCondition.java
@@ -19,36 +19,89 @@ 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.Alias;
 import org.apache.doris.nereids.trees.expressions.Expression;
+import org.apache.doris.nereids.trees.expressions.NamedExpression;
+import org.apache.doris.nereids.trees.expressions.Slot;
+import org.apache.doris.nereids.trees.expressions.StatementScopeIdGenerator;
 import org.apache.doris.nereids.trees.expressions.literal.BooleanLiteral;
+import org.apache.doris.nereids.trees.expressions.literal.NullLiteral;
+import org.apache.doris.nereids.trees.plans.Plan;
+import org.apache.doris.nereids.trees.plans.logical.LogicalEmptyRelation;
+import org.apache.doris.nereids.trees.plans.logical.LogicalJoin;
+import org.apache.doris.nereids.trees.plans.logical.LogicalProject;
+
+import com.google.common.collect.ImmutableList;
 
 import java.util.List;
+import java.util.Set;
 import java.util.stream.Collectors;
 
 /**
- * Eliminate true Condition in Join Condition.
+ * Eliminate constant conditions in Join Condition.
  */
 public class EliminateJoinCondition extends OneRewriteRuleFactory {
 
     @Override
     public Rule build() {
-        return logicalJoin().then(join -> {
-            List<Expression> hashJoinConjuncts = 
join.getHashJoinConjuncts().stream()
-                    .filter(expression -> 
!expression.equals(BooleanLiteral.TRUE))
-                    .collect(Collectors.toList());
-            List<Expression> otherJoinConjuncts = 
join.getOtherJoinConjuncts().stream()
-                    .filter(expression -> 
!expression.equals(BooleanLiteral.TRUE))
-                    .collect(Collectors.toList());
-            List<Expression> markJoinConjuncts = 
join.getMarkJoinConjuncts().stream()
-                    .filter(expression -> 
!expression.equals(BooleanLiteral.TRUE))
-                    .collect(Collectors.toList());
-            if (hashJoinConjuncts.size() == join.getHashJoinConjuncts().size()
-                    && otherJoinConjuncts.size() == 
join.getOtherJoinConjuncts().size()
-                    && markJoinConjuncts.size() == 
join.getMarkJoinConjuncts().size()) {
-                return null;
+        return logicalJoin()
+                .then(EliminateJoinCondition::eliminateJoinCondition)
+                .toRule(RuleType.ELIMINATE_JOIN_CONDITION);
+    }
+
+    static Plan eliminateJoinCondition(LogicalJoin<? extends Plan, ? extends 
Plan> join) {
+        List<Expression> hashJoinConjuncts = 
removeTrueConjuncts(join.getHashJoinConjuncts());
+        List<Expression> otherJoinConjuncts = 
removeTrueConjuncts(join.getOtherJoinConjuncts());
+        List<Expression> markJoinConjuncts = 
removeTrueConjuncts(join.getMarkJoinConjuncts());
+
+        if (!join.isMarkJoin() && (containsFalseOrNull(hashJoinConjuncts)
+                || containsFalseOrNull(otherJoinConjuncts))) {
+            switch (join.getJoinType()) {
+                case INNER_JOIN:
+                case CROSS_JOIN:
+                    return new 
LogicalEmptyRelation(StatementScopeIdGenerator.newRelationId(), 
join.getOutput());
+                case LEFT_OUTER_JOIN:
+                    return projectNullPaddedJoinOutput(join, join.left());
+                case RIGHT_OUTER_JOIN:
+                    return projectNullPaddedJoinOutput(join, join.right());
+                default:
+                    break;
+            }
+        }
+
+        if (hashJoinConjuncts.size() == join.getHashJoinConjuncts().size()
+                && otherJoinConjuncts.size() == 
join.getOtherJoinConjuncts().size()
+                && markJoinConjuncts.size() == 
join.getMarkJoinConjuncts().size()) {
+            return null;
+        }
+        return join.withJoinConjuncts(hashJoinConjuncts, otherJoinConjuncts, 
markJoinConjuncts,
+                join.getJoinReorderContext());
+    }
+
+    private static List<Expression> removeTrueConjuncts(List<Expression> 
conjuncts) {
+        return conjuncts.stream()
+                .filter(expression -> !expression.equals(BooleanLiteral.TRUE))
+                .collect(Collectors.toList());
+    }
+
+    private static boolean containsFalseOrNull(List<Expression> conjuncts) {
+        return conjuncts.stream()
+                .anyMatch(expression -> 
expression.equals(BooleanLiteral.FALSE) || expression.isNullLiteral());
+    }
+
+    private static LogicalProject<Plan> projectNullPaddedJoinOutput(
+            LogicalJoin<? extends Plan, ? extends Plan> join, Plan 
preservedChild) {
+        Set<Slot> preservedOutput = preservedChild.getOutputSet();
+        ImmutableList.Builder<NamedExpression> projects =
+                ImmutableList.builderWithExpectedSize(join.getOutput().size());
+        for (Slot output : join.getOutput()) {
+            if (preservedOutput.contains(output)) {
+                projects.add(output);
+            } else {
+                projects.add(new Alias(output.getExprId(), new 
NullLiteral(output.getDataType()),
+                        output.getName()));
             }
-            return join.withJoinConjuncts(hashJoinConjuncts, 
otherJoinConjuncts, markJoinConjuncts,
-                        join.getJoinReorderContext());
-        }).toRule(RuleType.ELIMINATE_JOIN_CONDITION);
+        }
+        return new LogicalProject<>(projects.build(), preservedChild);
     }
 }
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/EliminateJoinConditionTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/EliminateJoinConditionTest.java
index 37acd78e027..a08fa16e9f2 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/EliminateJoinConditionTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/EliminateJoinConditionTest.java
@@ -17,10 +17,22 @@
 
 package org.apache.doris.nereids.rules.rewrite;
 
+import org.apache.doris.nereids.trees.expressions.Alias;
+import org.apache.doris.nereids.trees.expressions.Cast;
+import org.apache.doris.nereids.trees.expressions.EqualTo;
+import org.apache.doris.nereids.trees.expressions.NamedExpression;
+import org.apache.doris.nereids.trees.expressions.Slot;
+import org.apache.doris.nereids.trees.expressions.functions.scalar.If;
 import org.apache.doris.nereids.trees.expressions.literal.BooleanLiteral;
+import org.apache.doris.nereids.trees.expressions.literal.IntegerLiteral;
+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.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.types.IntegerType;
 import org.apache.doris.nereids.util.LogicalPlanBuilder;
 import org.apache.doris.nereids.util.MemoPatternMatchSupported;
 import org.apache.doris.nereids.util.MemoTestUtils;
@@ -28,11 +40,17 @@ 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;
+
 class EliminateJoinConditionTest implements MemoPatternMatchSupported {
     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);
+    private final LogicalOlapScan scan4 = 
PlanConstructor.newLogicalOlapScan(3, "t4", 0);
 
     @Test
     void basicCase() {
@@ -48,4 +66,144 @@ class EliminateJoinConditionTest implements 
MemoPatternMatchSupported {
                                 && join.getOtherJoinConjuncts().size() == 0)
                 );
     }
+
+    @Test
+    void eliminateInnerJoinWithFalseCondition() {
+        LogicalPlan join = new LogicalPlanBuilder(scan1)
+                .join(scan2, JoinType.INNER_JOIN, ImmutableList.of(), 
ImmutableList.of(BooleanLiteral.FALSE))
+                .build();
+
+        PlanChecker.from(MemoTestUtils.createConnectContext(), join)
+                .applyTopDown(new EliminateJoinCondition())
+                .matches(logicalEmptyRelation());
+    }
+
+    @Test
+    void eliminateCrossJoinWithFalseCondition() {
+        LogicalPlan join = new LogicalPlanBuilder(scan1)
+                .join(scan2, JoinType.CROSS_JOIN, ImmutableList.of(), 
ImmutableList.of(BooleanLiteral.FALSE))
+                .build();
+
+        PlanChecker.from(MemoTestUtils.createConnectContext(), join)
+                .applyTopDown(new EliminateJoinCondition())
+                .matches(logicalEmptyRelation());
+    }
+
+    @Test
+    void eliminateLeftOuterJoinWithNullCondition() {
+        LogicalPlan join = new LogicalPlanBuilder(scan1)
+                .join(scan2, JoinType.LEFT_OUTER_JOIN, ImmutableList.of(),
+                        ImmutableList.of(NullLiteral.BOOLEAN_INSTANCE))
+                .build();
+
+        assertNullPaddedProject(join, scan1);
+    }
+
+    @Test
+    void eliminateRightOuterJoinWithFalseCondition() {
+        LogicalPlan join = new LogicalPlanBuilder(scan1)
+                .join(scan2, JoinType.RIGHT_OUTER_JOIN, ImmutableList.of(), 
ImmutableList.of(BooleanLiteral.FALSE))
+                .build();
+
+        assertNullPaddedProject(join, scan2);
+    }
+
+    private void assertNullPaddedProject(LogicalPlan join, LogicalPlan 
preservedChild) {
+        List<Slot> originalOutput = join.getOutput();
+        Set<Slot> preservedOutput = preservedChild.getOutputSet();
+
+        LogicalPlan rewritten = (LogicalPlan) 
PlanChecker.from(MemoTestUtils.createConnectContext(), join)
+                .applyTopDown(new EliminateJoinCondition())
+                .getPlan();
+        Assertions.assertInstanceOf(LogicalProject.class, rewritten);
+        LogicalProject<?> project = (LogicalProject<?>) rewritten;
+        Assertions.assertEquals(preservedChild, project.child());
+        Assertions.assertEquals(originalOutput, project.getOutput());
+        for (int i = 0; i < originalOutput.size(); i++) {
+            NamedExpression projectExpression = project.getProjects().get(i);
+            assertSlotContract(originalOutput.get(i), 
project.getOutput().get(i));
+            if (preservedOutput.contains(originalOutput.get(i))) {
+                Assertions.assertEquals(originalOutput.get(i), 
projectExpression);
+            } else {
+                Assertions.assertInstanceOf(Alias.class, projectExpression);
+                Assertions.assertInstanceOf(NullLiteral.class, 
projectExpression.child(0));
+                Assertions.assertEquals(originalOutput.get(i).getDataType(), 
projectExpression.child(0).getDataType());
+            }
+        }
+    }
+
+    private void assertSlotContract(Slot expected, Slot actual) {
+        Assertions.assertEquals(expected.getExprId(), actual.getExprId());
+        Assertions.assertEquals(expected.getName(), actual.getName());
+        Assertions.assertEquals(expected.getDataType(), actual.getDataType());
+        Assertions.assertEquals(expected.nullable(), actual.nullable());
+    }
+
+    @Test
+    void propagateNullPaddedOutputToInnerJoinInSamePass() {
+        Slot scan1Slot = scan1.getOutput().get(0);
+        LogicalPlan filteredScan1 = new LogicalPlanBuilder(scan1)
+                .filter(new EqualTo(scan1Slot, new IntegerLiteral(1)))
+                .build();
+        LogicalPlan leftOuterJoin = new LogicalPlanBuilder(filteredScan1)
+                .join(scan2, JoinType.LEFT_OUTER_JOIN, ImmutableList.of(),
+                        ImmutableList.of(new EqualTo(scan1Slot, new 
IntegerLiteral(2))))
+                .build();
+        Slot nullPaddedSlot = 
leftOuterJoin.getOutput().get(filteredScan1.getOutput().size());
+        LogicalPlan innerJoin = new LogicalPlanBuilder(leftOuterJoin)
+                .join(scan3, JoinType.INNER_JOIN, ImmutableList.of(),
+                        ImmutableList.of(new EqualTo(
+                                new If(BooleanLiteral.TRUE, nullPaddedSlot, 
nullPaddedSlot),
+                                scan3.getOutput().get(0))))
+                .build();
+
+        PlanChecker.from(MemoTestUtils.createConnectContext(), innerJoin)
+                .applyCustom(new ConstantPropagation())
+                .matches(logicalEmptyRelation());
+    }
+
+    @Test
+    void keepEqualToBetweenNullPaddedOutputs() {
+        LogicalPlan leftOuterJoin = new LogicalPlanBuilder(scan1)
+                .join(scan2, JoinType.LEFT_OUTER_JOIN, ImmutableList.of(), 
ImmutableList.of(BooleanLiteral.FALSE))
+                .build();
+        LogicalPlan rightOuterJoin = new LogicalPlanBuilder(scan3)
+                .join(scan4, JoinType.LEFT_OUTER_JOIN, ImmutableList.of(), 
ImmutableList.of(BooleanLiteral.FALSE))
+                .build();
+        Slot leftNullPaddedSlot = 
leftOuterJoin.getOutput().get(scan1.getOutput().size());
+        Slot rightNullPaddedSlot = 
rightOuterJoin.getOutput().get(scan3.getOutput().size());
+        LogicalPlan innerJoin = new LogicalPlanBuilder(leftOuterJoin)
+                .join(rightOuterJoin, JoinType.INNER_JOIN,
+                        ImmutableList.of(new EqualTo(leftNullPaddedSlot, 
rightNullPaddedSlot)), ImmutableList.of())
+                .build();
+
+        Plan rewritten = 
PlanChecker.from(MemoTestUtils.createConnectContext(), innerJoin)
+                .applyBottomUp(new EliminateJoinCondition())
+                .applyTopDown(new EliminateConstHashJoinCondition())
+                .getPlan();
+        Assertions.assertInstanceOf(LogicalJoin.class, rewritten);
+        Assertions.assertEquals(1, ((LogicalJoin<?, ?>) 
rewritten).getHashJoinConjuncts().size());
+    }
+
+    @Test
+    void keepEqualToBetweenNullableUniformExpressions() {
+        Alias leftNull = new Alias(new Cast(NullLiteral.INSTANCE, 
IntegerType.INSTANCE), "null_key");
+        Alias rightNull = new Alias(new Cast(NullLiteral.INSTANCE, 
IntegerType.INSTANCE), "null_key");
+        LogicalPlan leftProject = new LogicalPlanBuilder(scan1)
+                .projectExprs(ImmutableList.of(leftNull))
+                .build();
+        LogicalPlan rightProject = new LogicalPlanBuilder(scan2)
+                .projectExprs(ImmutableList.of(rightNull))
+                .build();
+        LogicalPlan innerJoin = new LogicalPlanBuilder(leftProject)
+                .join(rightProject, JoinType.INNER_JOIN,
+                        ImmutableList.of(new EqualTo(leftNull.toSlot(), 
rightNull.toSlot())), ImmutableList.of())
+                .build();
+
+        Plan rewritten = 
PlanChecker.from(MemoTestUtils.createConnectContext(), innerJoin)
+                .applyTopDown(new EliminateConstHashJoinCondition())
+                .getPlan();
+        Assertions.assertInstanceOf(LogicalJoin.class, rewritten);
+        Assertions.assertEquals(1, ((LogicalJoin<?, ?>) 
rewritten).getHashJoinConjuncts().size());
+    }
 }
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/InferPredicatesTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/InferPredicatesTest.java
index 1b1bf8afcf2..bbb4d3b7a27 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/InferPredicatesTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/InferPredicatesTest.java
@@ -19,10 +19,12 @@ package org.apache.doris.nereids.rules.rewrite;
 
 import org.apache.doris.nereids.CascadesContext;
 import org.apache.doris.nereids.hint.DistributeHint;
+import org.apache.doris.nereids.trees.expressions.Alias;
 import org.apache.doris.nereids.trees.expressions.EqualTo;
 import org.apache.doris.nereids.trees.expressions.Expression;
 import org.apache.doris.nereids.trees.expressions.MarkJoinSlotReference;
 import org.apache.doris.nereids.trees.expressions.literal.IntegerLiteral;
+import org.apache.doris.nereids.trees.expressions.literal.NullLiteral;
 import org.apache.doris.nereids.trees.plans.DistributeType;
 import org.apache.doris.nereids.trees.plans.JoinType;
 import org.apache.doris.nereids.trees.plans.logical.LogicalFilter;
@@ -635,7 +637,9 @@ class InferPredicatesTest extends TestWithFeService 
implements MemoPatternMatchS
     }
 
     /**
-     * in this case, filter on relation s1 should not contain s1.id = 1.
+     * In this case, filter on relation s1 should not contain s1.id = 1. 
Constant propagation can eliminate
+     * the left outer join because s1.id = 2 makes its s1.id = 1 conjunct 
false, so verify the eliminated
+     * join keeps the s2 columns as NULL while preserving only the s1.id = 2 
filter on the left child.
      */
     @Test
     void innerJoinShouldNotInferUnderLeftJoinOnClausePredicates() {
@@ -648,13 +652,18 @@ class InferPredicatesTest extends TestWithFeService 
implements MemoPatternMatchS
                 .printlnTree()
                 .matches(logicalProject(
                         logicalJoin(
-                                logicalFilter(
-                                        logicalOlapScan()
-                                ).when(filter -> filter.getConjuncts().size() 
== 1
-                                        && 
!ExpressionUtils.isInferred(filter.getPredicate())
-                                        && 
filter.getPredicate().toSql().contains("id = 2")),
+                                logicalProject(
+                                        logicalFilter(
+                                                logicalOlapScan()
+                                        ).when(filter -> 
filter.getConjuncts().size() == 1
+                                                && 
!ExpressionUtils.isInferred(filter.getPredicate())
+                                                && 
filter.getPredicate().toSql().contains("id = 2"))
+                                ).when(project -> 
project.getProjects().stream()
+                                        .filter(expression -> expression 
instanceof Alias
+                                                && expression.child(0) 
instanceof NullLiteral)
+                                        .count() == 3),
                                 any()
-                        ).when(join -> join.getJoinType() == 
JoinType.LEFT_OUTER_JOIN)
+                        ).when(join -> join.getJoinType() == 
JoinType.INNER_JOIN)
                 ));
     }
 
diff --git 
a/regression-test/data/nereids_rules_p0/constant_propagation/constant_propagation.out
 
b/regression-test/data/nereids_rules_p0/constant_propagation/constant_propagation.out
index 9158c626398..2a16f1f4711 100644
--- 
a/regression-test/data/nereids_rules_p0/constant_propagation/constant_propagation.out
+++ 
b/regression-test/data/nereids_rules_p0/constant_propagation/constant_propagation.out
@@ -96,10 +96,8 @@ PhysicalResultSink
 
 -- !join_2_shape --
 PhysicalResultSink
---NestedLoopJoin[LEFT_OUTER_JOIN]
-----PhysicalProject[t1.a]
-------PhysicalOlapScan[t1]
-----PhysicalEmptyRelation
+--PhysicalProject[NULL AS `x`, t1.a]
+----PhysicalOlapScan[t1]
 
 -- !join_2_result --
 1      \N
diff --git 
a/regression-test/data/nereids_rules_p0/constant_propagation/test_null_uniform_join.out
 
b/regression-test/data/nereids_rules_p0/constant_propagation/test_null_uniform_join.out
new file mode 100644
index 00000000000..29625377c3a
--- /dev/null
+++ 
b/regression-test/data/nereids_rules_p0/constant_propagation/test_null_uniform_join.out
@@ -0,0 +1,18 @@
+-- This file is automatically generated. You should know what you did if you 
want to edit this
+-- !null_padded_equality_plan --
+PhysicalResultSink
+--PhysicalProject
+----hashJoin[INNER_JOIN broadcast] hashCondition=((left_input.padded_id = 
right_input.padded_id)) otherCondition=()
+------PhysicalProject
+--------PhysicalOlapScan[test_null_uniform_join_left(l)]
+------PhysicalProject
+--------PhysicalOlapScan[test_null_uniform_join_right(l)]
+
+-- !cast_null_equality_plan --
+PhysicalResultSink
+--PhysicalProject
+----hashJoin[INNER_JOIN broadcast] hashCondition=((left_input.null_key = 
right_input.null_key)) otherCondition=()
+------PhysicalProject
+--------PhysicalOlapScan[test_null_uniform_join_left]
+------PhysicalProject
+--------PhysicalOlapScan[test_null_uniform_join_right]
diff --git 
a/regression-test/suites/nereids_rules_p0/constant_propagation/test_null_uniform_join.groovy
 
b/regression-test/suites/nereids_rules_p0/constant_propagation/test_null_uniform_join.groovy
new file mode 100644
index 00000000000..e9388b4f18e
--- /dev/null
+++ 
b/regression-test/suites/nereids_rules_p0/constant_propagation/test_null_uniform_join.groovy
@@ -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.
+
+suite("test_null_uniform_join") {
+    sql "SET disable_nereids_rules='INFER_JOIN_NOT_NULL'"
+
+    sql "DROP TABLE IF EXISTS test_null_uniform_join_left"
+    sql "DROP TABLE IF EXISTS test_null_uniform_join_right"
+
+    sql """
+        CREATE TABLE test_null_uniform_join_left (
+            id INT
+        ) DISTRIBUTED BY HASH(id) BUCKETS 1
+        PROPERTIES ("replication_num" = "1")
+    """
+    sql """
+        CREATE TABLE test_null_uniform_join_right (
+            id INT
+        ) DISTRIBUTED BY HASH(id) BUCKETS 1
+        PROPERTIES ("replication_num" = "1")
+    """
+
+    sql "INSERT INTO test_null_uniform_join_left VALUES (1), (2)"
+    sql "INSERT INTO test_null_uniform_join_right VALUES (3), (4)"
+
+    qt_null_padded_equality_plan """
+        EXPLAIN SHAPE PLAN
+        SELECT left_input.preserved_id, right_input.preserved_id
+        FROM (
+            SELECT l.id AS preserved_id, r.id AS padded_id
+            FROM test_null_uniform_join_left l
+            LEFT JOIN test_null_uniform_join_right r ON FALSE
+        ) left_input
+        INNER JOIN (
+            SELECT l.id AS preserved_id, r.id AS padded_id
+            FROM test_null_uniform_join_right l
+            LEFT JOIN test_null_uniform_join_left r ON FALSE
+        ) right_input
+        ON left_input.padded_id = right_input.padded_id
+    """
+
+    qt_cast_null_equality_plan """
+        EXPLAIN SHAPE PLAN
+        SELECT left_input.id, right_input.id
+        FROM (
+            SELECT id, CAST(NULL AS INT) AS null_key
+            FROM test_null_uniform_join_left
+        ) left_input
+        INNER JOIN (
+            SELECT id, CAST(NULL AS INT) AS null_key
+            FROM test_null_uniform_join_right
+        ) right_input
+        ON left_input.null_key = right_input.null_key
+    """
+
+}


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

Reply via email to