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)));
   }
 
   /**

Reply via email to