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>≤</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>≥</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. */