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 8c57c82214f..9bf6de49451 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 @@ private RelDataType getTightestCommonTypeOrThrow( 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 @@ private RelDataType getTightestCommonTypeOrThrow( // 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 @@ private RelDataType getTightestCommonTypeOrThrow( 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 @@ private RelDataType getTightestCommonTypeOrThrow( 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 @@ private RelDataType getTightestCommonTypeOrThrow( 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 @@ private RelDataType getTightestCommonTypeOrThrow( } // 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 @@ private RelDataType getTightestCommonTypeOrThrow( 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 24951049ef7..7d716674554 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 @@ private void checkNegWindow(String s, String msg) { 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 7377b9cfe16..c4228324544 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 @@ private static ImmutableList combine( 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))); } /**