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