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

englefly 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 e53d775b8d3 [fix](nereids) when Expression has no more than 2 
children, the attribute hasUnbound is not set correctly  (#60152)
e53d775b8d3 is described below

commit e53d775b8d3b59714e42d452e3da627fd779ace3
Author: minghong <[email protected]>
AuthorDate: Wed Jan 28 17:06:24 2026 +0800

    [fix](nereids) when Expression has no more than 2 children, the attribute 
hasUnbound is not set correctly  (#60152)
    
    ### What problem does this PR solve?
    
    Issue Number: close #xxx
    
    Related PR: #32617
---
 .../nereids/trees/expressions/Expression.java      |  51 +----
 .../trees/expressions/ExpressionUnboundTest.java   | 233 +++++++++++++++++++++
 2 files changed, 237 insertions(+), 47 deletions(-)

diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/Expression.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/Expression.java
index 0bf6ff95013..cd9aba84cfe 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/Expression.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/Expression.java
@@ -52,6 +52,7 @@ import com.google.common.collect.ImmutableSet.Builder;
 import com.google.common.collect.Lists;
 import org.apache.commons.lang3.StringUtils;
 
+import java.util.Arrays;
 import java.util.List;
 import java.util.Optional;
 import java.util.Set;
@@ -87,53 +88,7 @@ public abstract class Expression extends 
AbstractTreeNode<Expression> implements
     private final Supplier<Integer> hashCodeCache = 
LazyCompute.of(this::computeHashCode);
 
     protected Expression(Expression... children) {
-        super(children);
-
-        boolean hasUnbound = false;
-        switch (children.length) {
-            case 0:
-                this.depth = 1;
-                this.width = 1;
-                this.compareWidthAndDepth = supportCompareWidthAndDepth();
-                this.fastChildrenHashCode = 0;
-                break;
-            case 1:
-                Expression child = children[0];
-                this.depth = child.depth + 1;
-                this.width = child.width;
-                this.compareWidthAndDepth = child.compareWidthAndDepth && 
supportCompareWidthAndDepth();
-                this.fastChildrenHashCode = child.fastChildrenHashCode() + 1;
-                break;
-            case 2:
-                Expression left = children[0];
-                Expression right = children[1];
-                this.depth = Math.max(left.depth, right.depth) + 1;
-                this.width = left.width + right.width;
-                this.compareWidthAndDepth =
-                        left.compareWidthAndDepth && 
right.compareWidthAndDepth && supportCompareWidthAndDepth();
-                this.fastChildrenHashCode = left.fastChildrenHashCode() + 
right.fastChildrenHashCode() + 2;
-                break;
-            default:
-                int maxChildDepth = 0;
-                int sumChildWidth = 0;
-                boolean compareWidthAndDepth = true;
-                int fastChildrenHashCode = 0;
-                for (Expression expression : children) {
-                    child = expression;
-                    maxChildDepth = Math.max(child.depth, maxChildDepth);
-                    sumChildWidth += child.width;
-                    hasUnbound |= child.hasUnbound;
-                    compareWidthAndDepth &= child.compareWidthAndDepth;
-                    fastChildrenHashCode = fastChildrenHashCode + 
expression.fastChildrenHashCode() + 1;
-                }
-                this.depth = maxChildDepth + 1;
-                this.width = sumChildWidth;
-                this.compareWidthAndDepth = compareWidthAndDepth;
-                this.fastChildrenHashCode = fastChildrenHashCode;
-        }
-        checkLimit();
-        this.inferred = false;
-        this.hasUnbound = hasUnbound || this instanceof Unbound;
+        this(Arrays.asList(children));
     }
 
     protected Expression(List<Expression> children) {
@@ -157,6 +112,7 @@ public abstract class Expression extends 
AbstractTreeNode<Expression> implements
                 this.width = child.width;
                 this.compareWidthAndDepth = child.compareWidthAndDepth && 
supportCompareWidthAndDepth();
                 this.fastChildrenHashCode = child.fastChildrenHashCode() + 1;
+                hasUnbound = child.hasUnbound();
                 break;
             case 2:
                 Expression left = children.get(0);
@@ -166,6 +122,7 @@ public abstract class Expression extends 
AbstractTreeNode<Expression> implements
                 this.compareWidthAndDepth =
                         left.compareWidthAndDepth && 
right.compareWidthAndDepth && supportCompareWidthAndDepth();
                 this.fastChildrenHashCode = left.fastChildrenHashCode() + 
right.fastChildrenHashCode() + 2;
+                hasUnbound = left.hasUnbound() || right.hasUnbound();
                 break;
             default:
                 int maxChildDepth = 0;
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/ExpressionUnboundTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/ExpressionUnboundTest.java
new file mode 100644
index 00000000000..78fc821f336
--- /dev/null
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/ExpressionUnboundTest.java
@@ -0,0 +1,233 @@
+// 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.trees.expressions;
+
+import org.apache.doris.nereids.trees.expressions.visitor.ExpressionVisitor;
+import org.apache.doris.nereids.types.DataType;
+import org.apache.doris.nereids.types.IntegerType;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+
+/**
+ * Test for Expression hasUnbound assignment logic.
+ */
+public class ExpressionUnboundTest {
+
+    /**
+     * Mock bound expression for testing.
+     */
+    private static class MockBoundExpression extends Expression {
+        public MockBoundExpression() {
+            super();
+        }
+
+        public MockBoundExpression(Expression... children) {
+            super(children);
+        }
+
+        public MockBoundExpression(List<Expression> children) {
+            super(children);
+        }
+
+        @Override
+        public <R, C> R accept(ExpressionVisitor<R, C> visitor, C context) {
+            return null;
+        }
+
+        @Override
+        public DataType getDataType() {
+            return IntegerType.INSTANCE;
+        }
+
+        @Override
+        public boolean nullable() {
+            return false;
+        }
+
+        @Override
+        public Expression withChildren(List<Expression> children) {
+            return new MockBoundExpression(children);
+        }
+
+        @Override
+        protected String computeToSql() {
+            return toSql();
+        }
+    }
+
+    /**
+     * Mock unbound expression for testing.
+     */
+    private static class MockUnboundExpression extends Expression {
+        public MockUnboundExpression() {
+            super();
+        }
+
+        public MockUnboundExpression(Expression... children) {
+            super(children);
+        }
+
+        public MockUnboundExpression(List<Expression> children) {
+            super(children);
+        }
+
+        @Override
+        public <R, C> R accept(ExpressionVisitor<R, C> visitor, C context) {
+            return null;
+        }
+
+        @Override
+        public DataType getDataType() {
+            return IntegerType.INSTANCE;
+        }
+
+        @Override
+        public boolean nullable() {
+            return false;
+        }
+
+        @Override
+        public Expression withChildren(List<Expression> children) {
+            return new MockUnboundExpression(children);
+        }
+
+        @Override
+        protected String computeToSql() {
+            return toSql();
+        }
+
+        // Override hasUnbound to return true for testing
+        @Override
+        public boolean hasUnbound() {
+            return true;
+        }
+    }
+
+    /**
+     * Test case 1: Single bound child (line 115 logic).
+     * When there's one bound child, parent should be bound.
+     */
+    @Test
+    public void testSingleBoundChild() {
+        // Create a bound child expression
+        MockBoundExpression boundChild = new MockBoundExpression();
+        Assertions.assertFalse(boundChild.hasUnbound(), "Bound child should 
not have unbound");
+
+        // Create parent with single bound child
+        MockBoundExpression parent = new MockBoundExpression(boundChild);
+
+        // Verify line 115: hasUnbound = child.hasUnbound();
+        Assertions.assertFalse(parent.hasUnbound(), "Parent with bound child 
should not have unbound");
+    }
+
+    /**
+     * Test case 2: Single unbound child (line 115 logic).
+     * When there's one unbound child, parent should be unbound.
+     */
+    @Test
+    public void testSingleUnboundChild() {
+        // Create an unbound child expression
+        MockUnboundExpression unboundChild = new MockUnboundExpression();
+        Assertions.assertTrue(unboundChild.hasUnbound(), "Unbound child should 
have unbound");
+
+        // Create parent with single unbound child
+        MockBoundExpression parent = new MockBoundExpression(unboundChild);
+
+        // Verify line 115: hasUnbound = child.hasUnbound();
+        Assertions.assertTrue(parent.hasUnbound(), "Parent with unbound child 
should have unbound");
+    }
+
+    /**
+     * Test case 3: Two bound children (line 125 logic).
+     * When both children are bound, parent should be bound.
+     */
+    @Test
+    public void testTwoBoundChildren() {
+        // Create two bound child expressions
+        MockBoundExpression leftChild = new MockBoundExpression();
+        MockBoundExpression rightChild = new MockBoundExpression();
+        Assertions.assertFalse(leftChild.hasUnbound(), "Left bound child 
should not have unbound");
+        Assertions.assertFalse(rightChild.hasUnbound(), "Right bound child 
should not have unbound");
+
+        // Create parent with two bound children
+        MockBoundExpression parent = new MockBoundExpression(leftChild, 
rightChild);
+
+        // Verify line 125: hasUnbound = left.hasUnbound() || 
right.hasUnbound();
+        Assertions.assertFalse(parent.hasUnbound(), "Parent with two bound 
children should not have unbound");
+    }
+
+    /**
+     * Test case 4: Left unbound, right bound (line 125 logic).
+     * When left child is unbound, parent should be unbound.
+     */
+    @Test
+    public void testLeftUnboundRightBound() {
+        // Create unbound left and bound right child
+        MockUnboundExpression leftChild = new MockUnboundExpression();
+        MockBoundExpression rightChild = new MockBoundExpression();
+        Assertions.assertTrue(leftChild.hasUnbound(), "Left unbound child 
should have unbound");
+        Assertions.assertFalse(rightChild.hasUnbound(), "Right bound child 
should not have unbound");
+
+        // Create parent with left unbound, right bound
+        MockBoundExpression parent = new MockBoundExpression(leftChild, 
rightChild);
+
+        // Verify line 125: hasUnbound = left.hasUnbound() || 
right.hasUnbound();
+        Assertions.assertTrue(parent.hasUnbound(), "Parent with left unbound 
child should have unbound");
+    }
+
+    /**
+     * Test case 5: Left bound, right unbound (line 125 logic).
+     * When right child is unbound, parent should be unbound.
+     */
+    @Test
+    public void testLeftBoundRightUnbound() {
+        // Create bound left and unbound right child
+        MockBoundExpression leftChild = new MockBoundExpression();
+        MockUnboundExpression rightChild = new MockUnboundExpression();
+        Assertions.assertFalse(leftChild.hasUnbound(), "Left bound child 
should not have unbound");
+        Assertions.assertTrue(rightChild.hasUnbound(), "Right unbound child 
should have unbound");
+
+        // Create parent with left bound, right unbound
+        MockBoundExpression parent = new MockBoundExpression(leftChild, 
rightChild);
+
+        // Verify line 125: hasUnbound = left.hasUnbound() || 
right.hasUnbound();
+        Assertions.assertTrue(parent.hasUnbound(), "Parent with right unbound 
child should have unbound");
+    }
+
+    /**
+     * Test case 6: Both children unbound (line 125 logic).
+     * When both children are unbound, parent should be unbound.
+     */
+    @Test
+    public void testTwoUnboundChildren() {
+        // Create two unbound child expressions
+        MockUnboundExpression leftChild = new MockUnboundExpression();
+        MockUnboundExpression rightChild = new MockUnboundExpression();
+        Assertions.assertTrue(leftChild.hasUnbound(), "Left unbound child 
should have unbound");
+        Assertions.assertTrue(rightChild.hasUnbound(), "Right unbound child 
should have unbound");
+
+        // Create parent with two unbound children
+        MockBoundExpression parent = new MockBoundExpression(leftChild, 
rightChild);
+
+        // Verify line 125: hasUnbound = left.hasUnbound() || 
right.hasUnbound();
+        Assertions.assertTrue(parent.hasUnbound(), "Parent with two unbound 
children should have unbound");
+    }
+}


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

Reply via email to