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)^",