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

mbudiu 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 c4b0b2e7bf [CALCITE-6920] The type derived for a cast to INT ARRAY 
always has non-nullable elements
c4b0b2e7bf is described below

commit c4b0b2e7bf859a9b045389f323f7b678226caa04
Author: Mihai Budiu <[email protected]>
AuthorDate: Thu Mar 27 15:42:41 2025 -0700

    [CALCITE-6920] The type derived for a cast to INT ARRAY always has 
non-nullable elements
    
    Signed-off-by: Mihai Budiu <[email protected]>
---
 .../java/org/apache/calcite/rel/type/RelDataType.java  |  2 +-
 .../org/apache/calcite/rel/type/RelDataTypeSystem.java |  5 +++++
 .../apache/calcite/sql/SqlCollectionTypeNameSpec.java  |  4 +++-
 .../org/apache/calcite/sql/SqlMapTypeNameSpec.java     | 12 +++++++++++-
 .../apache/calcite/sql/fun/SqlLibraryOperators.java    |  2 +-
 .../java/org/apache/calcite/test/SqlValidatorTest.java | 15 ++++++++++-----
 .../java/org/apache/calcite/test/SqlOperatorTest.java  | 18 +++++++++---------
 7 files changed, 40 insertions(+), 18 deletions(-)

diff --git a/core/src/main/java/org/apache/calcite/rel/type/RelDataType.java 
b/core/src/main/java/org/apache/calcite/rel/type/RelDataType.java
index e1fa9fcfd7..082bd86a3e 100644
--- a/core/src/main/java/org/apache/calcite/rel/type/RelDataType.java
+++ b/core/src/main/java/org/apache/calcite/rel/type/RelDataType.java
@@ -309,7 +309,7 @@ default boolean 
equalsSansFieldNamesAndNullability(@Nullable RelDataType that) {
       }
       return true;
     } else {
-      return equals(that);
+      return SqlTypeUtil.equalSansNullability(this, that);
     }
   }
 
