This is an automated email from the ASF dual-hosted git repository.
Mryange 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 43f9ceb48d7 [fix](join) Preserve input nullability for ASOF match
conditions (#69630)
43f9ceb48d7 is described below
commit 43f9ceb48d73d157e18a7a96a53e7b62df756e88
Author: Mryange <[email protected]>
AuthorDate: Wed Oct 28 15:16:49 2026 +0800
[fix](join) Preserve input nullability for ASOF match conditions (#69630)
### What problem does this PR solve?
Issue Number: N/A
Related PR: #69616
Problem Summary: Outer ASOF joins can declare a NOT NULL input
expression as nullable while BE evaluates the match key before null
padding. Root cause: the final `AdjustNullable` pass uses join output
nullability for the match condition. Preserve child input nullability
for ASOF match conditions, retaining existing behavior for ordinary
joins.
### Release note
Fix incorrect nullability in ASOF JOIN match conditions.
### Check List (For Author)
- Test: Added FE unit coverage for all four ASOF join types and a
regular LEFT OUTER JOIN control; tests not run locally. Targeted
Checkstyle passed with zero violations.
- Manual diagnosis: The full CTAS reproducer fails on master with
nullable output normalization removed
(`declared=Nullable(DateTimeV2(0)), actual=DATETIMEV2`). No post-fix
build or runtime validation performed.
- Behavior changed: Yes. ASOF match conditions retain child input
nullability.
- Does this need documentation: No
---
.../nereids/rules/rewrite/AdjustNullable.java | 8 ++-
.../nereids/rules/rewrite/AdjustNullableTest.java | 68 ++++++++++++++++++++++
2 files changed, 73 insertions(+), 3 deletions(-)
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 73c2fe24c7d..4c6a93cb8b3 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
@@ -194,9 +194,11 @@ public class AdjustNullable extends
DefaultPlanRewriter<Map<ExprId, Slot>> imple
// then later at rewrite phase, the join conditions may push down,
and the push down condition with proper
// nullable attribute.
// 2) when at the end of rewrite phase, update other conjuncts with
join output nullables.
- // Just change it to be consistent with BE.
+ // Just change it to be consistent with BE. ASOF match conditions
are evaluated on child input blocks before
+ // outer-join null padding, so they keep the child nullable
attributes in both phases.
Optional<List<Expression>> otherConjuncts = Optional.empty();
- if (isAnalyzedPhase) {
+ boolean useJoinOutputNullableForOtherConjuncts = !isAnalyzedPhase &&
!join.getJoinType().isAsofJoin();
+ if (!useJoinOutputNullableForOtherConjuncts) {
otherConjuncts = doUpdateExpressions(join.getOtherJoinConjuncts(),
replaceMap, false);
}
for (Slot slot : join.getOutput()) {
@@ -205,7 +207,7 @@ public class AdjustNullable extends
DefaultPlanRewriter<Map<ExprId, Slot>> imple
if (!hadUpdatedMarkConjuncts) {
markConjuncts = doUpdateExpressions(join.getMarkJoinConjuncts(),
replaceMap, false);
}
- if (!isAnalyzedPhase) {
+ if (useJoinOutputNullableForOtherConjuncts) {
otherConjuncts = doUpdateExpressions(join.getOtherJoinConjuncts(),
replaceMap, false);
}
if (!hashConjuncts.isPresent() && !markConjuncts.isPresent() &&
!otherConjuncts.isPresent()) {
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/AdjustNullableTest.java
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/AdjustNullableTest.java
new file mode 100644
index 00000000000..ee05d8f4378
--- /dev/null
+++
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/AdjustNullableTest.java
@@ -0,0 +1,68 @@
+// 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.rewrite;
+
+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.logical.LogicalJoin;
+import org.apache.doris.nereids.trees.plans.logical.LogicalOlapScan;
+import org.apache.doris.nereids.util.LogicalPlanBuilder;
+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.HashMap;
+
+class AdjustNullableTest {
+
+ @Test
+ void testAsofMatchConditionUsesChildNullability() {
+ assertOtherConjunctNullability(JoinType.ASOF_LEFT_INNER_JOIN, false);
+ assertOtherConjunctNullability(JoinType.ASOF_RIGHT_INNER_JOIN, false);
+ assertOtherConjunctNullability(JoinType.ASOF_LEFT_OUTER_JOIN, false);
+ assertOtherConjunctNullability(JoinType.ASOF_RIGHT_OUTER_JOIN, false);
+ }
+
+ @Test
+ void testRegularOuterJoinOtherConditionUsesJoinOutputNullability() {
+ assertOtherConjunctNullability(JoinType.LEFT_OUTER_JOIN, true);
+ }
+
+ private void assertOtherConjunctNullability(JoinType joinType, boolean
rightNullable) {
+ LogicalOlapScan left = new
LogicalOlapScan(PlanConstructor.getNextRelationId(), PlanConstructor.student,
+ ImmutableList.of(""));
+ LogicalOlapScan right = new
LogicalOlapScan(PlanConstructor.getNextRelationId(), PlanConstructor.student,
+ ImmutableList.of(""));
+ Expression hashCondition = new EqualTo(left.getOutput().get(0),
right.getOutput().get(0));
+ Expression matchCondition = new GreaterThan(left.getOutput().get(1),
right.getOutput().get(1));
+ LogicalJoin<?, ?> join = (LogicalJoin<?, ?>) new
LogicalPlanBuilder(left)
+ .join(right, joinType, ImmutableList.of(hashCondition),
ImmutableList.of(matchCondition))
+ .build();
+
+ LogicalJoin<?, ?> adjustedJoin =
AdjustNullable.doVisitLogicalJoin(join, new HashMap<>(), false, false);
+ GreaterThan adjustedCondition = (GreaterThan)
adjustedJoin.getOtherJoinConjuncts().get(0);
+
+ Assertions.assertFalse(((SlotReference)
adjustedCondition.left()).nullable());
+ Assertions.assertEquals(rightNullable, ((SlotReference)
adjustedCondition.right()).nullable());
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]