Repository: apache/calcite
Pull request #4288: [CALCITE-6938] Support zero value creation of nested data types
Author: login: xiedeyantu
Labels: name: LGTM-will-merge-soon
description: Overall PR looks OK. Only minor things left.
Created: 2025-04-04T14:09:34Z | Merged: 2025-04-10T23:22:56Z

Please see: [CALCITE-6938](https://issues.apache.org/jira/browse/CALCITE-6938)


Diff:
diff --git a/core/src/main/java/org/apache/calcite/rex/RexBuilder.java b/core/src/main/java/org/apache/calcite/rex/RexBuilder.java
index 641a0cb35126..e4317ddbf65f 100644
--- a/core/src/main/java/org/apache/calcite/rex/RexBuilder.java
+++ b/core/src/main/java/org/apache/calcite/rex/RexBuilder.java
@@ -1924,6 +1924,27 @@ public RexLiteral makeZeroLiteral(RelDataType type) {
     return makeLiteral(zeroValue(type), type);
   }
 
+  public RexNode makeZeroRexNode(RelDataType type) {
+    switch (type.getSqlTypeName()) {
+    case ARRAY:
+      return makeCast(type,
+          makeCall(type, SqlStdOperatorTable.ARRAY_VALUE_CONSTRUCTOR, ImmutableList.of()));
+    case MULTISET:
+      return makeCast(type,
+          makeCall(type, SqlStdOperatorTable.MULTISET_VALUE, ImmutableList.of()));
+    case MAP:
+      return makeCast(type,
+          makeCall(type, SqlStdOperatorTable.MAP_VALUE_CONSTRUCTOR, ImmutableList.of()));
+    case ROW:
+      List<RexNode> zeroFields = type.getFieldList().stream()
+              .map(field -> makeZeroRexNode(field.getType()))
+              .collect(Collectors.toList());
+      return makeCall(type, SqlStdOperatorTable.ROW, zeroFields);
+    default:
+      return makeZeroLiteral(type);
+    }
+  }
+
   private static Comparable zeroValue(RelDataType type) {
     switch (type.getSqlTypeName()) {
     case CHAR:
diff --git a/core/src/test/java/org/apache/calcite/rex/RexBuilderTest.java b/core/src/test/java/org/apache/calcite/rex/RexBuilderTest.java
index fb181b423bb0..f41975262ce9 100644
--- a/core/src/test/java/org/apache/calcite/rex/RexBuilderTest.java
+++ b/core/src/test/java/org/apache/calcite/rex/RexBuilderTest.java
@@ -31,7 +31,10 @@
 import org.apache.calcite.sql.SqlKind;
 import org.apache.calcite.sql.fun.SqlLibraryOperators;
 import org.apache.calcite.sql.fun.SqlStdOperatorTable;
+import org.apache.calcite.sql.type.ArraySqlType;
 import org.apache.calcite.sql.type.BasicSqlType;
+import org.apache.calcite.sql.type.MapSqlType;
+import org.apache.calcite.sql.type.MultisetSqlType;
 import org.apache.calcite.sql.type.SqlTypeFactoryImpl;
 import org.apache.calcite.sql.type.SqlTypeName;
 import org.apache.calcite.test.CustomTypeSystems;
@@ -1104,6 +1107,140 @@ private static Stream<Arguments> testData4testMakeZeroLiteral() {
             relDataType -> new TimestampWithTimeZoneString(0, 1, 1, 0, 0, 0, "GMT+00:00")));
   }
 
+  /** Test case for
+   * <a href="https://issues.apache.org/jira/browse/CALCITE-6938">[CALCITE-6938]
+   * Support zero value creation of nested data types</a>. */
+  @ParameterizedTest
+  @MethodSource("testData4testMakeZeroForNestedType")
+  void testMakeZeroForNestedType(RelDataType type, RexNode expected) {
+    final RelDataTypeFactory typeFactory = new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT);
+    final RexBuilder rexBuilder = new RexBuilder(typeFactory);
+    assertThat(rexBuilder.makeZeroRexNode(type), is(equalTo(expected)));
+  }
+
+  @Test void testCreateCoalesce() {
+    RelDataTypeFactory typeFactory = new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT);
+    RexBuilder b = new RexBuilder(typeFactory);
+    RelDataType varcharType = typeFactory.createSqlType(SqlTypeName.VARCHAR);
+
+    RelDataType arrayType = new ArraySqlType(varcharType, false);
+    RexNode arrayZero = b.makeZeroRexNode(arrayType);
+
+    RexNode array =
+        b.makeCall(arrayType, SqlStdOperatorTable.ARRAY_VALUE_CONSTRUCTOR,
+        ImmutableList.of(
+            b.makeLiteral("1", varcharType)));
+
+    RexNode coalesce1 = b.makeCall(SqlStdOperatorTable.COALESCE, array, arrayZero);
+    assertThat(
+        coalesce1, hasToString(
+        "COALESCE(ARRAY('1'), CAST(ARRAY()):VARCHAR NOT NULL ARRAY NOT NULL)"));
+
+    RelDataType mapType = new MapSqlType(arrayType, arrayType, true);
+    RexNode mapZero = b.makeZeroRexNode(mapType);
+
+    RexNode map =
+        b.makeCall(new MapSqlType(arrayType, arrayType, true),
+        SqlStdOperatorTable.MAP_VALUE_CONSTRUCTOR,
+        ImmutableList.of(array, array));
+
+    RexNode coalesce2 = b.makeCall(SqlStdOperatorTable.COALESCE, map, mapZero);
+    assertThat(
+        coalesce2, hasToString(
+        "COALESCE(MAP(ARRAY('1'), ARRAY('1')), "
+        + "CAST(MAP()):(VARCHAR NOT NULL ARRAY NOT NULL, VARCHAR NOT NULL ARRAY NOT NULL) MAP)"));
+  }
+
+  private static Stream<Arguments> testData4testMakeZeroForNestedType() {
+    RelDataTypeFactory typeFactory = new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT);
+    RexBuilder b = new RexBuilder(typeFactory);
+
+    RelDataType integerType = typeFactory.createSqlType(SqlTypeName.INTEGER);
+    RelDataType varcharType = typeFactory.createSqlType(SqlTypeName.VARCHAR);
+
+    // ARRAY<INTEGER>
+    RelDataType arrayType = new ArraySqlType(integerType, false);
+    RexNode expectedArray =
+        b.makeCast(
+            arrayType, b.makeCall(arrayType, SqlStdOperatorTable.ARRAY_VALUE_CONSTRUCTOR,
+            ImmutableList.of()));
+
+    // MULTISET<INTEGER>
+    RelDataType multisetType = new MultisetSqlType(integerType, false);
+    RexNode expectedMultiset =
+        b.makeCast(
+            multisetType, b.makeCall(multisetType, SqlStdOperatorTable.MULTISET_VALUE,
+            ImmutableList.of()));
+
+    // MAP<VARCHAR, INTEGER>
+    RelDataType mapType = new MapSqlType(varcharType, integerType, false);
+    RexNode expectedMap =
+        b.makeCast(
+            mapType, b.makeCall(mapType, SqlStdOperatorTable.MAP_VALUE_CONSTRUCTOR,
+            ImmutableList.of()));
+
+    // ROW<INTEGER, VARCHAR>
+    RelDataType rowType =
+        typeFactory.createStructType(
+            ImmutableList.of(
+            new RelDataTypeFieldImpl("integer", 0, integerType),
+            new RelDataTypeFieldImpl("varchar", 1, varcharType)));
+    RexNode expectedRow =
+        b.makeCall(rowType, SqlStdOperatorTable.ROW,
+            ImmutableList.of(b.makeZeroLiteral(integerType),
+                b.makeZeroLiteral(varcharType)));
+
+    // ARRAY<ARRAY<INTEGER>>
+    RelDataType arrayArrayType = new ArraySqlType(arrayType, false);
+    RexNode expectedArrayArray =
+        b.makeCast(
+            arrayArrayType, b.makeCall(arrayArrayType, SqlStdOperatorTable.ARRAY_VALUE_CONSTRUCTOR,
+            ImmutableList.of()));
+
+    // ARRAY<MAP<VARCHAR, INTEGER>>
+    RelDataType arrayMapType = new ArraySqlType(mapType, false);
+    RexNode expectedArrayMap =
+        b.makeCast(
+            arrayMapType, b.makeCall(arrayMapType, SqlStdOperatorTable.ARRAY_VALUE_CONSTRUCTOR,
+            ImmutableList.of()));
+
+    // MAP<MAP<INTEGER, INTEGER>
+    RelDataType mapMapType = new MapSqlType(mapType, integerType, false);
+    RexNode expectedMapMap =
+        b.makeCast(
+            mapMapType, b.makeCall(mapMapType, SqlStdOperatorTable.MAP_VALUE_CONSTRUCTOR,
+            ImmutableList.of()));
+
+    // MAP<ARRAY<INTEGER>, INTEGER>
+    RelDataType mapArrayType = new MapSqlType(arrayType, integerType, false);
+    RexNode expectedMapArray =
+        b.makeCast(
+            mapArrayType, b.makeCall(mapArrayType, SqlStdOperatorTable.MAP_VALUE_CONSTRUCTOR,
+            ImmutableList.of()));
+
+    // ROW<ARRAY<INTEGER>, VARCHAR>
+    RelDataType rowArrayType =
+        typeFactory.createStructType(
+            ImmutableList.of(
+                new RelDataTypeFieldImpl("array", 0, arrayType),
+                new RelDataTypeFieldImpl("varchar", 1, varcharType)));
+    RexNode expectedRowArray =
+        b.makeCall(rowArrayType, SqlStdOperatorTable.ROW,
+            ImmutableList.of(expectedArray,
+                b.makeZeroLiteral(varcharType)));
+
+    return Stream.of(
+        Arguments.of(arrayType, expectedArray),
+        Arguments.of(multisetType, expectedMultiset),
+        Arguments.of(mapType, expectedMap),
+        Arguments.of(rowType, expectedRow),
+        Arguments.of(arrayArrayType, expectedArrayArray),
+        Arguments.of(arrayMapType, expectedArrayMap),
+        Arguments.of(mapMapType, expectedMapMap),
+        Arguments.of(mapArrayType, expectedMapArray),
+        Arguments.of(rowArrayType, expectedRowArray));
+  }
+
   /** Test case for
    * <a href="https://issues.apache.org/jira/browse/CALCITE-4632">[CALCITE-4632]
    * Find the least restrictive datatype for SARG</a>. */


