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]