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 b970e52254 [CALCITE-6741] The type of a comparison is nullable when
either operand is nullable
b970e52254 is described below
commit b970e522544aa4cc065cd486d1229ca72bd7f204
Author: Mihai Budiu <[email protected]>
AuthorDate: Thu Dec 19 17:12:52 2024 -0800
[CALCITE-6741] The type of a comparison is nullable when either operand is
nullable
Signed-off-by: Mihai Budiu <[email protected]>
---
.../validate/implicit/AbstractTypeCoercion.java | 53 +++++++++++-----------
.../org/apache/calcite/test/SqlValidatorTest.java | 2 +-
.../org/apache/calcite/test/TypeCoercionTest.java | 51 +++++++++++++++++++++
3 files changed, 79 insertions(+), 27 deletions(-)
diff --git
a/core/src/main/java/org/apache/calcite/sql/validate/implicit/AbstractTypeCoercion.java
b/core/src/main/java/org/apache/calcite/sql/validate/implicit/AbstractTypeCoercion.java
index 8c57c82214..9bf6de4945 100644
---
a/core/src/main/java/org/apache/calcite/sql/validate/implicit/AbstractTypeCoercion.java
+++
b/core/src/main/java/org/apache/calcite/sql/validate/implicit/AbstractTypeCoercion.java
@@ -495,6 +495,8 @@ public abstract class AbstractTypeCoercion implements
TypeCoercion {
return null;
}
+ boolean anyNullable = type1.isNullable() || type2.isNullable();
+
// this prevents the conversion between JavaType and normal RelDataType,
// as well as between JavaType and JavaType.
if (type1 instanceof RelDataTypeFactoryImpl.JavaType
@@ -516,35 +518,36 @@ public abstract class AbstractTypeCoercion implements
TypeCoercion {
// DATETIME < CHARACTER -> DATETIME
if (SqlTypeUtil.isCharacter(type1) && SqlTypeUtil.isDatetime(type2)) {
- return factory.createTypeWithNullability(type2, type1.isNullable());
+ return factory.createTypeWithNullability(type2, anyNullable);
}
if (SqlTypeUtil.isDatetime(type1) && SqlTypeUtil.isCharacter(type2)) {
- return factory.createTypeWithNullability(type1, type2.isNullable());
+ return factory.createTypeWithNullability(type1, anyNullable);
}
// DATE < TIMESTAMP -> TIMESTAMP
if (SqlTypeUtil.isDate(type1) && SqlTypeUtil.isTimestamp(type2)) {
- return factory.createTypeWithNullability(type2, type1.isNullable());
+ return factory.createTypeWithNullability(type2, anyNullable);
}
if (SqlTypeUtil.isDate(type2) && SqlTypeUtil.isTimestamp(type1)) {
- return factory.createTypeWithNullability(type1, type2.isNullable());
+ return factory.createTypeWithNullability(type1, anyNullable);
}
if (SqlTypeUtil.isString(type1) && typeName2 == SqlTypeName.NULL) {
- return factory.createTypeWithNullability(type1, type2.isNullable());
+ return factory.createTypeWithNullability(type1, true);
}
if (typeName1 == SqlTypeName.NULL && SqlTypeUtil.isString(type2)) {
- return factory.createTypeWithNullability(type2, type1.isNullable());
+ return factory.createTypeWithNullability(type2, true);
}
if (SqlTypeUtil.isDecimal(type1) && SqlTypeUtil.isCharacter(type2)
|| SqlTypeUtil.isCharacter(type1) && SqlTypeUtil.isDecimal(type2)) {
// There is no proper DECIMAL type for VARCHAR, using max
precision/scale DECIMAL
// as the best we can do.
- return SqlTypeUtil.getMaxPrecisionScaleDecimal(factory);
+ return factory.createTypeWithNullability(
+ SqlTypeUtil.getMaxPrecisionScaleDecimal(factory), anyNullable);
}
// Keep sync with MS-SQL:
@@ -562,13 +565,13 @@ public abstract class AbstractTypeCoercion implements
TypeCoercion {
if (SqlTypeUtil.isString(type1) && SqlTypeUtil.isString(type2)) {
// Return the string with the larger precision
if (type1.getPrecision() == RelDataType.PRECISION_NOT_SPECIFIED) {
- return factory.createTypeWithNullability(type1, type2.isNullable());
+ return factory.createTypeWithNullability(type1, anyNullable);
} else if (type2.getPrecision() == RelDataType.PRECISION_NOT_SPECIFIED) {
- return factory.createTypeWithNullability(type2, type1.isNullable());
+ return factory.createTypeWithNullability(type2, anyNullable);
} else if (type1.getPrecision() > type2.getPrecision()) {
- return factory.createTypeWithNullability(type1, type2.isNullable());
+ return factory.createTypeWithNullability(type1, anyNullable);
} else {
- return factory.createTypeWithNullability(type2, type1.isNullable());
+ return factory.createTypeWithNullability(type2, anyNullable);
}
}
@@ -577,40 +580,40 @@ public abstract class AbstractTypeCoercion implements
TypeCoercion {
if (SqlTypeUtil.isTimestamp(type1)) {
return null;
}
- return factory.createTypeWithNullability(type1, type2.isNullable());
+ return factory.createTypeWithNullability(type1, anyNullable);
}
if (SqlTypeUtil.isCharacter(type1) && SqlTypeUtil.isAtomic(type2)) {
if (SqlTypeUtil.isTimestamp(type2)) {
return null;
}
- return factory.createTypeWithNullability(type2, type1.isNullable());
+ return factory.createTypeWithNullability(type2, anyNullable);
}
if (validator.config().conformance().allowLenientCoercion()) {
if (SqlTypeUtil.isString(type1) && SqlTypeUtil.isArray(type2)) {
- return factory.createTypeWithNullability(type2, type1.isNullable());
+ return factory.createTypeWithNullability(type2, anyNullable);
}
if (SqlTypeUtil.isString(type2) && SqlTypeUtil.isArray(type1)) {
- return factory.createTypeWithNullability(type1, type2.isNullable());
+ return factory.createTypeWithNullability(type1, anyNullable);
}
}
if (SqlTypeUtil.isApproximateNumeric(type1) &&
SqlTypeUtil.isApproximateNumeric(type2)) {
if (type1.getPrecision() > type2.getPrecision()) {
- return factory.createTypeWithNullability(type1, type2.isNullable());
+ return factory.createTypeWithNullability(type1, anyNullable);
} else {
- return factory.createTypeWithNullability(type2, type1.isNullable());
+ return factory.createTypeWithNullability(type2, anyNullable);
}
}
if (SqlTypeUtil.isApproximateNumeric(type1) &&
SqlTypeUtil.isExactNumeric(type2)) {
- return factory.createTypeWithNullability(type1, type2.isNullable());
+ return factory.createTypeWithNullability(type1, anyNullable);
}
if (SqlTypeUtil.isApproximateNumeric(type2) &&
SqlTypeUtil.isExactNumeric(type1)) {
- return factory.createTypeWithNullability(type2, type1.isNullable());
+ return factory.createTypeWithNullability(type2, anyNullable);
}
if (SqlTypeUtil.isExactNumeric(type1) &&
SqlTypeUtil.isExactNumeric(type2)) {
@@ -622,12 +625,12 @@ public abstract class AbstractTypeCoercion implements
TypeCoercion {
Math.max(type1.getPrecision() - type1.getScale(),
type2.getPrecision() - type2.getScale()) + maxScale,
maxScale);
- return factory.createTypeWithNullability(result, type1.isNullable() ||
type2.isNullable());
+ return factory.createTypeWithNullability(result, anyNullable);
}
if (type1.getPrecision() > type2.getPrecision()) {
- return factory.createTypeWithNullability(type1, type2.isNullable());
+ return factory.createTypeWithNullability(type1, anyNullable);
} else {
- return factory.createTypeWithNullability(type2, type1.isNullable());
+ return factory.createTypeWithNullability(type2, anyNullable);
}
}
@@ -643,8 +646,7 @@ public abstract class AbstractTypeCoercion implements
TypeCoercion {
}
// The only maxCardinality that seems to be supported is -1, i.e.,
unlimited.
RelDataType resultType = factory.createArrayType(type, -1);
- return factory.createTypeWithNullability(
- resultType, type1.isNullable() || type2.isNullable());
+ return factory.createTypeWithNullability(resultType, anyNullable);
}
if (typeName1 == SqlTypeName.MAP) {
@@ -664,8 +666,7 @@ public abstract class AbstractTypeCoercion implements
TypeCoercion {
return null;
}
RelDataType resultType = factory.createMapType(keyType, valueType);
- return factory.createTypeWithNullability(
- resultType, type1.isNullable() || type2.isNullable());
+ return factory.createTypeWithNullability(resultType, anyNullable);
}
if (typeName1 == SqlTypeName.ROW) {
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 24951049ef..7d71667455 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
@@ -4480,7 +4480,7 @@ public class SqlValidatorTest extends
SqlValidatorTestCase {
expr("(1,2) in ((1,2), (3,4))")
.columnType("BOOLEAN NOT NULL");
expr("'medium' in (cast(null as varchar(10)), 'bc')")
- .columnType("BOOLEAN NOT NULL");
+ .columnType("BOOLEAN");
// nullability depends on nullability of both sides
sql("select empno in (1, 2) from emp")
diff --git a/core/src/test/java/org/apache/calcite/test/TypeCoercionTest.java
b/core/src/test/java/org/apache/calcite/test/TypeCoercionTest.java
index 7377b9cfe1..c422832454 100644
--- a/core/src/test/java/org/apache/calcite/test/TypeCoercionTest.java
+++ b/core/src/test/java/org/apache/calcite/test/TypeCoercionTest.java
@@ -409,6 +409,57 @@ class TypeCoercionTest {
f.comparisonCommonType(f.recordType("a", f.arrayType(f.intType)),
f.recordType("a", f.arrayType(f.intType)),
f.recordType("a", f.arrayType(f.intType)));
+
+ // Nullable types
+ // BOOLEAN
+ f.comparisonCommonType(f.booleanType, f.nullableBooleanType,
f.nullableBooleanType);
+ f.comparisonCommonType(f.nullableBooleanType, f.booleanType,
f.nullableBooleanType);
+ f.comparisonCommonType(f.nullableBooleanType, f.nullableBooleanType,
f.nullableBooleanType);
+ f.comparisonCommonType(f.nullableIntType, f.booleanType, null);
+ f.comparisonCommonType(f.bigintType, f.nullableBooleanType, null);
+ // INT
+ f.comparisonCommonType(f.nullableSmallintType, f.intType,
f.nullableIntType);
+ f.comparisonCommonType(f.smallintType, f.nullableBigintType,
f.nullableBigintType);
+ f.comparisonCommonType(f.nullableIntType, f.bigintType,
f.nullableBigintType);
+ f.comparisonCommonType(f.bigintType, f.nullableBigintType,
f.nullableBigintType);
+ // FLOAT/DOUBLE
+ f.comparisonCommonType(f.realType, f.nullableDoubleType,
f.nullableDoubleType);
+ f.comparisonCommonType(f.nullableRealType, f.realType, f.nullableRealType);
+ f.comparisonCommonType(f.doubleType, f.nullableDoubleType,
f.nullableDoubleType);
+ // EXACT + FRACTIONAL
+ f.comparisonCommonType(f.intType, f.nullableRealType, f.nullableRealType);
+ f.comparisonCommonType(f.nullableIntType, f.doubleType,
f.nullableDoubleType);
+ f.comparisonCommonType(f.bigintType, f.nullableRealType,
f.nullableRealType);
+ f.comparisonCommonType(f.nullableBigintType, f.doubleType,
f.nullableDoubleType);
+
+ RelDataType nullableDecimal54 =
+ f.typeFactory.createTypeWithNullability(
+ f.typeFactory.createSqlType(SqlTypeName.DECIMAL, 5, 4), true);
+ RelDataType nullableDecimal144 =
+ f.typeFactory.createTypeWithNullability(
+ f.typeFactory.createSqlType(SqlTypeName.DECIMAL, 14, 4), true);
+ f.comparisonCommonType(nullableDecimal54, f.doubleType,
f.nullableDoubleType);
+ f.comparisonCommonType(decimal54, f.nullableIntType, nullableDecimal144);
+ // CHAR/VARCHAR
+ f.comparisonCommonType(f.nullableCharType, f.varcharType,
f.nullableVarcharType);
+ f.comparisonCommonType(f.intType, f.nullableCharType, f.nullableIntType);
+ f.comparisonCommonType(f.doubleType, f.nullableCharType,
f.nullableDoubleType);
+ // TIMESTAMP
+ f.comparisonCommonType(f.timestampType, f.nullableTimestampType,
f.nullableTimestampType);
+ f.comparisonCommonType(f.nullableDateType, f.timestampType,
f.nullableTimestampType);
+ f.comparisonCommonType(f.nullableIntType, f.timestampType, null);
+ f.comparisonCommonType(f.varcharType, f.nullableTimestampType,
f.nullableTimestampType);
+ // generic
+ f.comparisonCommonType(f.charType, f.mapType(f.intType,
f.nullableCharType), null);
+ f.comparisonCommonType(f.arrayType(f.nullableIntType),
f.recordType(ImmutableList.of()),
+ null);
+ f.comparisonCommonType(f.recordType("a", f.nullableIntType),
+ f.recordType("a", f.intType), f.recordType("a", f.nullableIntType));
+ f.comparisonCommonType(f.recordType("a", f.intType),
+ f.recordType("a", f.nullableCharType), f.recordType("a",
f.nullableIntType));
+ f.comparisonCommonType(f.recordType("a", f.arrayType(f.nullableIntType)),
+ f.recordType("a", f.arrayType(f.intType)),
+ f.recordType("a", f.arrayType(f.nullableIntType)));
}
/**