diff --git 
a/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeSystem.java 
b/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeSystem.java
index 4d1995f7d5..5becafc2b7 100644
--- a/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeSystem.java
+++ b/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeSystem.java
@@ -150,6 +150,11 @@ default int getMaxNumericPrecision() {
   /** Returns the rounding behavior for numerical operations capable of 
discarding precision. */
   RoundingMode roundingMode();
 
+  /** Returns whether a MAP type is allowed to have nullable keys. */
+  default boolean mapKeysCanBeNullable() {
+    return false;
+  }
+
   /** Returns the LITERAL string for the type, either PREFIX/SUFFIX. */
   @Nullable String getLiteral(SqlTypeName typeName, boolean isPrefix);
 
diff --git 
a/core/src/main/java/org/apache/calcite/sql/SqlCollectionTypeNameSpec.java 
b/core/src/main/java/org/apache/calcite/sql/SqlCollectionTypeNameSpec.java
index ed996cc321..05be5ca3d4 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlCollectionTypeNameSpec.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlCollectionTypeNameSpec.java
@@ -81,7 +81,9 @@ public SqlTypeNameSpec getElementTypeName() {
   }
 
   @Override public RelDataType deriveType(SqlValidator validator) {
-    final RelDataType type = elementTypeName.deriveType(validator);
+    RelDataType type = elementTypeName.deriveType(validator);
+    // We have to assume that elements may be nullable
+    type = validator.getTypeFactory().enforceTypeWithNullability(type, true);
     return createCollectionType(type, validator.getTypeFactory());
   }
 
diff --git a/core/src/main/java/org/apache/calcite/sql/SqlMapTypeNameSpec.java 
b/core/src/main/java/org/apache/calcite/sql/SqlMapTypeNameSpec.java
index 37520fc834..146d510f5d 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlMapTypeNameSpec.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlMapTypeNameSpec.java
@@ -55,9 +55,19 @@ public SqlDataTypeSpec getValType() {
   }
 
   @Override public RelDataType deriveType(SqlValidator validator) {
+    RelDataType kType = keyType.deriveType(validator);
+    if (validator.getTypeFactory().getTypeSystem().mapKeysCanBeNullable()) {
+      // We have to assume that keys may be nullable
+      kType = validator.getTypeFactory().enforceTypeWithNullability(kType, 
true);
+    }
+
+    RelDataType valueType = valType.deriveType(validator);
+    // We have to assume that values may be nullable
+    valueType = 
validator.getTypeFactory().enforceTypeWithNullability(valueType, true);
+
     return validator
         .getTypeFactory()
-        .createMapType(keyType.deriveType(validator), 
valType.deriveType(validator));
+        .createMapType(kType, valueType);
   }
 
   @Override public void unparse(SqlWriter writer, int leftPrec, int rightPrec) 
{
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 48f323dc1c..cd1cd08ac6 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
@@ -1553,7 +1553,7 @@ private static RelDataType 
arrayInsertReturnType(SqlOperatorBinding opBinding) {
     type = opBinding.getTypeFactory().createTypeWithNullability(type, true);
     // make explicit CAST for array elements and inserted element to the 
biggest type
     // if array component type not equals to inserted element type
-    if (!componentType.equalsSansFieldNames(elementType2)) {
+    if (!componentType.equalsSansFieldNamesAndNullability(elementType2)) {
       // For array_insert, 0 is the array arg and 2 is the inserted element
       SqlValidatorUtil.
           adjustTypeForArrayFunctions(type, opBinding, 2);
diff --git a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java 
b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
index 31d191c667..8f96fcec2e 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
@@ -1469,7 +1469,7 @@ void testLikeAndSimilarFails() {
     expr("cast(ARRAY[1,2,3] AS VARIANT ARRAY)")
         .columnType("VARIANT NOT NULL ARRAY NOT NULL");
     expr("cast(MAP['a','b','c','d'] AS MAP<VARCHAR, VARIANT>)")
-        .columnType("(VARCHAR NOT NULL, VARIANT NOT NULL) MAP NOT NULL");
+        .columnType("(VARCHAR NOT NULL, VARIANT) MAP NOT NULL");
   }
 
   @Test void testAccessVariant() {
@@ -7967,6 +7967,11 @@ void testGroupExpressionEquivalenceParams() {
     sql("select cast(a as ^MyUDT^ array multiset) from COMPLEXTYPES.CTC_T1")
         .withExtendedCatalog()
         .fails("Unknown identifier 'MYUDT'");
+    // Test case for [CALCITE-6920]
+    // https://issues.apache.org/jira/projects/CALCITE/issues/CALCITE-6920
+    // The type derived for a cast to INT ARRAY always has non-nullable 
elements
+    expr("cast(CAST (ARRAY[1,null] AS VARIANT) AS INT ARRAY)")
+        .columnType("INTEGER ARRAY");
   }
 
   /**
@@ -7977,16 +7982,16 @@ void testGroupExpressionEquivalenceParams() {
   @Test void testCastMapType() {
     sql("select cast(\"int2IntMapType\" as map<int,int>) from 
COMPLEXTYPES.CTC_T1")
         .withExtendedCatalog()
-        .columnType("(INTEGER NOT NULL, INTEGER NOT NULL) MAP NOT NULL");
+        .columnType("(INTEGER NOT NULL, INTEGER) MAP NOT NULL");
     sql("select cast(\"int2varcharArrayMapType\" as map<int,varchar array>) "
         + "from COMPLEXTYPES.CTC_T1")
         .withExtendedCatalog()
-        .columnType("(INTEGER NOT NULL, VARCHAR NOT NULL ARRAY NOT NULL) MAP 
NOT NULL");
+        .columnType("(INTEGER NOT NULL, VARCHAR ARRAY) MAP NOT NULL");
     sql("select cast(\"varcharMultiset2IntIntMapType\" as map<varchar(5) 
multiset, map<int, int>>)"
         + " from COMPLEXTYPES.CTC_T1")
         .withExtendedCatalog()
-        .columnType("(VARCHAR(5) NOT NULL MULTISET NOT NULL, "
-            + "(INTEGER NOT NULL, INTEGER NOT NULL) MAP NOT NULL) MAP NOT 
NULL");
+        .columnType("(VARCHAR(5) MULTISET NOT NULL, "
+            + "(INTEGER NOT NULL, INTEGER) MAP) MAP NOT NULL");
   }
 
   @Test void testCastAsRowType() {
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 2abf8b0e0d..09e3ce7790 100644
--- a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
+++ b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
@@ -1824,7 +1824,7 @@ void testCastToBoolean(CastType castType, 
SqlOperatorFixture f) {
     f.checkScalar("cast(cast(CAST('abc' AS VARCHAR) as VARIANT) AS VARCHAR)", 
"abc",
         "VARCHAR");
     f.checkScalar("cast(cast(ARRAY[1,2,3] as VARIANT) AS INTEGER ARRAY)", "[1, 
2, 3]",
-        "INTEGER NOT NULL ARRAY");
+        "INTEGER ARRAY");
     f.checkScalar("cast(cast('abc' as VARIANT) AS VARCHAR)", "abc", "VARCHAR");
     f.checkScalar("cast(cast('abc' as VARIANT) AS CHAR(3))", "abc", "CHAR(3)");
 
@@ -7975,7 +7975,7 @@ void checkRegexpExtract(SqlOperatorFixture f0, 
FunctionAlias functionAlias) {
     f.checkScalar("array_append(array[map[1, 'a']], map[2, 'b'])", "[{1=a}, 
{2=b}]",
         "(INTEGER NOT NULL, CHAR(1) NOT NULL) MAP NOT NULL ARRAY NOT NULL");
     f.checkNull("array_append(cast(null as integer array), 1)");
-    f.checkType("array_append(cast(null as integer array), 1)", "INTEGER NOT 
NULL ARRAY");
+    f.checkType("array_append(cast(null as integer array), 1)", "INTEGER 
ARRAY");
     f.checkFails("^array_append(array[1, 2], true)^",
         "INTEGER is not comparable to BOOLEAN", false);
 
@@ -8312,7 +8312,7 @@ void checkRegexpExtract(SqlOperatorFixture f0, 
FunctionAlias functionAlias) {
     f.checkScalar("array_prepend(array[map[1, 'a']], map[2, 'b'])", "[{2=b}, 
{1=a}]",
         "(INTEGER NOT NULL, CHAR(1) NOT NULL) MAP NOT NULL ARRAY NOT NULL");
     f.checkNull("array_prepend(cast(null as integer array), 1)");
-    f.checkType("array_prepend(cast(null as integer array), 1)", "INTEGER NOT 
NULL ARRAY");
+    f.checkType("array_prepend(cast(null as integer array), 1)", "INTEGER 
ARRAY");
     f.checkFails("^array_prepend(array[1, 2], true)^",
         "INTEGER is not comparable to BOOLEAN", false);
 
@@ -8377,7 +8377,7 @@ void checkRegexpExtract(SqlOperatorFixture f0, 
FunctionAlias functionAlias) {
       f.checkScalar("array_remove(array[map[1, 'a']], map[1, 'a'])", "[]",
           "(INTEGER NOT NULL, CHAR(1) NOT NULL) MAP NOT NULL ARRAY NOT NULL");
       f.checkNull("array_remove(cast(null as integer array), 1)");
-      f.checkType("array_remove(cast(null as integer array), 1)", "INTEGER NOT 
NULL ARRAY");
+      f.checkType("array_remove(cast(null as integer array), 1)", "INTEGER 
ARRAY");
 
       // Flink and Spark differ on the following. The expression
       //   array_remove(array[1, null], cast(null as integer))
@@ -8823,10 +8823,10 @@ void checkArrayReverseFunc(SqlOperatorFixture f0, 
SqlFunction function,
 
     f.checkNull("arrays_zip(cast(null as integer array), array[1, 2])");
     f.checkType("arrays_zip(cast(null as integer array), array[1, 2])",
-        "RecordType(INTEGER NOT NULL 0, INTEGER NOT NULL 1) NOT NULL ARRAY");
+        "RecordType(INTEGER 0, INTEGER NOT NULL 1) NOT NULL ARRAY");
     f.checkNull("arrays_zip(array[1, 2], cast(null as integer array))");
     f.checkType("arrays_zip(array[1, 2], cast(null as integer array))",
-        "RecordType(INTEGER NOT NULL 0, INTEGER NOT NULL 1) NOT NULL ARRAY");
+        "RecordType(INTEGER NOT NULL 0, INTEGER 1) NOT NULL ARRAY");
     f.checkFails("^arrays_zip(array[1, 2], true)^",
         "Parameters must be of the same type", false);
   }
@@ -9012,10 +9012,10 @@ void checkArrayReverseFunc(SqlOperatorFixture f0, 
SqlFunction function,
     // test operand is null map
     f1.checkNull("map_concat(map('foo', 1), cast(null as map<varchar, int>))");
     f1.checkType("map_concat(map('foo', 1), cast(null as map<varchar, int>))",
-        "(VARCHAR NOT NULL, INTEGER NOT NULL) MAP");
+        "(VARCHAR NOT NULL, INTEGER) MAP");
     f1.checkNull("map_concat(cast(null as map<varchar, int>), map['foo', 1])");
     f1.checkType("map_concat(cast(null as map<varchar, int>), map['foo', 1])",
-        "(VARCHAR NOT NULL, INTEGER NOT NULL) MAP");
+        "(VARCHAR NOT NULL, INTEGER) MAP");
 
     f1.checkFails("^map_concat(map('foo', 1), null)^",
         "Function 'MAP_CONCAT' should all be of type map, "
@@ -9214,7 +9214,7 @@ void checkArrayReverseFunc(SqlOperatorFixture f0, 
SqlFunction function,
     f.checkScalar("map_from_arrays(array(), array())",
         "{}", "(UNKNOWN NOT NULL, UNKNOWN NOT NULL) MAP NOT NULL");
     f.checkType("map_from_arrays(cast(null as integer array), array['foo', 
'bar'])",
-        "(INTEGER NOT NULL, CHAR(3) NOT NULL) MAP");
+        "(INTEGER, CHAR(3) NOT NULL) MAP");
     f.checkNull("map_from_arrays(cast(null as integer array), array['foo', 
'bar'])");
 
     f.checkFails("^map_from_arrays(array[1, 2], 2)^",

Reply via email to