This is an automated email from the ASF dual-hosted git repository.

xuzifu666 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 2311114e3b [CALCITE-6091] Char that in array or map is truncated if 
CASE WHEN statement contains null
2311114e3b is described below

commit 2311114e3be8a0c3e804974ad39f1c789d9aecb4
Author: Yu Xu <[email protected]>
AuthorDate: Sat Oct 31 19:47:39 2026 +0800

    [CALCITE-6091] Char that in array or map is truncated if CASE WHEN 
statement contains null
---
 .../calcite/rel/type/RelDataTypeFactoryImpl.java   | 30 ++++++++++++++++++--
 .../calcite/sql/type/SqlTypeFactoryTest.java       | 33 ++++++++++++++++++++++
 core/src/test/resources/sql/cast.iq                | 28 ++++++++++++++++++
 3 files changed, 88 insertions(+), 3 deletions(-)

diff --git 
a/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeFactoryImpl.java 
b/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeFactoryImpl.java
index c0d90578fa..0c7655fd73 100644
--- a/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeFactoryImpl.java
+++ b/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeFactoryImpl.java
@@ -260,16 +260,28 @@ private RelDataType createStructType(
   protected @Nullable RelDataType leastRestrictiveArrayMultisetType(
       final List<RelDataType> types, SqlTypeName sqlTypeName) {
     assert sqlTypeName == SqlTypeName.ARRAY || sqlTypeName == 
SqlTypeName.MULTISET;
+    // Filter out NULL types; they only affect nullability, not the component 
type.
+    // E.g., leastRestrictive(ARRAY<CHAR(3)>, ARRAY<CHAR(1)>, NULL) should 
yield
+    // ARRAY<CHAR(3)> nullable, not fail.
+    final List<RelDataType> nonNullTypes = new ArrayList<>();
     boolean isNullable = false;
     for (RelDataType type : types) {
+      if (type.getSqlTypeName() == SqlTypeName.NULL) {
+        isNullable = true;
+        continue;
+      }
       if (type.getComponentType() == null) {
         return null;
       }
+      nonNullTypes.add(type);
       isNullable |= type.isNullable();
     }
+    if (nonNullTypes.isEmpty()) {
+      return null;
+    }
     final RelDataType type =
         leastRestrictive(
-            Util.transform(types,
+            Util.transform(nonNullTypes,
                 t -> t instanceof ArraySqlType
                     ? ((ArraySqlType) t).getComponentType()
                     : ((MultisetSqlType) t).getComponentType()));
@@ -285,22 +297,34 @@ private RelDataType createStructType(
   protected @Nullable RelDataType leastRestrictiveMapType(
       final List<RelDataType> types, SqlTypeName sqlTypeName) {
     assert sqlTypeName == SqlTypeName.MAP;
+    // Filter out NULL types; they only affect nullability, not key/value 
types.
+    // E.g., leastRestrictive(MAP<CHAR(10),CHAR(10)>, MAP<CHAR(1),CHAR(1)>, 
NULL)
+    // should yield MAP<CHAR(10),CHAR(10)> nullable, not fail.
+    final List<RelDataType> nonNullTypes = new ArrayList<>();
     boolean isNullable = false;
     for (RelDataType type : types) {
+      if (type.getSqlTypeName() == SqlTypeName.NULL) {
+        isNullable = true;
+        continue;
+      }
       if (!(type instanceof MapSqlType)) {
         return null;
       }
+      nonNullTypes.add(type);
       isNullable |= type.isNullable();
     }
+    if (nonNullTypes.isEmpty()) {
+      return null;
+    }
     final RelDataType keyType =
         leastRestrictive(
-            Util.transform(types, t -> ((MapSqlType) t).getKeyType()));
+            Util.transform(nonNullTypes, t -> ((MapSqlType) t).getKeyType()));
     if (keyType == null) {
       return null;
     }
     final RelDataType valueType =
         leastRestrictive(
-            Util.transform(types, t -> ((MapSqlType) t).getValueType()));
+            Util.transform(nonNullTypes, t -> ((MapSqlType) 
t).getValueType()));
     if (valueType == null) {
       return null;
     }
diff --git 
a/core/src/test/java/org/apache/calcite/sql/type/SqlTypeFactoryTest.java 
b/core/src/test/java/org/apache/calcite/sql/type/SqlTypeFactoryTest.java
index 68a34eb5ba..5c9b38ac34 100644
--- a/core/src/test/java/org/apache/calcite/sql/type/SqlTypeFactoryTest.java
+++ b/core/src/test/java/org/apache/calcite/sql/type/SqlTypeFactoryTest.java
@@ -150,6 +150,23 @@ private void checkUnknownWithType(SqlTypeFixture f, 
RelDataType type) {
     assertThat(leastRestrictive.getComponentType().getPrecision(), is(10));
   }
 
+  /** Test case for
+   * <a 
href="https://issues.apache.org/jira/browse/CALCITE-6091";>[CALCITE-6091]
+   * CASE WHEN with ARRAY/MAP types and NULL ELSE branch should not truncate
+   * element type</a>. */
+  @Test void testLeastRestrictiveForArraysWithNull() {
+    SqlTypeFixture f = new SqlTypeFixture();
+    // Simulates: CASE WHEN true THEN ARRAY['abc'] WHEN false THEN ARRAY['d'] 
ELSE NULL END
+    // Types: ARRAY<CHAR(3)>, ARRAY<CHAR(1)>, NULL
+    RelDataType leastRestrictive =
+        f.typeFactory.leastRestrictive(
+            Lists.newArrayList(f.arraySqlChar10, f.arraySqlChar1, f.sqlNull));
+    assertThat(leastRestrictive, notNullValue());
+    assertThat(leastRestrictive.getSqlTypeName(), is(SqlTypeName.ARRAY));
+    assertThat(leastRestrictive.isNullable(), is(true));
+    assertThat(leastRestrictive.getComponentType().getPrecision(), is(10));
+  }
+
   @Test void testLeastRestrictiveForMultisets() {
     SqlTypeFixture f = new SqlTypeFixture();
     RelDataType leastRestrictive =
@@ -189,6 +206,22 @@ private void checkUnknownWithType(SqlTypeFixture f, 
RelDataType type) {
     assertThat(leastRestrictive.getValueType().getPrecision(), is(10));
   }
 
+  /** Test case for
+   * <a 
href="https://issues.apache.org/jira/browse/CALCITE-6091";>[CALCITE-6091]
+   * CASE WHEN with MAP types and NULL ELSE branch should not fail type 
inference</a>. */
+  @Test void testLeastRestrictiveForMapsWithNull() {
+    SqlTypeFixture f = new SqlTypeFixture();
+    // Simulates: CASE WHEN ... THEN MAP[...] WHEN ... THEN MAP[...] ELSE NULL 
END
+    RelDataType leastRestrictive =
+        f.typeFactory.leastRestrictive(
+            Lists.newArrayList(f.mapSqlChar10Nullable, f.mapSqlChar1, 
f.sqlNull));
+    assertThat(leastRestrictive, notNullValue());
+    assertThat(leastRestrictive.getSqlTypeName(), is(SqlTypeName.MAP));
+    assertThat(leastRestrictive.isNullable(), is(true));
+    assertThat(leastRestrictive.getKeyType().getPrecision(), is(10));
+    assertThat(leastRestrictive.getValueType().getPrecision(), is(10));
+  }
+
   @Test void testLeastRestrictiveForTimestamps() {
     SqlTypeFixture f = new SqlTypeFixture();
     RelDataType leastRestrictive =
diff --git a/core/src/test/resources/sql/cast.iq 
b/core/src/test/resources/sql/cast.iq
index 9ec5a91829..80ffc8fc41 100644
--- a/core/src/test/resources/sql/cast.iq
+++ b/core/src/test/resources/sql/cast.iq
@@ -2241,4 +2241,32 @@ WHERE empno = 7369;
 
 !ok
 
+# Test case for [CALCITE-6091] CASE WHEN with ARRAY/MAP types and NULL ELSE
+# branch should not truncate element type
+!use scott
+
+SELECT CASE WHEN true THEN ARRAY['abc'] WHEN false THEN ARRAY['d'] ELSE null 
END AS c;
++-------+
+| C     |
++-------+
+| [abc] |
++-------+
+(1 row)
+
+!ok
+C CHAR(3) ARRAY
+!type
+
+SELECT CASE WHEN true THEN ARRAY[1, 2] WHEN false THEN ARRAY[3] ELSE null END 
AS c;
++--------+
+| C      |
++--------+
+| [1, 2] |
++--------+
+(1 row)
+
+!ok
+C INTEGER ARRAY
+!type
+
 # End cast.iq

Reply via email to