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

xuzifu666 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/calcite.git


The following commit(s) were added to refs/heads/main by this push:
     new 17af1848df [CALCITE-7374] NULLS LAST throws ClassCastException when 
sorting arrays
17af1848df is described below

commit 17af1848dff8a6f213e34119e97b63649bd3a211
Author: Zhen Chen <[email protected]>
AuthorDate: Thu Jan 15 23:14:10 2026 +0800

    [CALCITE-7374] NULLS LAST throws ClassCastException when sorting arrays
---
 .../adapter/enumerable/EnumerableMergeUnion.java   | 21 +++++++----
 .../java/org/apache/calcite/plan/RelTraitSet.java  | 16 ++++++++
 core/src/test/resources/sql/sort.iq                | 43 ++++++++++++++++++++++
 .../apache/calcite/linq4j/function/Functions.java  | 28 ++++++++++----
 4 files changed, 92 insertions(+), 16 deletions(-)

diff --git 
a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableMergeUnion.java
 
b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableMergeUnion.java
index 637a454c21..d06fa31661 100644
--- 
a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableMergeUnion.java
+++ 
b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableMergeUnion.java
@@ -22,8 +22,10 @@
 import org.apache.calcite.linq4j.tree.Expressions;
 import org.apache.calcite.linq4j.tree.ParameterExpression;
 import org.apache.calcite.plan.RelOptCluster;
+import org.apache.calcite.plan.RelTrait;
 import org.apache.calcite.plan.RelTraitSet;
 import org.apache.calcite.rel.RelCollation;
+import org.apache.calcite.rel.RelCollationTraitDef;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.util.BuiltInMethod;
 import org.apache.calcite.util.Pair;