Reviews:
user: login: mihaibudiu
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-04T17:47:44Z
user: login: xiedeyantu
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-04T21:38:10Z
user: login: mihaibudiu
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-04T21:44:54Z
user: login: xiedeyantu
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-04T21:53:59Z
user: login: mihaibudiu
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-04T21:55:49Z
user: login: xiedeyantu
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-04T21:58:45Z
user: login: xiedeyantu
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-05T06:28:08Z
user: login: mihaibudiu
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T18:37:57Z
user: login: xiedeyantu
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-07T22:46:31Z
user: login: mihaibudiu
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T22:48:53Z
user: login: xiedeyantu
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-07T23:03:30Z
user: login: mihaibudiu
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T23:05:42Z
user: login: xiedeyantu
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-07T23:09:27Z
user: login: mihaibudiu
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T17:34:58Z
user: login: xiedeyantu
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-08T23:17:14Z
user: login: mihaibudiu
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T23:19:02Z
user: login: xiedeyantu
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-08T23:19:18Z
user: login: mihaibudiu
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T23:21:03Z
user: login: xiedeyantu
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-08T23:23:39Z
user: login: mihaibudiu
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T23:25:32Z
user: login: xiedeyantu
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-08T23:29:20Z
user: login: mihaibudiu
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T23:30:44Z
user: login: mihaibudiu
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T23:31:01Z
user: login: xiedeyantu
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-10T13:19:28Z
user: login: xiedeyantu
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-10T13:22:08Z
user: login: mihaibudiu
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-10T17:07:00Z

Review comments:
diff_hunk: @@ -1917,6 +1917,34 @@ public RexLiteral makeZeroLiteral(RelDataType type) {
     return makeLiteral(zeroValue(type), type);
   }
 
