This is an automated email from the ASF dual-hosted git repository.
xiedeyantu 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 7b1650e31c [CALCITE-7862] ARG_MIN and ARG_MAX should support ARRAY,
MAP and ROW keys
7b1650e31c is described below
commit 7b1650e31ca1770d2d3e049f32f9044687012595
Author: Alan Jin <[email protected]>
AuthorDate: Fri Oct 16 06:33:21 2026 +0000
[CALCITE-7862] ARG_MIN and ARG_MAX should support ARRAY, MAP and ROW keys
---
.../org/apache/calcite/runtime/SqlFunctions.java | 42 ++++++++++++++++++++++
core/src/test/resources/sql/agg.iq | 37 +++++++++++++++++++
.../org/apache/calcite/test/SqlOperatorTest.java | 7 ++++
3 files changed, 86 insertions(+)
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 1c7159dedd..58e68cdb24 100644
--- a/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
+++ b/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
@@ -2570,6 +2570,27 @@ public static <T extends Comparable<T>> boolean
ltNullable(T b0, T b1) {
return b1 == null || b0 != null && b0.compareTo(b1) < 0;
}
+ /** Returns whether {@code b0} is less than {@code b1}
+ * (or {@code b1} is null). Helper for {@code ARG_MIN} on ARRAY keys. */
+ public static boolean ltNullable(@Nullable List<?> b0,
+ @Nullable List<?> b1) {
+ return b1 == null || b0 != null && lt(b0, b1);
+ }
+
+ /** Returns whether {@code b0} is less than {@code b1}
+ * (or {@code b1} is null). Helper for {@code ARG_MIN} on MAP keys. */
+ public static boolean ltNullable(@Nullable Map<?, ?> b0,
+ @Nullable Map<?, ?> b1) {
+ return b1 == null || b0 != null && lt(b0, b1);
+ }
+
+ /** Returns whether {@code b0} is less than {@code b1}
+ * (or {@code b1} is null). Helper for {@code ARG_MIN} on ROW keys. */
+ public static boolean ltNullable(@Nullable Object @Nullable [] b0,
+ @Nullable Object @Nullable [] b1) {
+ return b1 == null || b0 != null && lt(b0, b1);
+ }
+
public static boolean lt(byte b0, byte b1) {
return b0 < b1;
}
@@ -2706,6 +2727,27 @@ public static <T extends Comparable<T>> boolean
gtNullable(T b0, T b1) {
return b1 == null || b0 != null && b0.compareTo(b1) > 0;
}
+ /** Returns whether {@code b0} is greater than {@code b1}
+ * (or {@code b1} is null). Helper for {@code ARG_MAX} on ARRAY keys. */
+ public static boolean gtNullable(@Nullable List<?> b0,
+ @Nullable List<?> b1) {
+ return b1 == null || b0 != null && gt(b0, b1);
+ }
+
+ /** Returns whether {@code b0} is greater than {@code b1}
+ * (or {@code b1} is null). Helper for {@code ARG_MAX} on MAP keys. */
+ public static boolean gtNullable(@Nullable Map<?, ?> b0,
+ @Nullable Map<?, ?> b1) {
+ return b1 == null || b0 != null && gt(b0, b1);
+ }
+
+ /** Returns whether {@code b0} is greater than {@code b1}
+ * (or {@code b1} is null). Helper for {@code ARG_MAX} on ROW keys. */
+ public static boolean gtNullable(@Nullable Object @Nullable [] b0,
+ @Nullable Object @Nullable [] b1) {
+ return b1 == null || b0 != null && gt(b0, b1);
+ }
+
public static boolean gt(byte b0, byte b1) {
return b0 > b1;
}
diff --git a/core/src/test/resources/sql/agg.iq
b/core/src/test/resources/sql/agg.iq
index 55fd016f03..2b035304b8 100644
--- a/core/src/test/resources/sql/agg.iq
+++ b/core/src/test/resources/sql/agg.iq
@@ -3910,6 +3910,43 @@ from emp;
!ok
+# [CALCITE-7862] ARG_MIN and ARG_MAX fail with "Unable to implement" when
+# the key is an ARRAY, MAP or ROW.
+# Keys compare as in MIN and MAX; a row whose key is NULL is skipped.
+select arg_min(x, v) as mi, arg_max(x, v) as ma, min(v) as mn, max(v) as mx
+from (values (1, array[3, 1]), (2, array[1, 5]), (3, array[1, 4]),
+ (4, null)) as t(x, v);
++----+----+--------+--------+
+| MI | MA | MN | MX |
++----+----+--------+--------+
+| 3 | 1 | [1, 4] | [3, 1] |
++----+----+--------+--------+
+(1 row)
+
+!ok
+
+select arg_min(x, v) as mi, arg_max(x, v) as ma, min(v) as mn, max(v) as mx
+from (values (1, map['a', 2]), (2, map['a', 1]), (3, map['b', 0])) as t(x, v);
++----+----+-------+-------+
+| MI | MA | MN | MX |
++----+----+-------+-------+
+| 2 | 3 | {a=1} | {b=0} |
++----+----+-------+-------+
+(1 row)
+
+!ok
+
+select arg_min(x, v) as mi, arg_max(x, v) as ma, min(v) as mn, max(v) as mx
+from (values (1, row(2, 'b')), (2, row(1, 'a')), (3, row(1, 'c'))) as t(x, v);
++----+----+--------+--------+
+| MI | MA | MN | MX |
++----+----+--------+--------+
+| 2 | 1 | {1, a} | {2, b} |
++----+----+--------+--------+
+(1 row)
+
+!ok
+
# DISTINCT query with ORDER BY on aggregate when there is an implicit cast
select distinct sum(deptno + '1') as deptsum from dept order by 1;
+---------+
diff --git a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
index 7453fecc40..644a0f92a0 100644
--- a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
+++ b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
@@ -18402,6 +18402,10 @@ void checkBitOr(SqlOperatorFixture f0, FunctionAlias
functionAlias) {
// Nullable without GROUP BY even for non-nullable arguments, since the
// input may be empty
f.checkColumnType("select arg_min(1, 2) from (values (1))", "INTEGER");
+ // Test cases for [CALCITE-7862]
+ // ARG_MIN and ARG_MAX fail when the key is an ARRAY, MAP or ROW
+ f.checkAgg("arg_min(x, array[mod(x, 3), x])", xValues, isSingle("3"));
+ f.checkAgg("arg_max(x, array[mod(x, 3), x])", xValues, isSingle("5"));
};
final Consumer<SqlOperatorFixture> consumer2 = f -> {
@@ -18411,6 +18415,9 @@ void checkBitOr(SqlOperatorFixture f0, FunctionAlias
functionAlias) {
// ARG_MIN ignores nullability of second argument
f.checkAggType("min_by(1, cast(null as integer))", "INTEGER");
f.checkAggType("max_by(1, cast(null as integer))", "INTEGER");
+ // Test cases for [CALCITE-7862]
+ f.checkAgg("min_by(x, array[mod(x, 3), x])", xValues, isSingle("3"));
+ f.checkAgg("max_by(x, array[mod(x, 3), x])", xValues, isSingle("5"));
};
consumer.accept(f0);