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]

Reply via email to