Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[CALCITE-6741] The type of a comparison is nullable when either opera… #4102

Merged
merged 1 commit into from
Dec 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:
Expand All @@ -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);
}
}

Expand All @@ -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)) {
Expand All @@ -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);
}
}

Expand All @@ -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) {
Expand All @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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");
NobiGo marked this conversation as resolved.
Show resolved Hide resolved
.columnType("BOOLEAN");

// nullability depends on nullability of both sides
sql("select empno in (1, 2) from emp")
Expand Down
51 changes: 51 additions & 0 deletions core/src/test/java/org/apache/calcite/test/TypeCoercionTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,57 @@ private static ImmutableList<RelDataType> 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)));
}

/**
Expand Down
Loading