@@ -42,17 +44,20 @@ public class EnumerableMergeUnion extends EnumerableUnion {
   protected EnumerableMergeUnion(RelOptCluster cluster, RelTraitSet traitSet,
       List<RelNode> inputs, boolean all) {
     super(cluster, traitSet, inputs, all);
-    final RelCollation collation = traitSet.getCollation();
-    if (collation == null || collation.getFieldCollations().isEmpty()) {
+    final List<RelCollation> collations = traitSet.getCollations();
+    if (collations.isEmpty() || 
collations.get(0).getFieldCollations().isEmpty()) {
       throw new IllegalArgumentException("EnumerableMergeUnion with no 
collation");
     }
     for (RelNode input : inputs) {
-      final RelCollation inputCollation = input.getTraitSet().getCollation();
-      if (inputCollation == null || !inputCollation.satisfies(collation)) {
-        throw new IllegalArgumentException("EnumerableMergeUnion input does "
-            + "not satisfy collation. EnumerableMergeUnion collation: "
-            + collation + ". Input collation: " + inputCollation + ". Input: "
-            + input);
+      final RelTrait inputCollationTrait =
+          input.getTraitSet().getTrait(RelCollationTraitDef.INSTANCE);
+      for (RelCollation collation : collations) {
+        if (inputCollationTrait == null || 
!inputCollationTrait.satisfies(collation)) {
+          throw new IllegalArgumentException("EnumerableMergeUnion input does "
+              + "not satisfy collation. EnumerableMergeUnion collation: "
+              + collation + ". Input collation: " + inputCollationTrait + ". 
Input: "
+              + input);
+        }
       }
     }
   }
diff --git a/core/src/main/java/org/apache/calcite/plan/RelTraitSet.java 
b/core/src/main/java/org/apache/calcite/plan/RelTraitSet.java
index b91b660752..6574742606 100644
--- a/core/src/main/java/org/apache/calcite/plan/RelTraitSet.java
+++ b/core/src/main/java/org/apache/calcite/plan/RelTraitSet.java
@@ -392,6 +392,22 @@ public RelTraitSet getDefaultSansConvention() {
     return (@Nullable T) getTrait(RelCollationTraitDef.INSTANCE);
   }
 
+  /**
+   * Returns {@link RelCollation} traits defined by
+   * {@link RelCollationTraitDef#INSTANCE}.
+   */
+  @SuppressWarnings("unchecked")
+  public List<RelCollation> getCollations() {
+    RelCollation trait = getTrait(RelCollationTraitDef.INSTANCE);
+    if (trait == null) {
+      return ImmutableList.of();
+    }
+    if (trait instanceof RelCompositeTrait) {
+      return ((RelCompositeTrait<RelCollation>) trait).traitList();
+    }
+    return ImmutableList.of(trait);
+  }
+
   /**
    * Returns the size of the RelTraitSet.
    *
diff --git a/core/src/test/resources/sql/sort.iq 
b/core/src/test/resources/sql/sort.iq
index fb69970e41..b9e0412ff9 100644
--- a/core/src/test/resources/sql/sort.iq
+++ b/core/src/test/resources/sql/sort.iq
@@ -465,4 +465,47 @@ order by arr desc nulls first;
 
 !ok
 
+# [CALCITE-7374] NULLS LAST throws ClassCastException when sorting arrays
+select * from
+(values
+    (2, array[null, 3]),
+    (3, array[3, 4]),
+    (1, array[1, 2]),
+    (4, array[4, 5]),
+    (5, cast(null as integer array))) as t(id, arr)
+order by arr nulls last;
++----+-----------+
+| ID | ARR       |
++----+-----------+
+|  1 | [1, 2]    |
+|  3 | [3, 4]    |
+|  4 | [4, 5]    |
+|  2 | [null, 3] |
+|  5 |           |
++----+-----------+
+(5 rows)
+
+!ok
+
+select * from
+(values
+    (2, array[null, 3]),
+    (3, array[3, 4]),
+    (1, array[1, 2]),
+    (4, array[4, 5]),
+    (5, cast(null as integer array))) as t(id, arr)
+order by arr desc nulls last;
++----+-----------+
+| ID | ARR       |
++----+-----------+
+|  2 | [null, 3] |
+|  4 | [4, 5]    |
+|  3 | [3, 4]    |
+|  1 | [1, 2]    |
+|  5 |           |
++----+-----------+
+(5 rows)
+
+!ok
+
 # End sort.iq
diff --git 
a/linq4j/src/main/java/org/apache/calcite/linq4j/function/Functions.java 
b/linq4j/src/main/java/org/apache/calcite/linq4j/function/Functions.java
index 73a06f42bd..6e9a186344 100644
--- a/linq4j/src/main/java/org/apache/calcite/linq4j/function/Functions.java
+++ b/linq4j/src/main/java/org/apache/calcite/linq4j/function/Functions.java
@@ -556,7 +556,8 @@ private static class NullsFirstComparator
       } else if (o1 instanceof Object[] && o2 instanceof Object[]) {
         return compareObjectArrays((Object[]) o1, (Object[]) o2);
       } else {
-        throw new IllegalArgumentException();
+        throw new IllegalArgumentException("Item types do not match: "
+            + o1.getClass() + " vs " + o2.getClass());
       }
     }
   }
@@ -582,7 +583,8 @@ private static class NullsLastComparator
       } else if (o1 instanceof Object[] && o2 instanceof Object[]) {
         return compareObjectArrays((Object[]) o1, (Object[]) o2);
       } else {
-        throw new IllegalArgumentException();
+        throw new IllegalArgumentException("Item types do not match: "
+            + o1.getClass() + " vs " + o2.getClass());
       }
     }
   }
@@ -590,7 +592,7 @@ private static class NullsLastComparator
   /** Nulls first reverse comparator. */
   private static class NullsFirstReverseComparator
       implements Comparator<Object>, Serializable  {
-    @Override public int compare(Object o1, Object o2) {
+    @Override public int compare(@Nullable Object o1, @Nullable Object o2) {
       if (o1 == o2) {
         return 0;
       }
@@ -608,7 +610,8 @@ private static class NullsFirstReverseComparator
       } else if (o1 instanceof Object[] && o2 instanceof Object[]) {
         return -compareObjectArrays((Object[]) o1, (Object[]) o2);
       } else {
-        throw new IllegalArgumentException();
+        throw new IllegalArgumentException("Item types do not match: "
+            + o1.getClass() + " vs " + o2.getClass());
       }
     }
   }
@@ -707,8 +710,8 @@ public static int compareObjectArrays(@Nullable Object 
@Nullable [] b0,
 
   /** Nulls last reverse comparator. */
   private static class NullsLastReverseComparator
-      implements Comparator<Comparable>, Serializable  {
-    @Override public int compare(Comparable o1, Comparable o2) {
+      implements Comparator<Object>, Serializable  {
+    @Override public int compare(@Nullable Object o1, @Nullable Object o2) {
       if (o1 == o2) {
         return 0;
       }
@@ -718,8 +721,17 @@ private static class NullsLastReverseComparator
       if (o2 == null) {
         return -1;
       }
-      //noinspection unchecked
-      return -o1.compareTo(o2);
+      if (o1 instanceof Comparable && o2 instanceof Comparable) {
+        //noinspection unchecked
+        return -((Comparable) o1).compareTo(o2);
+      } else if (o1 instanceof List && o2 instanceof List) {
+        return -compareLists((List<?>) o1, (List<?>) o2);
+      } else if (o1 instanceof Object[] && o2 instanceof Object[]) {
+        return -compareObjectArrays((Object[]) o1, (Object[]) o2);
+      } else {
+        throw new IllegalArgumentException("Item types do not match: "
+            + o1.getClass() + " vs " + o2.getClass());
+      }
     }
   }
 

Reply via email to