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 ae7e2e042c [CALCITE-7705] LISTAGG result type is never nullable
ae7e2e042c is described below
commit ae7e2e042c326b9e37b85ee7f3df608cd9a6ee8f
Author: Mihai Budiu <[email protected]>
AuthorDate: Mon Aug 10 16:21:57 2026 -0700
[CALCITE-7705] LISTAGG result type is never nullable
Signed-off-by: Mihai Budiu <[email protected]>
---
.../calcite/sql/fun/SqlLibraryOperators.java | 4 ++--
.../calcite/sql/fun/SqlStdOperatorTable.java | 3 ++-
core/src/test/resources/sql/agg.iq | 24 ++++++++++++++++++++++
.../org/apache/calcite/test/SqlOperatorTest.java | 15 ++++++++++++++
4 files changed, 43 insertions(+), 3 deletions(-)
diff --git
a/core/src/main/java/org/apache/calcite/sql/fun/SqlLibraryOperators.java
b/core/src/main/java/org/apache/calcite/sql/fun/SqlLibraryOperators.java
index 6e328261c6..aef50bbc63 100644
--- a/core/src/main/java/org/apache/calcite/sql/fun/SqlLibraryOperators.java
+++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlLibraryOperators.java
@@ -846,7 +846,7 @@ static RelDataType deriveTypeSplit(SqlOperatorBinding
operatorBinding,
@LibraryOperator(libraries = {BIG_QUERY, POSTGRESQL}, exceptLibraries =
{REDSHIFT})
public static final SqlAggFunction STRING_AGG =
SqlBasicAggFunction
- .create(SqlKind.STRING_AGG, ReturnTypes.ARG0_NULLABLE,
+ .create(SqlKind.STRING_AGG, ReturnTypes.ARG0_NULLABLE_IF_EMPTY,
OperandTypes.STRING.or(OperandTypes.STRING_STRING))
.withFunctionType(SqlFunctionCategory.SYSTEM)
.withSyntax(SqlSyntax.ORDERED_FUNCTION);
@@ -862,7 +862,7 @@ static RelDataType deriveTypeSplit(SqlOperatorBinding
operatorBinding,
SqlBasicAggFunction
.create(SqlKind.GROUP_CONCAT,
ReturnTypes.andThen(ReturnTypes::stripOrderBy,
- ReturnTypes.ARG0_NULLABLE),
+ ReturnTypes.ARG0_NULLABLE_IF_EMPTY),
OperandTypes.STRING.or(OperandTypes.STRING_STRING))
.withFunctionType(SqlFunctionCategory.SYSTEM)
.withAllowsNullTreatment(false)
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 be9f23abe6..ef7240d35e 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
@@ -2540,7 +2540,8 @@ public class SqlStdOperatorTable extends
ReflectiveSqlOperatorTable {
* The LISTAGG operator. String aggregator function.
*/
public static final SqlAggFunction LISTAGG =
- new SqlListaggAggFunction(SqlKind.LISTAGG, ReturnTypes.ARG0_NULLABLE);
+ new SqlListaggAggFunction(SqlKind.LISTAGG,
+ ReturnTypes.ARG0_NULLABLE_IF_EMPTY);
/**
* The FUSION operator. Multiset aggregator function.
diff --git a/core/src/test/resources/sql/agg.iq
b/core/src/test/resources/sql/agg.iq
index cbd50747b3..d6557e34f1 100644
--- a/core/src/test/resources/sql/agg.iq
+++ b/core/src/test/resources/sql/agg.iq
@@ -3380,6 +3380,30 @@ select listagg(ename) as combined_name from emp;
!ok
+# [CALCITE-7705] LISTAGG result type is never nullable
+# Empty input yields NULL even though the argument is NOT NULL.
+select listagg(v, ',') as r from (values ('a')) as t(v) where false;
++---+
+| R |
++---+
+| |
++---+
+(1 row)
+
+!ok
+
+# The IS NULL test must not be simplified away based on the result type.
+select r is null as n from (
+ select listagg(v, ',') as r from (values ('a')) as t(v) where false);
++------+
+| N |
++------+
+| true |
++------+
+(1 row)
+
+!ok
+
select listagg(ename) within group(order by gender, ename) as combined_name
from emp;
+-------------------------------------------------------+
| COMBINED_NAME |
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 cdae823649..c6be7efedf 100644
--- a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
+++ b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
@@ -12935,6 +12935,14 @@ private static void checkDecodeFunc(SqlOperatorFixture
f) {
false);
f.checkAggType("listagg('test')", "CHAR(4) NOT NULL");
f.checkAggType("listagg('test', ', ')", "CHAR(4) NOT NULL");
+ // Test case for [CALCITE-7705]
+ // LISTAGG result type is never nullable
+ // Nullable without GROUP BY even for a non-nullable argument, since the
+ // input may be empty
+ f.checkColumnType("select listagg('test') from (values (1))", "CHAR(4)");
+ // A FILTER clause may exclude all rows, so the result is nullable
+ f.checkColumnType("select listagg('test') filter (where x > 1) "
+ + "from (values (1)) as t(x) group by x", "CHAR(4)");
final String[] values1 = {"'hello'", "CAST(null AS CHAR)", "'world'",
"'!'"};
f.checkAgg("listagg(x)", values1, isSingle("hello,world,! "));
final String[] values2 = {"0", "1", "2", "3"};
@@ -12950,6 +12958,10 @@ private static void checkDecodeFunc(SqlOperatorFixture
f) {
private static void checkStringAggFunc(SqlOperatorFixture f) {
final String[] values = {"'x'", "null", "'yz'"};
+ // Test case for [CALCITE-7705]
+ // LISTAGG result type is never nullable
+ f.checkColumnType("select string_agg('x', ',') from (values (1))",
+ "CHAR(1)");
f.checkAgg("string_agg(x)", values, isSingle("x ,yz"));
f.checkAgg("string_agg(x,':')", values, isSingle("x :yz"));
f.checkAgg("string_agg(x,':' order by x)", values, isSingle("x :yz"));
@@ -13008,6 +13020,9 @@ private static void
checkStringAggFuncFails(SqlOperatorFixture f) {
private static void checkGroupConcatFunc(SqlOperatorFixture f) {
final String[] values = {"'x'", "null", "'yz'"};
+ // Test case for [CALCITE-7705]
+ // LISTAGG result type is never nullable
+ f.checkColumnType("select group_concat('x') from (values (1))", "CHAR(1)");
f.checkAgg("group_concat(x)", values, isSingle("x ,yz"));
f.checkAgg("group_concat(x,':')", values, isSingle("x :yz"));
f.checkAgg("group_concat(x,':' order by x)", values, isSingle("x :yz"));