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

yiguolei pushed a commit to branch branch-4.2
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/branch-4.2 by this push:
     new 301d2914708 branch-4.2: [fix](analyzer) Decide a GROUP BY / ORDER BY 
ordinal on the value the user wrote #68096 (#70047)
301d2914708 is described below

commit 301d2914708ddf369f69a02b378a4d05e34540ae
Author: morrySnow <[email protected]>
AuthorDate: Wed Nov 4 08:08:59 2026 +0800

    branch-4.2: [fix](analyzer) Decide a GROUP BY / ORDER BY ordinal on the 
value the user wrote #68096 (#70047)
    
    picked from master #68096
    
    Co-authored-by: Ambuj Upadhyay <[email protected]>
---
 .../doris/nereids/parser/LogicalPlanBuilder.java   | 10 ++--
 .../nereids/rules/analysis/BindExpression.java     | 15 ++++--
 .../nereids/parser/LogicalPlanBuilderTest.java     | 24 ++++++++++
 .../nereids/rules/analysis/BindExpressionTest.java | 53 ++++++++++++++++++++++
 4 files changed, 96 insertions(+), 6 deletions(-)

diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java
index b9932c79f10..58741bb3ffe 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java
@@ -4553,9 +4553,13 @@ public class LogicalPlanBuilder extends 
DorisParserBaseVisitor<Object> {
             List<OrderKey> newOrderKeys = sort.getOrderKeys().stream()
                     .map(key -> {
                         if (key.getExpr() instanceof IntegerLikeLiteral) {
-                            return key.withExpression(
-                                    new UnboundSlot(String.valueOf(
-                                            ((IntegerLikeLiteral) 
key.getExpr()).getIntValue())));
+                            // The slot is named after the literal, so 
narrowing it renames it:
+                            // getIntValue() truncates to the low 32 bits, 
turning
+                            // `order by 4294967297` into a reference to "1". 
Keep the digits
+                            // the user wrote.
+                            return key.withExpression(new UnboundSlot(
+                                    ((IntegerLikeLiteral) key.getExpr())
+                                            
.getBigDecimalValue().toBigIntegerExact().toString()));
                         }
                         return key;
                     })
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/BindExpression.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/BindExpression.java
index b86fabbd9c1..fcad7c8bdca 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/BindExpression.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/BindExpression.java
@@ -139,6 +139,7 @@ import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 import org.jetbrains.annotations.NotNull;
 
+import java.math.BigDecimal;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
@@ -2026,9 +2027,17 @@ public class BindExpression implements 
AnalysisRuleFactory {
     private Expression bindWithOrdinal(
             Expression unbound, SimpleExprAnalyzer analyzer, List<? extends 
Expression> boundSelectOutput) {
         if (unbound instanceof IntegerLikeLiteral) {
-            int ordinal = ((IntegerLikeLiteral) unbound).getIntValue();
-            if (ordinal >= 1 && ordinal <= boundSelectOutput.size()) {
-                Expression boundSelectItem = boundSelectOutput.get(ordinal - 
1);
+            // Decide positional-vs-constant on the value the user actually 
wrote.
+            // getIntValue() is getNumber().intValue(), which truncates a 
BIGINT or a
+            // LARGEINT to its low 32 bits, so an ordinal congruent to a valid 
one
+            // modulo 2^32 passed this test and bound to a real select item:
+            // `GROUP BY 4294967297` silently became `GROUP BY <select item 
1>`.
+            // LargeIntLiteral overrides getBigDecimalValue but not 
getLongValue, so
+            // the BigDecimal form is the only one that is width independent.
+            BigDecimal ordinal = ((IntegerLikeLiteral) 
unbound).getBigDecimalValue();
+            if (ordinal.compareTo(BigDecimal.ONE) >= 0
+                    && 
ordinal.compareTo(BigDecimal.valueOf(boundSelectOutput.size())) <= 0) {
+                Expression boundSelectItem = 
boundSelectOutput.get(ordinal.intValue() - 1);
                 return boundSelectItem instanceof Alias ? 
boundSelectItem.child(0) : boundSelectItem;
             } else {
                 return unbound; // bound literal
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/parser/LogicalPlanBuilderTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/parser/LogicalPlanBuilderTest.java
index 9690e24ea85..59c413cf82c 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/parser/LogicalPlanBuilderTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/parser/LogicalPlanBuilderTest.java
@@ -18,6 +18,7 @@
 package org.apache.doris.nereids.parser;
 
 import org.apache.doris.nereids.analyzer.UnboundSlot;
+import org.apache.doris.nereids.trees.expressions.Expression;
 import org.apache.doris.nereids.trees.expressions.literal.IntegerLikeLiteral;
 import org.apache.doris.nereids.trees.plans.commands.DeleteFromCommand;
 import org.apache.doris.nereids.trees.plans.commands.DeleteFromUsingCommand;
@@ -248,4 +249,27 @@ public class LogicalPlanBuilderTest {
         // Second key: column name remains as UnboundSlot
         Assertions.assertInstanceOf(UnboundSlot.class, 
sort.getOrderKeys().get(1).getExpr());
     }
+
+    @Test
+    public void testDeleteAndUpdateOrderByWideOrdinalKeepsItsDigits() {
+        // The ordinal becomes an UnboundSlot named after the literal, so 
narrowing the literal
+        // renames the slot. 4294967297 is 2^32 + 1, which getIntValue() 
turned into "1".
+        // 18446744073709551617 is 2^64 + 1, a LARGEINT, which getLongValue() 
also narrows to 1,
+        // so it pins the width-independent accessor rather than merely a 
wider one.
+        for (String ordinal : new String[] {"4294967297", 
"18446744073709551617"}) {
+            assertFirstOrderKeyIsSlotNamed("DELETE FROM t ORDER BY " + ordinal 
+ " LIMIT 10", ordinal);
+            assertFirstOrderKeyIsSlotNamed("UPDATE t SET c1 = 10 ORDER BY " + 
ordinal + " LIMIT 10", ordinal);
+        }
+    }
+
+    private void assertFirstOrderKeyIsSlotNamed(String sql, String 
expectedName) {
+        LogicalPlan plan = parser.parseSingle(sql);
+        LogicalPlan query = plan instanceof UpdateCommand
+                ? ((UpdateCommand) plan).getLogicalQuery()
+                : ((DeleteFromUsingCommand) plan).getLogicalQuery();
+        LogicalSort<?> sort = (LogicalSort<?>) ((LogicalLimit<?>) 
query).child();
+        Expression key = sort.getOrderKeys().get(0).getExpr();
+        Assertions.assertInstanceOf(UnboundSlot.class, key, sql);
+        Assertions.assertEquals(expectedName, ((UnboundSlot) key).getName(), 
sql);
+    }
 }
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/analysis/BindExpressionTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/analysis/BindExpressionTest.java
index 10360ce3128..00cbfb2232e 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/analysis/BindExpressionTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/analysis/BindExpressionTest.java
@@ -19,6 +19,7 @@ package org.apache.doris.nereids.rules.analysis;
 
 import org.apache.doris.catalog.Column;
 import org.apache.doris.catalog.PrimitiveType;
+import org.apache.doris.common.NereidsException;
 import org.apache.doris.datasource.iceberg.IcebergMergeOperation;
 import org.apache.doris.datasource.iceberg.IcebergUtils;
 import org.apache.doris.nereids.exceptions.AnalysisException;
@@ -27,10 +28,12 @@ import org.apache.doris.nereids.rules.RulePromise;
 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.SlotReference;
 import org.apache.doris.nereids.trees.expressions.literal.IntegerLiteral;
 import org.apache.doris.nereids.trees.expressions.literal.TinyIntLiteral;
 import org.apache.doris.nereids.trees.plans.JoinType;
+import org.apache.doris.nereids.trees.plans.logical.LogicalSort;
 import org.apache.doris.nereids.types.SmallIntType;
 import org.apache.doris.nereids.types.StringType;
 import org.apache.doris.nereids.types.VariantType;
@@ -57,6 +60,56 @@ class BindExpressionTest extends TestWithFeService 
implements GeneratedPlanPatte
         );
     }
 
+    @Test
+    void testGroupByOrdinalIsNotNarrowed() {
+        // getIntValue() is getNumber().intValue(), so a BIGINT or LARGEINT 
ordinal was truncated
+        // to its low 32 bits before the `>= 1 && <= selectItems` test. 
4294967297 is 2^32 + 1 and
+        // 18446744073709551617 is 2^64 + 1: both truncated to 1 and silently 
bound to the first
+        // select item, while the plainly out-of-range 3 did not. The LARGEINT 
narrows to 1 through
+        // getLongValue() as well, so it pins the width-independent accessor, 
not merely a wider one.
+        String outOfRange = "select col1, count(*) from t1 group by 3";
+
+        // As a constant, the literal leaves col1 ungrouped. 
checkPlannerResult surfaces that as
+        // NereidsException, and every out-of-range ordinal must fail for that 
same reason.
+        NereidsException expected = 
Assertions.assertThrows(NereidsException.class,
+                () -> 
PlanChecker.from(connectContext).checkPlannerResult(outOfRange),
+                "an ordinal past the end of the select list must not bind");
+        for (String ordinal : new String[] {"4294967297", 
"18446744073709551617"}) {
+            String wrapsToOne = "select col1, count(*) from t1 group by " + 
ordinal;
+            NereidsException actual = 
Assertions.assertThrows(NereidsException.class,
+                    () -> 
PlanChecker.from(connectContext).checkPlannerResult(wrapsToOne),
+                    ordinal + " must not be narrowed into range");
+            Assertions.assertEquals(expected.getMessage(), actual.getMessage(),
+                    ordinal + " must be rejected for the same reason as the 
out-of-range ordinal");
+        }
+    }
+
+    @Test
+    void testOrderByOrdinalIsNotNarrowed() {
+        // bindWithOrdinal also serves ORDER BY, on a plain select and on a 
set operation. There an
+        // out-of-range ordinal does not throw -- it stays a constant sort key 
-- so the check is on
+        // the analyzed plan: a wide ordinal must never turn into a sort on a 
column.
+        String[] queries = {
+                "select col1, col2 from t1 order by %s",
+                "select col1, col2 from t1 union all select col1, col2 from t2 
order by %s",
+        };
+        for (String query : queries) {
+            // The check can tell the two apart: a real ordinal does sort on a 
column.
+            PlanChecker.from(connectContext)
+                    .analyze(String.format(query, "1"))
+                    
.matches(logicalSort().when(BindExpressionTest::sortsOnASlot));
+            for (String ordinal : new String[] {"4294967297", 
"18446744073709551617"}) {
+                PlanChecker.from(connectContext)
+                        .analyze(String.format(query, ordinal))
+                        
.nonMatch(logicalSort().when(BindExpressionTest::sortsOnASlot));
+            }
+        }
+    }
+
+    private static boolean sortsOnASlot(LogicalSort<?> sort) {
+        return sort.getOrderKeys().stream().anyMatch(key -> key.getExpr() 
instanceof Slot);
+    }
+
     @Test
     void testJoin() {
         for (JoinType joinType : JoinType.values()) {


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

Reply via email to