This is an automated email from the ASF dual-hosted git repository.
mihaibudiu 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 018de2e8a3 [CALCITE-7706] ARG_MIN ignores nullability of second
argument
018de2e8a3 is described below
commit 018de2e8a3e47a8f6d173c64ef928cefefd725e9
Author: Mihai Budiu <[email protected]>
AuthorDate: Mon Aug 10 16:05:18 2026 -0700
[CALCITE-7706] ARG_MIN ignores nullability of second argument
Signed-off-by: Mihai Budiu <[email protected]>
---
.../calcite/sql/fun/SqlStdOperatorTable.java | 9 +++++--
core/src/test/resources/sql/agg.iq | 30 ++++++++++++++++++++++
.../org/apache/calcite/test/SqlOperatorTest.java | 15 +++++++++++
3 files changed, 52 insertions(+), 2 deletions(-)
diff --git
a/core/src/main/java/org/apache/calcite/sql/fun/SqlStdOperatorTable.java
b/core/src/main/java/org/apache/calcite/sql/fun/SqlStdOperatorTable.java
index ef7240d35e..d50bc7299e 100644
--- a/core/src/main/java/org/apache/calcite/sql/fun/SqlStdOperatorTable.java
+++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlStdOperatorTable.java
@@ -68,6 +68,7 @@
import org.apache.calcite.sql.type.SqlReturnTypeInference;
import org.apache.calcite.sql.type.SqlTypeFamily;
import org.apache.calcite.sql.type.SqlTypeName;
+import org.apache.calcite.sql.type.SqlTypeTransforms;
import org.apache.calcite.sql.util.ReflectiveSqlOperatorTable;
import org.apache.calcite.sql.validate.SqlConformance;
import org.apache.calcite.sql.validate.SqlConformanceEnum;
@@ -1125,7 +1126,9 @@ public class SqlStdOperatorTable extends
ReflectiveSqlOperatorTable {
*/
public static final SqlBasicAggFunction ARG_MAX =
SqlBasicAggFunction.create("ARG_MAX", SqlKind.ARG_MAX,
- ReturnTypes.ARG0_NULLABLE_IF_EMPTY, OperandTypes.ANY_COMPARABLE)
+ ReturnTypes.ARG0_NULLABLE_IF_EMPTY
+ .andThen(SqlTypeTransforms.TO_NULLABLE),
+ OperandTypes.ANY_COMPARABLE)
.withGroupOrder(Optionality.FORBIDDEN)
.withFunctionType(SqlFunctionCategory.SYSTEM);
@@ -1134,7 +1137,9 @@ public class SqlStdOperatorTable extends
ReflectiveSqlOperatorTable {
*/
public static final SqlBasicAggFunction ARG_MIN =
SqlBasicAggFunction.create("ARG_MIN", SqlKind.ARG_MIN,
- ReturnTypes.ARG0_NULLABLE_IF_EMPTY, OperandTypes.ANY_COMPARABLE)
+ ReturnTypes.ARG0_NULLABLE_IF_EMPTY
+ .andThen(SqlTypeTransforms.TO_NULLABLE),
+ OperandTypes.ANY_COMPARABLE)
.withGroupOrder(Optionality.FORBIDDEN)
.withFunctionType(SqlFunctionCategory.SYSTEM);
diff --git a/core/src/test/resources/sql/agg.iq
b/core/src/test/resources/sql/agg.iq
index bc24ba6014..41f2436a25 100644
--- a/core/src/test/resources/sql/agg.iq
+++ b/core/src/test/resources/sql/agg.iq
@@ -3866,6 +3866,36 @@ group by deptno;
!ok
+# [CALCITE-7706] ARG_MIN ignores nullability of second argument
+# Rows whose comparator is NULL are skipped, so a group where every
+# comparator value is NULL yields NULL even though the value argument
+# is NOT NULL. ARG_MAX behaves the same.
+select g, arg_min(v, c) as mi, arg_max(v, c) as ma
+from (values (1, 10, cast(null as integer))) as t(g, v, c)
+group by g;
++---+----+----+
+| G | MI | MA |
++---+----+----+
+| 1 | | |
++---+----+----+
+(1 row)
+
+!ok
+
+# The IS NULL test must not be simplified away based on the result type.
+select mi is null as n1, ma is null as n2 from (
+ select g, arg_min(v, c) as mi, arg_max(v, c) as ma
+ from (values (1, 10, cast(null as integer))) as t(g, v, c)
+ group by g);
++------+------+
+| N1 | N2 |
++------+------+
+| true | true |
++------+------+
+(1 row)
+
+!ok
+
# ARG_MIN, ARG_MAX applied to an integer.
select arg_min(deptno, empno) as mi,
arg_max(deptno, empno) as ma,
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 c6be7efedf..9473246f62 100644
--- a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
+++ b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
@@ -18138,11 +18138,26 @@ void checkBitOr(SqlOperatorFixture f0, FunctionAlias
functionAlias) {
final Consumer<SqlOperatorFixture> consumer = f -> {
f.checkAgg("arg_min(mod(x, 3), x)", xValues, isSingle("2"));
f.checkAgg("arg_max(mod(x, 3), x)", xValues, isSingle("1"));
+ f.checkAggType("arg_min(1, 2)", "INTEGER NOT NULL");
+ f.checkAggType("arg_max(1, 2)", "INTEGER NOT NULL");
+ // Test cases for [CALCITE-7706]
+ // ARG_MIN ignores nullability of second argument
+ f.checkAggType("arg_min(1, cast(null as integer))", "INTEGER");
+ f.checkAggType("arg_max(1, cast(null as integer))", "INTEGER");
+ f.checkAggType("arg_min(cast(null as integer), 2)", "INTEGER");
+ f.checkAggType("arg_max(cast(null as integer), 2)", "INTEGER");
+ // 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");
};
final Consumer<SqlOperatorFixture> consumer2 = f -> {
f.checkAgg("min_by(mod(x, 3), x)", xValues, isSingle("2"));
f.checkAgg("max_by(mod(x, 3), x)", xValues, isSingle("1"));
+ // Test cases for [CALCITE-7706]
+ // 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");
};
consumer.accept(f0);