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

zhenchen 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 ae42f777f0 [CALCITE-4813] ANY_VALUE assumes that arguments should be 
comparable
ae42f777f0 is described below

commit ae42f777f0b2e81086101f7d9e89573d1bf3b7c6
Author: Zhen Chen <[email protected]>
AuthorDate: Tue Dec 23 07:06:07 2025 +0800

    [CALCITE-4813] ANY_VALUE assumes that arguments should be comparable
---
 .../org/apache/calcite/runtime/SqlFunctions.java   | 62 ++++++++++++++---
 core/src/test/resources/sql/blank.iq               | 77 ++++++++++++++++++++++
 .../apache/calcite/linq4j/function/Functions.java  | 75 ++++++++++++++++-----
 3 files changed, 191 insertions(+), 23 deletions(-)

diff --git a/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java 
b/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
index 52031cf494..dc4c6c2587 100644
--- a/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
+++ b/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
@@ -2242,7 +2242,11 @@ public static boolean lt(List<?> b0, List<?> b1) {
     return Functions.compareLists(b0, b1) < 0;
   }
 
-  public static boolean lt(Object[] b0, Object[] b1) {
+  public static boolean lt(Map<?, ?> b0, Map<?, ?> b1) {
+    return Functions.compareMaps(b0, b1) < 0;
+  }
+
+  public static boolean lt(@Nullable Object @Nullable [] b0, @Nullable Object 
@Nullable [] b1) {
     return Functions.compareObjectArrays(b0, b1) < 0;
   }
 
@@ -2292,7 +2296,7 @@ public static boolean le(List<?> b0, List<?> b1) {
   }
 
   /** SQL <code>&le;</code> operator applied to Object[] values. */
-  public static boolean le(Object[] b0, Object[] b1) {
+  public static boolean le(@Nullable Object @Nullable [] b0, @Nullable Object 
@Nullable [] b1) {
     return Functions.compareObjectArrays(b0, b1) <= 0;
   }
 
@@ -2375,7 +2379,11 @@ public static boolean gt(List<?> b0, List<?> b1) {
     return Functions.compareLists(b0, b1) > 0;
   }
 
-  public static boolean gt(Object[] b0, Object[] b1) {
+  public static boolean gt(Map<?, ?> b0, Map<?, ?> b1) {
+    return Functions.compareMaps(b0, b1) > 0;
+  }
+
+  public static boolean gt(@Nullable Object @Nullable [] b0, @Nullable Object 
@Nullable [] b1) {
     return Functions.compareObjectArrays(b0, b1) > 0;
   }
 
@@ -2426,7 +2434,7 @@ public static boolean ge(List<?> b0, List<?> b1) {
   }
 
   /** SQL <code>&ge;</code> operator applied to Object[] values. */
-  public static boolean ge(Object[] b0, Object[] b1) {
+  public static boolean ge(@Nullable Object @Nullable [] b0, @Nullable Object 
@Nullable [] b1) {
     return Functions.compareObjectArrays(b0, b1) >= 0;
   }
 
@@ -4641,8 +4649,27 @@ public static double lesser(double b0, double b1) {
     return b0 > b1 ? b1 : b0;
   }
 
-  public static @Nullable <T extends Comparable<T>> List<T> lesser(
-      @Nullable List<T> b0, @Nullable List<T> b1) {
+  public static @Nullable List lesser(@Nullable List b0, @Nullable List b1) {
+    if (b0 == null) {
+      return b1;
+    }
+    if (b1 == null) {
+      return b0;
+    }
+    return lt(b0, b1) ? b0 : b1;
+  }
+
+  public static @Nullable Map lesser(@Nullable Map b0, @Nullable Map b1) {
+    if (b0 == null) {
+      return b1;
+    }
+    if (b1 == null) {
+      return b0;
+    }
+    return lt(b0, b1) ? b0 : b1;
+  }
+
+  public static @Nullable Object[] lesser(@Nullable Object[] b0, @Nullable 
Object[] b1) {
     if (b0 == null) {
       return b1;
     }
@@ -4652,8 +4679,27 @@ public static double lesser(double b0, double b1) {
     return lt(b0, b1) ? b0 : b1;
   }
 
-  public static @Nullable <T extends Comparable<T>> List<T> greater(
-      @Nullable List<T> b0, @Nullable List<T> b1) {
+  public static @Nullable List greater(@Nullable List b0, @Nullable List b1) {
+    if (b0 == null) {
+      return b1;
+    }
+    if (b1 == null) {
+      return b0;
+    }
+    return gt(b0, b1) ? b0 : b1;
+  }
+
+  public static @Nullable Map greater(@Nullable Map b0, @Nullable Map b1) {
+    if (b0 == null) {
+      return b1;
+    }
+    if (b1 == null) {
+      return b0;
+    }
+    return gt(b0, b1) ? b0 : b1;
+  }
+
+  public static @Nullable Object[] greater(@Nullable Object[] b0, @Nullable 
Object[] b1) {
     if (b0 == null) {
       return b1;
     }
diff --git a/core/src/test/resources/sql/blank.iq 
b/core/src/test/resources/sql/blank.iq
index 9c200caf7e..c44c39530a 100644
--- a/core/src/test/resources/sql/blank.iq
+++ b/core/src/test/resources/sql/blank.iq
@@ -154,4 +154,81 @@ select * from table1 where j not in (select i from table2) 
or j = 3;
 
 !ok
 
+# [CALCITE-4813] ANY_VALUE assumes that arguments should be comparable
+select any_value(r) over(), s from(select array[f, s] r, s from (select 1 as 
f, 2 as s) t) t;
++--------+---+
+| EXPR$0 | S |
++--------+---+
+| [1, 2] | 2 |
++--------+---+
+(1 row)
+
+!ok
+
+select any_value(r) over(), s from(select map[f, s] r, s from (select 1 as f, 
2 as s) t) t;
++--------+---+
+| EXPR$0 | S |
++--------+---+
+| {1=2}  | 2 |
++--------+---+
+(1 row)
+
+!ok
+
+select any_value(r) over(), s from(select row(f, s) r, s from (select 1 as f, 
2 as s) t) t;
++--------+---+
+| EXPR$0 | S |
++--------+---+
+| {1, 2} | 2 |
++--------+---+
+(1 row)
+
+!ok
+
+
+CREATE TABLE complex_t (
+    a INTEGER ARRAY,
+    m MAP<VARCHAR, DOUBLE>,
+    r ROW(r1 VARCHAR, r2 INTEGER, r3 VARCHAR)
+);
+(0 rows modified)
+
+!update
+
+INSERT INTO complex_t VALUES (
+    ARRAY[1, 2, 3, 4, 5],
+    MAP['math', 95.5, 'science', 88.0, 'english', 92.3],
+    ROW('Alice Johnson', 30, 'a')
+),
+(
+    ARRAY[10, 20, 30, 40, 50, 60],
+    MAP['physics', 96.2, 'chemistry', 91.8, 'biology', 89.5, 
'computer_science', 98.7],
+    ROW('Bob Smith', 25, 'b')
+),
+(
+    ARRAY[100, 200, 300],
+    MAP['leadership', 88.9, 'teamwork', 94.2, 'communication', 91.5, 
'problem_solving', 97.8],
+    ROW('Charlie Chen', 35, 'c')
+);
+(3 rows modified)
+
+!update
+
+select
+    max(a) as max_a,
+    max(m) as max_m,
+    max(r) as max_r,
+    min(a) as min_a,
+    min(m) as min_m,
+    min(r) as min_r
+from complex_t;
++-----------------+----------------------------------------------------------------------------------------------+-----------------------+-----------------+------------------------------------------------------------------------------------------+------------------------+
+| MAX_A           | MAX_M                                                      
                                  | MAX_R                 | MIN_A           | 
MIN_M                                                                           
         | MIN_R                  |
++-----------------+----------------------------------------------------------------------------------------------+-----------------------+-----------------+------------------------------------------------------------------------------------------+------------------------+
+| [100, 200, 300] | {physics         =96.2, chemistry       =91.8, biology     
    =89.5, computer_science=98.7} | {Charlie Chen, 35, c} | [1, 2, 3, 4, 5] | 
{leadership     =88.9, teamwork       =94.2, communication  =91.5, 
problem_solving=97.8} | {Alice Johnson, 30, a} |
++-----------------+----------------------------------------------------------------------------------------------+-----------------------+-----------------+------------------------------------------------------------------------------------------+------------------------+
+(1 row)
+
+!ok
+
 # End blank.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 329e758b94..5f7aa258d4 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
@@ -16,8 +16,6 @@
  */
 package org.apache.calcite.linq4j.function;
 
-import com.google.common.collect.Lists;
-
 import org.checkerframework.checker.nullness.qual.Nullable;
 import org.checkerframework.framework.qual.DefaultQualifier;
 import org.checkerframework.framework.qual.TypeUseLocation;
@@ -31,6 +29,7 @@
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -573,9 +572,7 @@ private static class NullsLastComparator
       } else if (o1 instanceof List && o2 instanceof List) {
         return compareLists((List<?>) o1, (List<?>) o2);
       } else if (o1 instanceof Object[] && o2 instanceof Object[]) {
-        final List<Object> list1 = Lists.newArrayList((Object[]) o1);
-        final List<Object> list2 = Lists.newArrayList((Object[]) o2);
-        return compareLists(list1, list2);
+        return compareObjectArrays((Object[]) o1, (Object[]) o2);
       } else {
         throw new IllegalArgumentException();
       }
@@ -601,9 +598,7 @@ private static class NullsFirstReverseComparator
       } else if (o1 instanceof List && o2 instanceof List) {
         return -compareLists((List<?>) o1, (List<?>) o2);
       } else if (o1 instanceof Object[] && o2 instanceof Object[]) {
-        final List<Object> list1 = Lists.newArrayList((Object[]) o1);
-        final List<Object> list2 = Lists.newArrayList((Object[]) o2);
-        return -compareLists(list1, list2);
+        return -compareObjectArrays((Object[]) o1, (Object[]) o2);
       } else {
         throw new IllegalArgumentException();
       }
@@ -611,6 +606,9 @@ private static class NullsFirstReverseComparator
   }
 
   public static int compareLists(List<?> b0, List<?> b1) {
+    if (b0 == b1) {
+      return 0;
+    }
     if (b0.isEmpty() && b1.isEmpty()) {
       return 0;
     }
@@ -623,10 +621,46 @@ public static int compareLists(List<?> b0, List<?> b1) {
     return Integer.compare(b0.size(), b1.size());
   }
 
+  /**
+   * Compares two maps.
+   *
+   * <p>Since maps in Calcite are implemented using {@link 
java.util.LinkedHashMap},
+   * which guarantees insertion order, this method follows DuckDB's behavior by
+   * comparing entries in order. For each entry, it first compares the key and
+   * then the value.
+   */
+  public static int compareMaps(Map<?, ?> b0, Map<?, ?> b1) {
+    if (b0 == b1) {
+      return 0;
+    }
+    final Iterator<? extends Map.Entry<?, ?>> i0 = b0.entrySet().iterator();
+    final Iterator<? extends Map.Entry<?, ?>> i1 = b1.entrySet().iterator();
+    while (i0.hasNext() && i1.hasNext()) {
+      Map.Entry<?, ?> e0 = i0.next();
+      Map.Entry<?, ?> e1 = i1.next();
+      int c = compareListItems(e0.getKey(), e1.getKey());
+      if (c != 0) {
+        return c;
+      }
+      c = compareListItems(e0.getValue(), e1.getValue());
+      if (c != 0) {
+        return c;
+      }
+    }
+    if (i0.hasNext()) {
+      return 1;
+    }
+    if (i1.hasNext()) {
+      return -1;
+    }
+    return 0;
+  }
+
   private static int compareListItems(@Nullable Object item0, @Nullable Object 
item1) {
-    if (item0 == null && item1 == null) {
+    if (item0 == item1) {
       return 0;
-    } else if (item0 == null) {
+    }
+    if (item0 == null) {
       return 1;
     } else if (item1 == null) {
       return -1;
@@ -635,6 +669,8 @@ private static int compareListItems(@Nullable Object item0, 
@Nullable Object ite
       final List<?> b0ItemList = (List<?>) item0;
       final List<?> b1ItemList = (List<?>) item1;
       return compareLists(b0ItemList, b1ItemList);
+    } else if (item0 instanceof Map && item1 instanceof Map) {
+      return compareMaps((Map) item0, (Map) item1);
     } else if (item0 instanceof Object[] && item1 instanceof Object[]) {
       return compareObjectArrays((Object[]) item0, (Object[]) item1);
     } else if (item0.getClass().equals(item1.getClass()) && item0 instanceof 
Comparable<?>) {
@@ -642,14 +678,23 @@ private static int compareListItems(@Nullable Object 
item0, @Nullable Object ite
       final Comparable b1Comparable = (Comparable) item1;
       return b0Comparable.compareTo(b1Comparable);
     } else {
-      throw new IllegalArgumentException("Item types do not match");
+      throw new IllegalArgumentException("Item types do not match: "
+          + item0.getClass() + " vs " + item1.getClass());
     }
   }
 
-  public static int compareObjectArrays(Object[] b0, Object[] b1) {
-    final List<Object> b0List = Lists.newArrayList(b0);
-    final List<Object> b1List = Lists.newArrayList(b1);
-    return compareLists(b0List, b1List);
+  public static int compareObjectArrays(@Nullable Object @Nullable [] b0,
+      @Nullable Object @Nullable [] b1) {
+    if (b0 == b1) {
+      return 0;
+    }
+    if (b0 == null) {
+      return 1;
+    }
+    if (b1 == null) {
+      return -1;
+    }
+    return compareLists(Arrays.asList(b0), Arrays.asList(b1));
   }
 
   /** Nulls last reverse comparator. */

Reply via email to