+  public RexNode makeZeroForNestedType(RelDataType type) {
+    switch (type.getSqlTypeName()) {
+    case ARRAY:
path: core/src/main/java/org/apache/calcite/rex/RexBuilder.java
user: login: mihaibudiu
body: so you think a zero array is an array containing a zero?
or should it be an empty array?
the question is "zero for what algebraic operation"?
for concatenation the empty array is the zero.
created_at: 2025-04-04T17:46:45Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1917,6 +1917,34 @@ public RexLiteral makeZeroLiteral(RelDataType type) {
     return makeLiteral(zeroValue(type), type);
   }
 
+  public RexNode makeZeroForNestedType(RelDataType type) {
+    switch (type.getSqlTypeName()) {
+    case ARRAY:
path: core/src/main/java/org/apache/calcite/rex/RexBuilder.java
user: login: mihaibudiu
body: same question for multisets and maps
created_at: 2025-04-04T17:47:30Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1917,6 +1917,34 @@ public RexLiteral makeZeroLiteral(RelDataType type) {
     return makeLiteral(zeroValue(type), type);
   }
 
+  public RexNode makeZeroForNestedType(RelDataType type) {
+    switch (type.getSqlTypeName()) {
+    case ARRAY:
path: core/src/main/java/org/apache/calcite/rex/RexBuilder.java
user: login: mihaibudiu
body: I think an empty object makes more sense
created_at: 2025-04-04T17:47:38Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1917,6 +1917,34 @@ public RexLiteral makeZeroLiteral(RelDataType type) {
     return makeLiteral(zeroValue(type), type);
   }
 
+  public RexNode makeZeroForNestedType(RelDataType type) {
+    switch (type.getSqlTypeName()) {
+    case ARRAY:
path: core/src/main/java/org/apache/calcite/rex/RexBuilder.java
user: login: xiedeyantu
body: Yes, I also think what you said is very reasonable. Maybe I should implement an XxxZero for each nested type and implement the Comparable interface. I am trying to modify it. Could you give some suggestions?
created_at: 2025-04-04T21:38:10Z
author_association: MEMBER
diff_hunk: @@ -1917,6 +1917,34 @@ public RexLiteral makeZeroLiteral(RelDataType type) {
     return makeLiteral(zeroValue(type), type);
   }
 
+  public RexNode makeZeroForNestedType(RelDataType type) {
+    switch (type.getSqlTypeName()) {
+    case ARRAY:
path: core/src/main/java/org/apache/calcite/rex/RexBuilder.java
user: login: mihaibudiu
body: Why do you need the Comparable interface?
Just modify this function to return empty collections for ARRAY, MAP, MULTISET.
created_at: 2025-04-04T21:44:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1917,6 +1917,34 @@ public RexLiteral makeZeroLiteral(RelDataType type) {
     return makeLiteral(zeroValue(type), type);
   }
 
+  public RexNode makeZeroForNestedType(RelDataType type) {
+    switch (type.getSqlTypeName()) {
+    case ARRAY:
path: core/src/main/java/org/apache/calcite/rex/RexBuilder.java
user: login: xiedeyantu
body: Oh! I may have over-complicated it. I will modify the logic according to your suggestion. I have another question. If it is ARRAY<ARRAY<INTEGER>>>, the zero value should be ARRAY[ARRAY[]], right?
created_at: 2025-04-04T21:53:59Z
author_association: MEMBER
diff_hunk: @@ -1917,6 +1917,34 @@ public RexLiteral makeZeroLiteral(RelDataType type) {
     return makeLiteral(zeroValue(type), type);
   }
 
+  public RexNode makeZeroForNestedType(RelDataType type) {
+    switch (type.getSqlTypeName()) {
+    case ARRAY:
path: core/src/main/java/org/apache/calcite/rex/RexBuilder.java
user: login: mihaibudiu
body: Yes, but I think it will just work, no need to worry about it.
Just invoke the constructor for array with no arguments and cast it to the correct type.
created_at: 2025-04-04T21:55:49Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1917,6 +1917,34 @@ public RexLiteral makeZeroLiteral(RelDataType type) {
     return makeLiteral(zeroValue(type), type);
   }
 
+  public RexNode makeZeroForNestedType(RelDataType type) {
+    switch (type.getSqlTypeName()) {
+    case ARRAY:
path: core/src/main/java/org/apache/calcite/rex/RexBuilder.java
user: login: xiedeyantu
body: Got it, thanks a lot!
created_at: 2025-04-04T21:58:45Z
author_association: MEMBER
diff_hunk: @@ -1917,6 +1917,34 @@ public RexLiteral makeZeroLiteral(RelDataType type) {
     return makeLiteral(zeroValue(type), type);
   }
 
+  public RexNode makeZeroForNestedType(RelDataType type) {
+    switch (type.getSqlTypeName()) {
+    case ARRAY:
path: core/src/main/java/org/apache/calcite/rex/RexBuilder.java
user: login: xiedeyantu
body: Please take a look, I have addressed, thanks!
created_at: 2025-04-05T06:28:08Z
author_association: MEMBER
diff_hunk: @@ -1918,31 +1918,24 @@ public RexLiteral makeZeroLiteral(RelDataType type) {
   }
 
   public RexNode makeZeroForNestedType(RelDataType type) {
+    SqlOperator op;
     switch (type.getSqlTypeName()) {
     case ARRAY:
-      ArraySqlType arrayType = (ArraySqlType) type;
-      RexNode zeroElement = makeZeroForNestedType(arrayType.getComponentType());
-      return makeCall(type, SqlStdOperatorTable.ARRAY_VALUE_CONSTRUCTOR,
-          ImmutableList.of(zeroElement));
+      op = SqlStdOperatorTable.ARRAY_VALUE_CONSTRUCTOR;
path: core/src/main/java/org/apache/calcite/rex/RexBuilder.java
user: login: mihaibudiu
body: What is the type of the constructed array?
I wonder whether this will create problems because the type needs to be inferred.
But you know the type already, so you can synthesize a `CAST(ARRAY[] AS type)`.
Same below.
created_at: 2025-04-07T18:37:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1918,31 +1918,24 @@ public RexLiteral makeZeroLiteral(RelDataType type) {
   }
 
   public RexNode makeZeroForNestedType(RelDataType type) {
+    SqlOperator op;
     switch (type.getSqlTypeName()) {
     case ARRAY:
-      ArraySqlType arrayType = (ArraySqlType) type;
-      RexNode zeroElement = makeZeroForNestedType(arrayType.getComponentType());
-      return makeCall(type, SqlStdOperatorTable.ARRAY_VALUE_CONSTRUCTOR,
-          ImmutableList.of(zeroElement));
+      op = SqlStdOperatorTable.ARRAY_VALUE_CONSTRUCTOR;
path: core/src/main/java/org/apache/calcite/rex/RexBuilder.java
user: login: xiedeyantu
body: When using CAST, may need to write something like ARRAY[]::INTEGER? I may not be clear about the correct way to write it, but I think it need to write all types.
created_at: 2025-04-07T22:46:30Z
author_association: MEMBER
diff_hunk: @@ -1918,31 +1918,24 @@ public RexLiteral makeZeroLiteral(RelDataType type) {
   }
 
   public RexNode makeZeroForNestedType(RelDataType type) {
+    SqlOperator op;
     switch (type.getSqlTypeName()) {
     case ARRAY:
-      ArraySqlType arrayType = (ArraySqlType) type;
-      RexNode zeroElement = makeZeroForNestedType(arrayType.getComponentType());
-      return makeCall(type, SqlStdOperatorTable.ARRAY_VALUE_CONSTRUCTOR,
-          ImmutableList.of(zeroElement));
+      op = SqlStdOperatorTable.ARRAY_VALUE_CONSTRUCTOR;
path: core/src/main/java/org/apache/calcite/rex/RexBuilder.java
user: login: mihaibudiu
body: I am not sure what this statements means. This is not legal SQL.
And here I am talking about the internal representation.

created_at: 2025-04-07T22:48:52Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1918,31 +1918,24 @@ public RexLiteral makeZeroLiteral(RelDataType type) {
   }
 
   public RexNode makeZeroForNestedType(RelDataType type) {
+    SqlOperator op;
     switch (type.getSqlTypeName()) {
     case ARRAY:
-      ArraySqlType arrayType = (ArraySqlType) type;
-      RexNode zeroElement = makeZeroForNestedType(arrayType.getComponentType());
-      return makeCall(type, SqlStdOperatorTable.ARRAY_VALUE_CONSTRUCTOR,
-          ImmutableList.of(zeroElement));
+      op = SqlStdOperatorTable.ARRAY_VALUE_CONSTRUCTOR;
path: core/src/main/java/org/apache/calcite/rex/RexBuilder.java
user: login: xiedeyantu
body: You mean to keep the actual type and the writing consistent to avoid problems with `CAST(ARRAY[] AS type)` in SQL, right? I'll think about it and may need to adjust it again.
created_at: 2025-04-07T23:03:30Z
author_association: MEMBER
diff_hunk: @@ -1918,31 +1918,24 @@ public RexLiteral makeZeroLiteral(RelDataType type) {
   }
 
   public RexNode makeZeroForNestedType(RelDataType type) {
+    SqlOperator op;
     switch (type.getSqlTypeName()) {
     case ARRAY:
-      ArraySqlType arrayType = (ArraySqlType) type;
-      RexNode zeroElement = makeZeroForNestedType(arrayType.getComponentType());
-      return makeCall(type, SqlStdOperatorTable.ARRAY_VALUE_CONSTRUCTOR,
-          ImmutableList.of(zeroElement));
+      op = SqlStdOperatorTable.ARRAY_VALUE_CONSTRUCTOR;
path: core/src/main/java/org/apache/calcite/rex/RexBuilder.java
user: login: mihaibudiu
body: This should be a simple change. Just wrap the constructor call into a cast that produces `type`.
created_at: 2025-04-07T23:05:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1918,31 +1918,24 @@ public RexLiteral makeZeroLiteral(RelDataType type) {
   }
 
   public RexNode makeZeroForNestedType(RelDataType type) {
+    SqlOperator op;
     switch (type.getSqlTypeName()) {
     case ARRAY:
-      ArraySqlType arrayType = (ArraySqlType) type;
-      RexNode zeroElement = makeZeroForNestedType(arrayType.getComponentType());
-      return makeCall(type, SqlStdOperatorTable.ARRAY_VALUE_CONSTRUCTOR,
-          ImmutableList.of(zeroElement));
+      op = SqlStdOperatorTable.ARRAY_VALUE_CONSTRUCTOR;
path: core/src/main/java/org/apache/calcite/rex/RexBuilder.java
user: login: xiedeyantu
body: Thanks! I will fix it soon.
created_at: 2025-04-07T23:09:27Z
author_association: MEMBER
diff_hunk: @@ -1917,6 +1917,27 @@ public RexLiteral makeZeroLiteral(RelDataType type) {
     return makeLiteral(zeroValue(type), type);
   }
 
+  public RexNode makeZeroForNestedType(RelDataType type) {
+    SqlOperator op;
+    switch (type.getSqlTypeName()) {
+    case ARRAY:
+      op = SqlStdOperatorTable.ARRAY_VALUE_CONSTRUCTOR;
+      break;
+    case MULTISET:
+      op = SqlStdOperatorTable.MULTISET_VALUE;
+      break;
+    case MAP:
+      op = SqlStdOperatorTable.MAP_VALUE_CONSTRUCTOR;
+      break;
+    case ROW:
+      op = SqlStdOperatorTable.ROW;
path: core/src/main/java/org/apache/calcite/rex/RexBuilder.java
user: login: mihaibudiu
body: I don't think this works for ROW. For ROW your original solution was correct: you need to create a zero for each field using a recursive call.
created_at: 2025-04-08T17:33:36Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1104,6 +1107,140 @@ private static Stream<Arguments> testData4testMakeZeroLiteral() {
             relDataType -> new TimestampWithTimeZoneString(0, 1, 1, 0, 0, 0, "GMT+00:00")));
   }
 
+  /** Test case for
+   * <a href="https://issues.apache.org/jira/browse/CALCITE-6938">[CALCITE-6938]
+   * Support zero value creation of nested data types</a>. */
+  @ParameterizedTest
+  @MethodSource("testData4testMakeZeroForNestedType")
+  void testMakeZeroForNestedType(RelDataType type, RexNode expected) {
+    final RelDataTypeFactory typeFactory = new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT);
+    final RexBuilder rexBuilder = new RexBuilder(typeFactory);
+    assertThat(rexBuilder.makeZeroForNestedType(type), is(equalTo(expected)));
+  }
+
+  @Test void testCreateCoalesce() {
+    RelDataTypeFactory typeFactory = new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT);
+    RexBuilder b = new RexBuilder(typeFactory);
+    RelDataType varcharType = typeFactory.createSqlType(SqlTypeName.VARCHAR);
+
+    RelDataType arrayType = new ArraySqlType(varcharType, false);
+    RexNode arrayZero = b.makeZeroForNestedType(arrayType);
+
+    RexNode array =
+        b.makeCall(arrayType, SqlStdOperatorTable.ARRAY_VALUE_CONSTRUCTOR,
+        ImmutableList.of(
+            b.makeLiteral("1", varcharType)));
+
+    RexNode coalesce1 = b.makeCall(SqlStdOperatorTable.COALESCE, array, arrayZero);
+    assertThat(
+        coalesce1, hasToString(
+        "COALESCE(ARRAY('1'), CAST(ARRAY()):VARCHAR NOT NULL ARRAY NOT NULL)"));
+
+    RelDataType mapType = new MapSqlType(arrayType, arrayType, true);
+    RexNode mapZero = b.makeZeroForNestedType(mapType);
+
+    RexNode map =
+        b.makeCall(new MapSqlType(arrayType, arrayType, true),
+        SqlStdOperatorTable.MAP_VALUE_CONSTRUCTOR,
+        ImmutableList.of(array, array));
+
+    RexNode coalesce2 = b.makeCall(SqlStdOperatorTable.COALESCE, map, mapZero);
+    assertThat(
+        coalesce2, hasToString(
+        "COALESCE(MAP(ARRAY('1'), ARRAY('1')), "
+        + "CAST(MAP()):(VARCHAR NOT NULL ARRAY NOT NULL, VARCHAR NOT NULL ARRAY NOT NULL) MAP)"));
+  }
+
+  private static Stream<Arguments> testData4testMakeZeroForNestedType() {
+    RelDataTypeFactory typeFactory = new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT);
+    RexBuilder b = new RexBuilder(typeFactory);
+
+    RelDataType integerType = typeFactory.createSqlType(SqlTypeName.INTEGER);
+    RelDataType varcharType = typeFactory.createSqlType(SqlTypeName.VARCHAR);
+
+    // ARRAY<INTEGER>
+    RelDataType arrayType = new ArraySqlType(integerType, false);
+    RexNode expectedArray =
+        b.makeCast(
+            arrayType, b.makeCall(arrayType, SqlStdOperatorTable.ARRAY_VALUE_CONSTRUCTOR,
+            ImmutableList.of()));
+
+    // MULTISET<INTEGER>
+    RelDataType multisetType = new MultisetSqlType(integerType, false);
+    RexNode expectedMultiset =
+        b.makeCast(
+            multisetType, b.makeCall(multisetType, SqlStdOperatorTable.MULTISET_VALUE,
+            ImmutableList.of()));
+
+    // MAP<VARCHAR, INTEGER>
+    RelDataType mapType = new MapSqlType(varcharType, integerType, false);
+    RexNode expectedMap =
+        b.makeCast(
+            mapType, b.makeCall(mapType, SqlStdOperatorTable.MAP_VALUE_CONSTRUCTOR,
+            ImmutableList.of()));
+
+    // ROW<INTEGER, VARCHAR>
+    RelDataType rowType =
path: core/src/test/java/org/apache/calcite/rex/RexBuilderTest.java
user: login: mihaibudiu
body: I don't think this would pass validation.
Which means that these tests are not really conclusive, since they can generate incorrect programs. Is there a way you could make sure that these programs are valid?
created_at: 2025-04-08T17:34:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1917,6 +1917,27 @@ public RexLiteral makeZeroLiteral(RelDataType type) {
     return makeLiteral(zeroValue(type), type);
   }
 
+  public RexNode makeZeroForNestedType(RelDataType type) {
+    SqlOperator op;
+    switch (type.getSqlTypeName()) {
+    case ARRAY:
+      op = SqlStdOperatorTable.ARRAY_VALUE_CONSTRUCTOR;
+      break;
+    case MULTISET:
+      op = SqlStdOperatorTable.MULTISET_VALUE;
+      break;
+    case MAP:
+      op = SqlStdOperatorTable.MAP_VALUE_CONSTRUCTOR;
+      break;
+    case ROW:
+      op = SqlStdOperatorTable.ROW;
path: core/src/main/java/org/apache/calcite/rex/RexBuilder.java
user: login: xiedeyantu
body: I tried to sort out the logic according to your suggestion. ROW needs to be processed recursively and filled with real zero values. Should MAP have the same logic as ROW or ARRAY? If both use recursive logic, then ARRAY and MULITSET only need to do a CAST at the innermost level?
created_at: 2025-04-08T23:17:13Z
author_association: MEMBER
diff_hunk: @@ -1917,6 +1917,27 @@ public RexLiteral makeZeroLiteral(RelDataType type) {
     return makeLiteral(zeroValue(type), type);
   }
 
+  public RexNode makeZeroForNestedType(RelDataType type) {
+    SqlOperator op;
+    switch (type.getSqlTypeName()) {
+    case ARRAY:
+      op = SqlStdOperatorTable.ARRAY_VALUE_CONSTRUCTOR;
+      break;
+    case MULTISET:
+      op = SqlStdOperatorTable.MULTISET_VALUE;
+      break;
+    case MAP:
+      op = SqlStdOperatorTable.MAP_VALUE_CONSTRUCTOR;
+      break;
+    case ROW:
+      op = SqlStdOperatorTable.ROW;
path: core/src/main/java/org/apache/calcite/rex/RexBuilder.java
user: login: mihaibudiu
body: Yes, ROW needs real zeros, MAP, ARRAY, and MULTISET should be empty.
The empty value is the zero value for monoid provided by the concatenation operation for these data structures.
created_at: 2025-04-08T23:19:02Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1104,6 +1107,140 @@ private static Stream<Arguments> testData4testMakeZeroLiteral() {
             relDataType -> new TimestampWithTimeZoneString(0, 1, 1, 0, 0, 0, "GMT+00:00")));
   }
 
+  /** Test case for
+   * <a href="https://issues.apache.org/jira/browse/CALCITE-6938">[CALCITE-6938]
+   * Support zero value creation of nested data types</a>. */
+  @ParameterizedTest
+  @MethodSource("testData4testMakeZeroForNestedType")
+  void testMakeZeroForNestedType(RelDataType type, RexNode expected) {
+    final RelDataTypeFactory typeFactory = new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT);
+    final RexBuilder rexBuilder = new RexBuilder(typeFactory);
+    assertThat(rexBuilder.makeZeroForNestedType(type), is(equalTo(expected)));
+  }
+
+  @Test void testCreateCoalesce() {
+    RelDataTypeFactory typeFactory = new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT);
+    RexBuilder b = new RexBuilder(typeFactory);
+    RelDataType varcharType = typeFactory.createSqlType(SqlTypeName.VARCHAR);
+
+    RelDataType arrayType = new ArraySqlType(varcharType, false);
+    RexNode arrayZero = b.makeZeroForNestedType(arrayType);
+
+    RexNode array =
+        b.makeCall(arrayType, SqlStdOperatorTable.ARRAY_VALUE_CONSTRUCTOR,
+        ImmutableList.of(
+            b.makeLiteral("1", varcharType)));
+
+    RexNode coalesce1 = b.makeCall(SqlStdOperatorTable.COALESCE, array, arrayZero);
+    assertThat(
+        coalesce1, hasToString(
+        "COALESCE(ARRAY('1'), CAST(ARRAY()):VARCHAR NOT NULL ARRAY NOT NULL)"));
+
+    RelDataType mapType = new MapSqlType(arrayType, arrayType, true);
+    RexNode mapZero = b.makeZeroForNestedType(mapType);
+
+    RexNode map =
+        b.makeCall(new MapSqlType(arrayType, arrayType, true),
+        SqlStdOperatorTable.MAP_VALUE_CONSTRUCTOR,
+        ImmutableList.of(array, array));
+
+    RexNode coalesce2 = b.makeCall(SqlStdOperatorTable.COALESCE, map, mapZero);
+    assertThat(
+        coalesce2, hasToString(
+        "COALESCE(MAP(ARRAY('1'), ARRAY('1')), "
+        + "CAST(MAP()):(VARCHAR NOT NULL ARRAY NOT NULL, VARCHAR NOT NULL ARRAY NOT NULL) MAP)"));
+  }
+
+  private static Stream<Arguments> testData4testMakeZeroForNestedType() {
+    RelDataTypeFactory typeFactory = new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT);
+    RexBuilder b = new RexBuilder(typeFactory);
+
+    RelDataType integerType = typeFactory.createSqlType(SqlTypeName.INTEGER);
+    RelDataType varcharType = typeFactory.createSqlType(SqlTypeName.VARCHAR);
+
+    // ARRAY<INTEGER>
+    RelDataType arrayType = new ArraySqlType(integerType, false);
+    RexNode expectedArray =
+        b.makeCast(
+            arrayType, b.makeCall(arrayType, SqlStdOperatorTable.ARRAY_VALUE_CONSTRUCTOR,
+            ImmutableList.of()));
+
+    // MULTISET<INTEGER>
+    RelDataType multisetType = new MultisetSqlType(integerType, false);
+    RexNode expectedMultiset =
+        b.makeCast(
+            multisetType, b.makeCall(multisetType, SqlStdOperatorTable.MULTISET_VALUE,
+            ImmutableList.of()));
+
+    // MAP<VARCHAR, INTEGER>
+    RelDataType mapType = new MapSqlType(varcharType, integerType, false);
+    RexNode expectedMap =
+        b.makeCast(
+            mapType, b.makeCall(mapType, SqlStdOperatorTable.MAP_VALUE_CONSTRUCTOR,
+            ImmutableList.of()));
+
+    // ROW<INTEGER, VARCHAR>
+    RelDataType rowType =
path: core/src/test/java/org/apache/calcite/rex/RexBuilderTest.java
user: login: xiedeyantu
body: Is it that the test for ROW needs to be modified, but not the others? I saw that makeZoreLiteral also verifies how the zero value is constructed, and I referred to its logic.
created_at: 2025-04-08T23:19:18Z
author_association: MEMBER
diff_hunk: @@ -1104,6 +1107,140 @@ private static Stream<Arguments> testData4testMakeZeroLiteral() {
             relDataType -> new TimestampWithTimeZoneString(0, 1, 1, 0, 0, 0, "GMT+00:00")));
   }
 
+  /** Test case for
+   * <a href="https://issues.apache.org/jira/browse/CALCITE-6938">[CALCITE-6938]
+   * Support zero value creation of nested data types</a>. */
+  @ParameterizedTest
+  @MethodSource("testData4testMakeZeroForNestedType")
+  void testMakeZeroForNestedType(RelDataType type, RexNode expected) {
+    final RelDataTypeFactory typeFactory = new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT);
+    final RexBuilder rexBuilder = new RexBuilder(typeFactory);
+    assertThat(rexBuilder.makeZeroForNestedType(type), is(equalTo(expected)));
+  }
+
+  @Test void testCreateCoalesce() {
+    RelDataTypeFactory typeFactory = new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT);
+    RexBuilder b = new RexBuilder(typeFactory);
+    RelDataType varcharType = typeFactory.createSqlType(SqlTypeName.VARCHAR);
+
+    RelDataType arrayType = new ArraySqlType(varcharType, false);
+    RexNode arrayZero = b.makeZeroForNestedType(arrayType);
+
+    RexNode array =
+        b.makeCall(arrayType, SqlStdOperatorTable.ARRAY_VALUE_CONSTRUCTOR,
+        ImmutableList.of(
+            b.makeLiteral("1", varcharType)));
+
+    RexNode coalesce1 = b.makeCall(SqlStdOperatorTable.COALESCE, array, arrayZero);
+    assertThat(
+        coalesce1, hasToString(
+        "COALESCE(ARRAY('1'), CAST(ARRAY()):VARCHAR NOT NULL ARRAY NOT NULL)"));
+
+    RelDataType mapType = new MapSqlType(arrayType, arrayType, true);
+    RexNode mapZero = b.makeZeroForNestedType(mapType);
+
+    RexNode map =
+        b.makeCall(new MapSqlType(arrayType, arrayType, true),
+        SqlStdOperatorTable.MAP_VALUE_CONSTRUCTOR,
+        ImmutableList.of(array, array));
+
+    RexNode coalesce2 = b.makeCall(SqlStdOperatorTable.COALESCE, map, mapZero);
+    assertThat(
+        coalesce2, hasToString(
+        "COALESCE(MAP(ARRAY('1'), ARRAY('1')), "
+        + "CAST(MAP()):(VARCHAR NOT NULL ARRAY NOT NULL, VARCHAR NOT NULL ARRAY NOT NULL) MAP)"));
+  }
+
+  private static Stream<Arguments> testData4testMakeZeroForNestedType() {
+    RelDataTypeFactory typeFactory = new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT);
+    RexBuilder b = new RexBuilder(typeFactory);
+
+    RelDataType integerType = typeFactory.createSqlType(SqlTypeName.INTEGER);
+    RelDataType varcharType = typeFactory.createSqlType(SqlTypeName.VARCHAR);
+
+    // ARRAY<INTEGER>
+    RelDataType arrayType = new ArraySqlType(integerType, false);
+    RexNode expectedArray =
+        b.makeCast(
+            arrayType, b.makeCall(arrayType, SqlStdOperatorTable.ARRAY_VALUE_CONSTRUCTOR,
+            ImmutableList.of()));
+
+    // MULTISET<INTEGER>
+    RelDataType multisetType = new MultisetSqlType(integerType, false);
+    RexNode expectedMultiset =
+        b.makeCast(
+            multisetType, b.makeCall(multisetType, SqlStdOperatorTable.MULTISET_VALUE,
+            ImmutableList.of()));
+
+    // MAP<VARCHAR, INTEGER>
+    RelDataType mapType = new MapSqlType(varcharType, integerType, false);
+    RexNode expectedMap =
+        b.makeCast(
+            mapType, b.makeCall(mapType, SqlStdOperatorTable.MAP_VALUE_CONSTRUCTOR,
+            ImmutableList.of()));
+
+    // ROW<INTEGER, VARCHAR>
+    RelDataType rowType =
path: core/src/test/java/org/apache/calcite/rex/RexBuilderTest.java
user: login: mihaibudiu
body: it would be nice if there was a way to validate all of them.
I didn't think exactly how.
created_at: 2025-04-08T23:21:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1104,6 +1107,140 @@ private static Stream<Arguments> testData4testMakeZeroLiteral() {
             relDataType -> new TimestampWithTimeZoneString(0, 1, 1, 0, 0, 0, "GMT+00:00")));
   }
 
+  /** Test case for
+   * <a href="https://issues.apache.org/jira/browse/CALCITE-6938">[CALCITE-6938]
+   * Support zero value creation of nested data types</a>. */
+  @ParameterizedTest
+  @MethodSource("testData4testMakeZeroForNestedType")
+  void testMakeZeroForNestedType(RelDataType type, RexNode expected) {
+    final RelDataTypeFactory typeFactory = new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT);
+    final RexBuilder rexBuilder = new RexBuilder(typeFactory);
+    assertThat(rexBuilder.makeZeroForNestedType(type), is(equalTo(expected)));
+  }
+
+  @Test void testCreateCoalesce() {
+    RelDataTypeFactory typeFactory = new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT);
+    RexBuilder b = new RexBuilder(typeFactory);
+    RelDataType varcharType = typeFactory.createSqlType(SqlTypeName.VARCHAR);
+
+    RelDataType arrayType = new ArraySqlType(varcharType, false);
+    RexNode arrayZero = b.makeZeroForNestedType(arrayType);
+
+    RexNode array =
+        b.makeCall(arrayType, SqlStdOperatorTable.ARRAY_VALUE_CONSTRUCTOR,
+        ImmutableList.of(
+            b.makeLiteral("1", varcharType)));
+
+    RexNode coalesce1 = b.makeCall(SqlStdOperatorTable.COALESCE, array, arrayZero);
+    assertThat(
+        coalesce1, hasToString(
+        "COALESCE(ARRAY('1'), CAST(ARRAY()):VARCHAR NOT NULL ARRAY NOT NULL)"));
+
+    RelDataType mapType = new MapSqlType(arrayType, arrayType, true);
+    RexNode mapZero = b.makeZeroForNestedType(mapType);
+
+    RexNode map =
+        b.makeCall(new MapSqlType(arrayType, arrayType, true),
+        SqlStdOperatorTable.MAP_VALUE_CONSTRUCTOR,
+        ImmutableList.of(array, array));
+
+    RexNode coalesce2 = b.makeCall(SqlStdOperatorTable.COALESCE, map, mapZero);
+    assertThat(
+        coalesce2, hasToString(
+        "COALESCE(MAP(ARRAY('1'), ARRAY('1')), "
+        + "CAST(MAP()):(VARCHAR NOT NULL ARRAY NOT NULL, VARCHAR NOT NULL ARRAY NOT NULL) MAP)"));
+  }
+
+  private static Stream<Arguments> testData4testMakeZeroForNestedType() {
+    RelDataTypeFactory typeFactory = new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT);
+    RexBuilder b = new RexBuilder(typeFactory);
+
+    RelDataType integerType = typeFactory.createSqlType(SqlTypeName.INTEGER);
+    RelDataType varcharType = typeFactory.createSqlType(SqlTypeName.VARCHAR);
+
+    // ARRAY<INTEGER>
+    RelDataType arrayType = new ArraySqlType(integerType, false);
+    RexNode expectedArray =
+        b.makeCast(
+            arrayType, b.makeCall(arrayType, SqlStdOperatorTable.ARRAY_VALUE_CONSTRUCTOR,
+            ImmutableList.of()));
+
+    // MULTISET<INTEGER>
+    RelDataType multisetType = new MultisetSqlType(integerType, false);
+    RexNode expectedMultiset =
+        b.makeCast(
+            multisetType, b.makeCall(multisetType, SqlStdOperatorTable.MULTISET_VALUE,
+            ImmutableList.of()));
+
+    // MAP<VARCHAR, INTEGER>
+    RelDataType mapType = new MapSqlType(varcharType, integerType, false);
+    RexNode expectedMap =
+        b.makeCast(
+            mapType, b.makeCall(mapType, SqlStdOperatorTable.MAP_VALUE_CONSTRUCTOR,
+            ImmutableList.of()));
+
+    // ROW<INTEGER, VARCHAR>
+    RelDataType rowType =
path: core/src/test/java/org/apache/calcite/rex/RexBuilderTest.java
user: login: xiedeyantu
body: Should I keep this validation logic? Should I restore the zero value implementation logic of ROW to the previous version?
created_at: 2025-04-08T23:23:39Z
author_association: MEMBER
diff_hunk: @@ -1104,6 +1107,140 @@ private static Stream<Arguments> testData4testMakeZeroLiteral() {
             relDataType -> new TimestampWithTimeZoneString(0, 1, 1, 0, 0, 0, "GMT+00:00")));
   }
 
+  /** Test case for
+   * <a href="https://issues.apache.org/jira/browse/CALCITE-6938">[CALCITE-6938]
+   * Support zero value creation of nested data types</a>. */
+  @ParameterizedTest
+  @MethodSource("testData4testMakeZeroForNestedType")
+  void testMakeZeroForNestedType(RelDataType type, RexNode expected) {
+    final RelDataTypeFactory typeFactory = new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT);
+    final RexBuilder rexBuilder = new RexBuilder(typeFactory);
+    assertThat(rexBuilder.makeZeroForNestedType(type), is(equalTo(expected)));
+  }
+
+  @Test void testCreateCoalesce() {
+    RelDataTypeFactory typeFactory = new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT);
+    RexBuilder b = new RexBuilder(typeFactory);
+    RelDataType varcharType = typeFactory.createSqlType(SqlTypeName.VARCHAR);
+
+    RelDataType arrayType = new ArraySqlType(varcharType, false);
+    RexNode arrayZero = b.makeZeroForNestedType(arrayType);
+
+    RexNode array =
+        b.makeCall(arrayType, SqlStdOperatorTable.ARRAY_VALUE_CONSTRUCTOR,
+        ImmutableList.of(
+            b.makeLiteral("1", varcharType)));
+
+    RexNode coalesce1 = b.makeCall(SqlStdOperatorTable.COALESCE, array, arrayZero);
+    assertThat(
+        coalesce1, hasToString(
+        "COALESCE(ARRAY('1'), CAST(ARRAY()):VARCHAR NOT NULL ARRAY NOT NULL)"));
+
+    RelDataType mapType = new MapSqlType(arrayType, arrayType, true);
+    RexNode mapZero = b.makeZeroForNestedType(mapType);
+
+    RexNode map =
+        b.makeCall(new MapSqlType(arrayType, arrayType, true),
+        SqlStdOperatorTable.MAP_VALUE_CONSTRUCTOR,
+        ImmutableList.of(array, array));
+
+    RexNode coalesce2 = b.makeCall(SqlStdOperatorTable.COALESCE, map, mapZero);
+    assertThat(
+        coalesce2, hasToString(
+        "COALESCE(MAP(ARRAY('1'), ARRAY('1')), "
+        + "CAST(MAP()):(VARCHAR NOT NULL ARRAY NOT NULL, VARCHAR NOT NULL ARRAY NOT NULL) MAP)"));
+  }
+
+  private static Stream<Arguments> testData4testMakeZeroForNestedType() {
+    RelDataTypeFactory typeFactory = new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT);
+    RexBuilder b = new RexBuilder(typeFactory);
+
+    RelDataType integerType = typeFactory.createSqlType(SqlTypeName.INTEGER);
+    RelDataType varcharType = typeFactory.createSqlType(SqlTypeName.VARCHAR);
+
+    // ARRAY<INTEGER>
+    RelDataType arrayType = new ArraySqlType(integerType, false);
+    RexNode expectedArray =
+        b.makeCast(
+            arrayType, b.makeCall(arrayType, SqlStdOperatorTable.ARRAY_VALUE_CONSTRUCTOR,
+            ImmutableList.of()));
+
+    // MULTISET<INTEGER>
+    RelDataType multisetType = new MultisetSqlType(integerType, false);
+    RexNode expectedMultiset =
+        b.makeCast(
+            multisetType, b.makeCall(multisetType, SqlStdOperatorTable.MULTISET_VALUE,
+            ImmutableList.of()));
+
+    // MAP<VARCHAR, INTEGER>
+    RelDataType mapType = new MapSqlType(varcharType, integerType, false);
+    RexNode expectedMap =
+        b.makeCast(
+            mapType, b.makeCall(mapType, SqlStdOperatorTable.MAP_VALUE_CONSTRUCTOR,
+            ImmutableList.of()));
+
+    // ROW<INTEGER, VARCHAR>
+    RelDataType rowType =
path: core/src/test/java/org/apache/calcite/rex/RexBuilderTest.java
user: login: mihaibudiu
body: I think zero was correct for ROW previously.
For validation you can keep this, but can you make sure that there is additional validation? I haven't looked to see how builder results are validated.
created_at: 2025-04-08T23:25:32Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1104,6 +1107,140 @@ private static Stream<Arguments> testData4testMakeZeroLiteral() {
             relDataType -> new TimestampWithTimeZoneString(0, 1, 1, 0, 0, 0, "GMT+00:00")));
   }
 
+  /** Test case for
+   * <a href="https://issues.apache.org/jira/browse/CALCITE-6938">[CALCITE-6938]
+   * Support zero value creation of nested data types</a>. */
+  @ParameterizedTest
+  @MethodSource("testData4testMakeZeroForNestedType")
+  void testMakeZeroForNestedType(RelDataType type, RexNode expected) {
+    final RelDataTypeFactory typeFactory = new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT);
+    final RexBuilder rexBuilder = new RexBuilder(typeFactory);
+    assertThat(rexBuilder.makeZeroForNestedType(type), is(equalTo(expected)));
+  }
+
+  @Test void testCreateCoalesce() {
+    RelDataTypeFactory typeFactory = new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT);
+    RexBuilder b = new RexBuilder(typeFactory);
+    RelDataType varcharType = typeFactory.createSqlType(SqlTypeName.VARCHAR);
+
+    RelDataType arrayType = new ArraySqlType(varcharType, false);
+    RexNode arrayZero = b.makeZeroForNestedType(arrayType);
+
+    RexNode array =
+        b.makeCall(arrayType, SqlStdOperatorTable.ARRAY_VALUE_CONSTRUCTOR,
+        ImmutableList.of(
+            b.makeLiteral("1", varcharType)));
+
+    RexNode coalesce1 = b.makeCall(SqlStdOperatorTable.COALESCE, array, arrayZero);
+    assertThat(
+        coalesce1, hasToString(
+        "COALESCE(ARRAY('1'), CAST(ARRAY()):VARCHAR NOT NULL ARRAY NOT NULL)"));
+
+    RelDataType mapType = new MapSqlType(arrayType, arrayType, true);
+    RexNode mapZero = b.makeZeroForNestedType(mapType);
+
+    RexNode map =
+        b.makeCall(new MapSqlType(arrayType, arrayType, true),
+        SqlStdOperatorTable.MAP_VALUE_CONSTRUCTOR,
+        ImmutableList.of(array, array));
+
+    RexNode coalesce2 = b.makeCall(SqlStdOperatorTable.COALESCE, map, mapZero);
+    assertThat(
+        coalesce2, hasToString(
+        "COALESCE(MAP(ARRAY('1'), ARRAY('1')), "
+        + "CAST(MAP()):(VARCHAR NOT NULL ARRAY NOT NULL, VARCHAR NOT NULL ARRAY NOT NULL) MAP)"));
+  }
+
+  private static Stream<Arguments> testData4testMakeZeroForNestedType() {
+    RelDataTypeFactory typeFactory = new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT);
+    RexBuilder b = new RexBuilder(typeFactory);
+
+    RelDataType integerType = typeFactory.createSqlType(SqlTypeName.INTEGER);
+    RelDataType varcharType = typeFactory.createSqlType(SqlTypeName.VARCHAR);
+
+    // ARRAY<INTEGER>
+    RelDataType arrayType = new ArraySqlType(integerType, false);
+    RexNode expectedArray =
+        b.makeCast(
+            arrayType, b.makeCall(arrayType, SqlStdOperatorTable.ARRAY_VALUE_CONSTRUCTOR,
+            ImmutableList.of()));
+
+    // MULTISET<INTEGER>
+    RelDataType multisetType = new MultisetSqlType(integerType, false);
+    RexNode expectedMultiset =
+        b.makeCast(
+            multisetType, b.makeCall(multisetType, SqlStdOperatorTable.MULTISET_VALUE,
+            ImmutableList.of()));
+
+    // MAP<VARCHAR, INTEGER>
+    RelDataType mapType = new MapSqlType(varcharType, integerType, false);
+    RexNode expectedMap =
+        b.makeCast(
+            mapType, b.makeCall(mapType, SqlStdOperatorTable.MAP_VALUE_CONSTRUCTOR,
+            ImmutableList.of()));
+
+    // ROW<INTEGER, VARCHAR>
+    RelDataType rowType =
path: core/src/test/java/org/apache/calcite/rex/RexBuilderTest.java
user: login: xiedeyantu
body: Sorry, I didn't express myself clearly. I changed the zero value logic of ROW, and its corresponding validation will also be changed. I will keep the rest as it is.
created_at: 2025-04-08T23:29:20Z
author_association: MEMBER
diff_hunk: @@ -1104,6 +1107,140 @@ private static Stream<Arguments> testData4testMakeZeroLiteral() {
             relDataType -> new TimestampWithTimeZoneString(0, 1, 1, 0, 0, 0, "GMT+00:00")));
   }
 
+  /** Test case for
+   * <a href="https://issues.apache.org/jira/browse/CALCITE-6938">[CALCITE-6938]
+   * Support zero value creation of nested data types</a>. */
+  @ParameterizedTest
+  @MethodSource("testData4testMakeZeroForNestedType")
+  void testMakeZeroForNestedType(RelDataType type, RexNode expected) {
+    final RelDataTypeFactory typeFactory = new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT);
+    final RexBuilder rexBuilder = new RexBuilder(typeFactory);
+    assertThat(rexBuilder.makeZeroForNestedType(type), is(equalTo(expected)));
+  }
+
+  @Test void testCreateCoalesce() {
+    RelDataTypeFactory typeFactory = new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT);
+    RexBuilder b = new RexBuilder(typeFactory);
+    RelDataType varcharType = typeFactory.createSqlType(SqlTypeName.VARCHAR);
+
+    RelDataType arrayType = new ArraySqlType(varcharType, false);
+    RexNode arrayZero = b.makeZeroForNestedType(arrayType);
+
+    RexNode array =
+        b.makeCall(arrayType, SqlStdOperatorTable.ARRAY_VALUE_CONSTRUCTOR,
+        ImmutableList.of(
+            b.makeLiteral("1", varcharType)));
+
+    RexNode coalesce1 = b.makeCall(SqlStdOperatorTable.COALESCE, array, arrayZero);
+    assertThat(
+        coalesce1, hasToString(
+        "COALESCE(ARRAY('1'), CAST(ARRAY()):VARCHAR NOT NULL ARRAY NOT NULL)"));
+
+    RelDataType mapType = new MapSqlType(arrayType, arrayType, true);
+    RexNode mapZero = b.makeZeroForNestedType(mapType);
+
+    RexNode map =
+        b.makeCall(new MapSqlType(arrayType, arrayType, true),
+        SqlStdOperatorTable.MAP_VALUE_CONSTRUCTOR,
+        ImmutableList.of(array, array));
+
+    RexNode coalesce2 = b.makeCall(SqlStdOperatorTable.COALESCE, map, mapZero);
+    assertThat(
+        coalesce2, hasToString(
+        "COALESCE(MAP(ARRAY('1'), ARRAY('1')), "
+        + "CAST(MAP()):(VARCHAR NOT NULL ARRAY NOT NULL, VARCHAR NOT NULL ARRAY NOT NULL) MAP)"));
+  }
+
+  private static Stream<Arguments> testData4testMakeZeroForNestedType() {
+    RelDataTypeFactory typeFactory = new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT);
+    RexBuilder b = new RexBuilder(typeFactory);
+
+    RelDataType integerType = typeFactory.createSqlType(SqlTypeName.INTEGER);
+    RelDataType varcharType = typeFactory.createSqlType(SqlTypeName.VARCHAR);
+
+    // ARRAY<INTEGER>
+    RelDataType arrayType = new ArraySqlType(integerType, false);
+    RexNode expectedArray =
+        b.makeCast(
+            arrayType, b.makeCall(arrayType, SqlStdOperatorTable.ARRAY_VALUE_CONSTRUCTOR,
+            ImmutableList.of()));
+
+    // MULTISET<INTEGER>
+    RelDataType multisetType = new MultisetSqlType(integerType, false);
+    RexNode expectedMultiset =
+        b.makeCast(
+            multisetType, b.makeCall(multisetType, SqlStdOperatorTable.MULTISET_VALUE,
+            ImmutableList.of()));
+
+    // MAP<VARCHAR, INTEGER>
+    RelDataType mapType = new MapSqlType(varcharType, integerType, false);
+    RexNode expectedMap =
+        b.makeCast(
+            mapType, b.makeCall(mapType, SqlStdOperatorTable.MAP_VALUE_CONSTRUCTOR,
+            ImmutableList.of()));
+
+    // ROW<INTEGER, VARCHAR>
+    RelDataType rowType =
path: core/src/test/java/org/apache/calcite/rex/RexBuilderTest.java
user: login: mihaibudiu
body: the fact that there was no error shows that the validation is not strong enough. I don't think you should be able to create a ROW value with no fields when the type has fields.
created_at: 2025-04-08T23:30:44Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1104,6 +1107,140 @@ private static Stream<Arguments> testData4testMakeZeroLiteral() {
             relDataType -> new TimestampWithTimeZoneString(0, 1, 1, 0, 0, 0, "GMT+00:00")));
   }
 
+  /** Test case for
+   * <a href="https://issues.apache.org/jira/browse/CALCITE-6938">[CALCITE-6938]
+   * Support zero value creation of nested data types</a>. */
+  @ParameterizedTest
+  @MethodSource("testData4testMakeZeroForNestedType")
+  void testMakeZeroForNestedType(RelDataType type, RexNode expected) {
+    final RelDataTypeFactory typeFactory = new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT);
+    final RexBuilder rexBuilder = new RexBuilder(typeFactory);
+    assertThat(rexBuilder.makeZeroForNestedType(type), is(equalTo(expected)));
+  }
+
+  @Test void testCreateCoalesce() {
+    RelDataTypeFactory typeFactory = new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT);
+    RexBuilder b = new RexBuilder(typeFactory);
+    RelDataType varcharType = typeFactory.createSqlType(SqlTypeName.VARCHAR);
+
+    RelDataType arrayType = new ArraySqlType(varcharType, false);
+    RexNode arrayZero = b.makeZeroForNestedType(arrayType);
+
+    RexNode array =
+        b.makeCall(arrayType, SqlStdOperatorTable.ARRAY_VALUE_CONSTRUCTOR,
+        ImmutableList.of(
+            b.makeLiteral("1", varcharType)));
+
+    RexNode coalesce1 = b.makeCall(SqlStdOperatorTable.COALESCE, array, arrayZero);
+    assertThat(
+        coalesce1, hasToString(
+        "COALESCE(ARRAY('1'), CAST(ARRAY()):VARCHAR NOT NULL ARRAY NOT NULL)"));
+
+    RelDataType mapType = new MapSqlType(arrayType, arrayType, true);
+    RexNode mapZero = b.makeZeroForNestedType(mapType);
+
+    RexNode map =
+        b.makeCall(new MapSqlType(arrayType, arrayType, true),
+        SqlStdOperatorTable.MAP_VALUE_CONSTRUCTOR,
+        ImmutableList.of(array, array));
+
+    RexNode coalesce2 = b.makeCall(SqlStdOperatorTable.COALESCE, map, mapZero);
+    assertThat(
+        coalesce2, hasToString(
+        "COALESCE(MAP(ARRAY('1'), ARRAY('1')), "
+        + "CAST(MAP()):(VARCHAR NOT NULL ARRAY NOT NULL, VARCHAR NOT NULL ARRAY NOT NULL) MAP)"));
+  }
+
+  private static Stream<Arguments> testData4testMakeZeroForNestedType() {
+    RelDataTypeFactory typeFactory = new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT);
+    RexBuilder b = new RexBuilder(typeFactory);
+
+    RelDataType integerType = typeFactory.createSqlType(SqlTypeName.INTEGER);
+    RelDataType varcharType = typeFactory.createSqlType(SqlTypeName.VARCHAR);
+
+    // ARRAY<INTEGER>
+    RelDataType arrayType = new ArraySqlType(integerType, false);
+    RexNode expectedArray =
+        b.makeCast(
+            arrayType, b.makeCall(arrayType, SqlStdOperatorTable.ARRAY_VALUE_CONSTRUCTOR,
+            ImmutableList.of()));
+
+    // MULTISET<INTEGER>
+    RelDataType multisetType = new MultisetSqlType(integerType, false);
+    RexNode expectedMultiset =
+        b.makeCast(
+            multisetType, b.makeCall(multisetType, SqlStdOperatorTable.MULTISET_VALUE,
+            ImmutableList.of()));
+
+    // MAP<VARCHAR, INTEGER>
+    RelDataType mapType = new MapSqlType(varcharType, integerType, false);
+    RexNode expectedMap =
+        b.makeCast(
+            mapType, b.makeCall(mapType, SqlStdOperatorTable.MAP_VALUE_CONSTRUCTOR,
+            ImmutableList.of()));
+
+    // ROW<INTEGER, VARCHAR>
+    RelDataType rowType =
path: core/src/test/java/org/apache/calcite/rex/RexBuilderTest.java
user: login: mihaibudiu
body: if you find a way to validate the row, it will work for all other values.
created_at: 2025-04-08T23:31:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1917,6 +1917,27 @@ public RexLiteral makeZeroLiteral(RelDataType type) {
     return makeLiteral(zeroValue(type), type);
   }
 
+  public RexNode makeZeroForNestedType(RelDataType type) {
+    SqlOperator op;
+    switch (type.getSqlTypeName()) {
+    case ARRAY:
+      op = SqlStdOperatorTable.ARRAY_VALUE_CONSTRUCTOR;
+      break;
+    case MULTISET:
+      op = SqlStdOperatorTable.MULTISET_VALUE;
+      break;
+    case MAP:
+      op = SqlStdOperatorTable.MAP_VALUE_CONSTRUCTOR;
+      break;
+    case ROW:
+      op = SqlStdOperatorTable.ROW;
path: core/src/main/java/org/apache/calcite/rex/RexBuilder.java
user: login: xiedeyantu
body: Done!
created_at: 2025-04-10T13:19:28Z
author_association: MEMBER
diff_hunk: @@ -1104,6 +1107,140 @@ private static Stream<Arguments> testData4testMakeZeroLiteral() {
             relDataType -> new TimestampWithTimeZoneString(0, 1, 1, 0, 0, 0, "GMT+00:00")));
   }
 
+  /** Test case for
+   * <a href="https://issues.apache.org/jira/browse/CALCITE-6938">[CALCITE-6938]
+   * Support zero value creation of nested data types</a>. */
+  @ParameterizedTest
+  @MethodSource("testData4testMakeZeroForNestedType")
+  void testMakeZeroForNestedType(RelDataType type, RexNode expected) {
+    final RelDataTypeFactory typeFactory = new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT);
+    final RexBuilder rexBuilder = new RexBuilder(typeFactory);
+    assertThat(rexBuilder.makeZeroForNestedType(type), is(equalTo(expected)));
+  }
+
+  @Test void testCreateCoalesce() {
+    RelDataTypeFactory typeFactory = new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT);
+    RexBuilder b = new RexBuilder(typeFactory);
+    RelDataType varcharType = typeFactory.createSqlType(SqlTypeName.VARCHAR);
+
+    RelDataType arrayType = new ArraySqlType(varcharType, false);
+    RexNode arrayZero = b.makeZeroForNestedType(arrayType);
+
+    RexNode array =
+        b.makeCall(arrayType, SqlStdOperatorTable.ARRAY_VALUE_CONSTRUCTOR,
+        ImmutableList.of(
+            b.makeLiteral("1", varcharType)));
+
+    RexNode coalesce1 = b.makeCall(SqlStdOperatorTable.COALESCE, array, arrayZero);
+    assertThat(
+        coalesce1, hasToString(
+        "COALESCE(ARRAY('1'), CAST(ARRAY()):VARCHAR NOT NULL ARRAY NOT NULL)"));
+
+    RelDataType mapType = new MapSqlType(arrayType, arrayType, true);
+    RexNode mapZero = b.makeZeroForNestedType(mapType);
+
+    RexNode map =
+        b.makeCall(new MapSqlType(arrayType, arrayType, true),
+        SqlStdOperatorTable.MAP_VALUE_CONSTRUCTOR,
+        ImmutableList.of(array, array));
+
+    RexNode coalesce2 = b.makeCall(SqlStdOperatorTable.COALESCE, map, mapZero);
+    assertThat(
+        coalesce2, hasToString(
+        "COALESCE(MAP(ARRAY('1'), ARRAY('1')), "
+        + "CAST(MAP()):(VARCHAR NOT NULL ARRAY NOT NULL, VARCHAR NOT NULL ARRAY NOT NULL) MAP)"));
+  }
+
+  private static Stream<Arguments> testData4testMakeZeroForNestedType() {
+    RelDataTypeFactory typeFactory = new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT);
+    RexBuilder b = new RexBuilder(typeFactory);
+
+    RelDataType integerType = typeFactory.createSqlType(SqlTypeName.INTEGER);
+    RelDataType varcharType = typeFactory.createSqlType(SqlTypeName.VARCHAR);
+
+    // ARRAY<INTEGER>
+    RelDataType arrayType = new ArraySqlType(integerType, false);
+    RexNode expectedArray =
+        b.makeCast(
+            arrayType, b.makeCall(arrayType, SqlStdOperatorTable.ARRAY_VALUE_CONSTRUCTOR,
+            ImmutableList.of()));
+
+    // MULTISET<INTEGER>
+    RelDataType multisetType = new MultisetSqlType(integerType, false);
+    RexNode expectedMultiset =
+        b.makeCast(
+            multisetType, b.makeCall(multisetType, SqlStdOperatorTable.MULTISET_VALUE,
+            ImmutableList.of()));
+
+    // MAP<VARCHAR, INTEGER>
+    RelDataType mapType = new MapSqlType(varcharType, integerType, false);
+    RexNode expectedMap =
+        b.makeCast(
+            mapType, b.makeCall(mapType, SqlStdOperatorTable.MAP_VALUE_CONSTRUCTOR,
+            ImmutableList.of()));
+
+    // ROW<INTEGER, VARCHAR>
+    RelDataType rowType =
path: core/src/test/java/org/apache/calcite/rex/RexBuilderTest.java
user: login: xiedeyantu
body: Should I keep the test case writing similar to makeZeroLiteral?
created_at: 2025-04-10T13:22:08Z
author_association: MEMBER

PR comments:
user: login: mihaibudiu
created_at: 2025-04-10T17:07:20Z
body: Please squash the commits so we can merge this.
author_association: CONTRIBUTOR
user: login: xiedeyantu
created_at: 2025-04-10T22:45:20Z
body: > Please squash the commits so we can merge this.

@mihaibudiu Thanks, I have rebased and squashed.
author_association: MEMBER
user: login: sonarqubecloud[bot]
created_at: 2025-04-10T23:07:50Z
body: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=apache_calcite&pullRequest=4288) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [3 New issues](https://sonarcloud.io/project/issues?id=apache_calcite&pullRequest=4288&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=apache_calcite&pullRequest=4288&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=apache_calcite&pullRequest=4288&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [100.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=apache_calcite&pullRequest=4288&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=apache_calcite&pullRequest=4288&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=apache_calcite&pullRequest=4288)


author_association: NONE