This is an automated email from the ASF dual-hosted git repository.

xiong 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 ea1a255fd0 [CALCITE-6485] AssertionError When an IN list containing 
NULL has an implicit coercion type converter
ea1a255fd0 is described below

commit ea1a255fd071a518fe8d30e361efa0696164a037
Author: Xiong Duan <[email protected]>
AuthorDate: Sat Jul 20 08:33:31 2024 +0800

    [CALCITE-6485] AssertionError When an IN list containing NULL has an 
implicit coercion type converter
---
 .../validate/implicit/AbstractTypeCoercion.java    | 30 +++++++------
 .../calcite/test/TypeCoercionConverterTest.java    | 33 ++++++++++++++
 .../org/apache/calcite/test/TypeCoercionTest.java  | 50 ++++++++++++++++++----
 .../calcite/test/TypeCoercionConverterTest.xml     | 36 ++++++++++++++++
 4 files changed, 129 insertions(+), 20 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 7b6e7c5554..948f43a7f8 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
@@ -378,10 +378,16 @@ public abstract class AbstractTypeCoercion implements 
TypeCoercion {
     }
     // If one type is with Null type name: returns the other.
     if (SqlTypeUtil.isNull(type1)) {
-      return type2;
+      if (SqlTypeUtil.isMap(type2) || SqlTypeUtil.isRow(type2) || 
SqlTypeUtil.isArray(type2)) {
+        return type2;
+      }
+      return factory.createTypeWithNullability(type2, type1.isNullable());
     }
     if (SqlTypeUtil.isNull(type2)) {
-      return type1;
+      if (SqlTypeUtil.isMap(type1) || SqlTypeUtil.isRow(type1) || 
SqlTypeUtil.isArray(type1)) {
+        return type1;
+      }
+      return factory.createTypeWithNullability(type1, type2.isNullable());
     }
     RelDataType resultType = null;
     if (SqlTypeUtil.isString(type1)
@@ -507,28 +513,28 @@ public abstract class AbstractTypeCoercion implements 
TypeCoercion {
     // REVIEW Danny 2019-09-23: There is some legacy redundant code in 
SqlToRelConverter
     // that coerce Datetime and CHARACTER comparison.
     if (SqlTypeUtil.isCharacter(type1) && SqlTypeUtil.isDatetime(type2)) {
-      return type2;
+      return factory.createTypeWithNullability(type2, type1.isNullable());
     }
 
     if (SqlTypeUtil.isDatetime(type1) && SqlTypeUtil.isCharacter(type2)) {
-      return type1;
+      return factory.createTypeWithNullability(type1, type2.isNullable());
     }
 
     // DATE + TIMESTAMP -> TIMESTAMP
     if (SqlTypeUtil.isDate(type1) && SqlTypeUtil.isTimestamp(type2)) {
-      return type2;
+      return factory.createTypeWithNullability(type2, type1.isNullable());
     }
 
     if (SqlTypeUtil.isDate(type2) && SqlTypeUtil.isTimestamp(type1)) {
-      return type1;
+      return factory.createTypeWithNullability(type1, type2.isNullable());
     }
 
     if (SqlTypeUtil.isString(type1) && typeName2 == SqlTypeName.NULL) {
-      return type1;
+      return factory.createTypeWithNullability(type1, type2.isNullable());
     }
 
     if (typeName1 == SqlTypeName.NULL && SqlTypeUtil.isString(type2)) {
-      return type2;
+      return factory.createTypeWithNullability(type2, type1.isNullable());
     }
 
     if (SqlTypeUtil.isDecimal(type1) && SqlTypeUtil.isCharacter(type2)
@@ -555,23 +561,23 @@ public abstract class AbstractTypeCoercion implements 
TypeCoercion {
       if (SqlTypeUtil.isTimestamp(type1)) {
         return null;
       }
-      return type1;
+      return factory.createTypeWithNullability(type1, type2.isNullable());
     }
 
     if (SqlTypeUtil.isCharacter(type1) && SqlTypeUtil.isAtomic(type2)) {
       if (SqlTypeUtil.isTimestamp(type2)) {
         return null;
       }
-      return type2;
+      return factory.createTypeWithNullability(type2, type1.isNullable());
     }
 
     if (validator.config().conformance().allowLenientCoercion()) {
       if (SqlTypeUtil.isString(type1) && SqlTypeUtil.isArray(type2)) {
-        return type2;
+        return factory.createTypeWithNullability(type2, type1.isNullable());
       }
 
       if (SqlTypeUtil.isString(type2) && SqlTypeUtil.isArray(type1)) {
-        return type1;
+        return factory.createTypeWithNullability(type1, type2.isNullable());
       }
     }
 
diff --git 
a/core/src/test/java/org/apache/calcite/test/TypeCoercionConverterTest.java 
b/core/src/test/java/org/apache/calcite/test/TypeCoercionConverterTest.java
index b3514def3c..41c14ed8d9 100644
--- a/core/src/test/java/org/apache/calcite/test/TypeCoercionConverterTest.java
+++ b/core/src/test/java/org/apache/calcite/test/TypeCoercionConverterTest.java
@@ -76,6 +76,22 @@ class TypeCoercionConverterTest extends SqlToRelTestBase {
         + "from (values (true, true, true))").ok();
   }
 
+  /**
+   * Test case for
+   * <a 
href="https://issues.apache.org/jira/browse/CALCITE-6485";>[CALCITE-6485]
+   * AssertionError When an IN list containing NULL
+   * has an implicit coercion type converter</a>. */
+  @Test void testInOperationWithNull() {
+    sql("select\n"
+        + "1 in (null, '2', '3') as f0,\n"
+        + "1 in ('1', null, '3') as f1,\n"
+        + "(1, 2) in ((null, '2')) as f2,\n"
+        + "(1, 2) in (('1', null)) as f3,\n"
+        + "(1, 2) in (('1', '2'), ('1', cast(null as char))) as f4,\n"
+        + "(1, 2) in (('1', '3'), ('1', cast(null as char))) as f5\n"
+        + "from (values (null, true, null, null, true, null))").ok();
+  }
+
   @Test void testNotInOperation() {
     sql("select\n"
         + "1 not in ('1', '2', '3') as f0,\n"
@@ -84,6 +100,23 @@ class TypeCoercionConverterTest extends SqlToRelTestBase {
         + "from (values (false, false, false))").ok();
   }
 
+  /**
+   * Test case for
+   * <a 
href="https://issues.apache.org/jira/browse/CALCITE-6485";>[CALCITE-6485]
+   * AssertionError When an IN list containing NULL
+   * has an implicit coercion type converter</a>. */
+  @Test void testNotInOperationWithNull() {
+    sql("select\n"
+        + "1 not in (null, '2', '3') as f0,\n"
+        + "1 not in ('1', null, '3') as f1,\n"
+        + "(1, 2) not in ((null, '2')) as f2,\n"
+        + "(1, 2) not in (('1', null)) as f3,\n"
+        + "(1, 2) not in (('1', '2'), ('1', cast(null as char))) as f4,\n"
+        + "(1, 2) not in (('2', '3'), ('1', cast(null as char))) as f5\n"
+        + "from (values (null, false, null, null, false, null))").ok();
+  }
+
+
   /** Test cases for {@link TypeCoercion#inOperationCoercion}. */
   @Test void testInDateTimestamp() {
     sql("select (t1_timestamp, t1_date)\n"
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 616658dee7..425db3cfb6 100644
--- a/core/src/test/java/org/apache/calcite/test/TypeCoercionTest.java
+++ b/core/src/test/java/org/apache/calcite/test/TypeCoercionTest.java
@@ -105,21 +105,21 @@ class TypeCoercionTest {
     final Fixture f = fixture();
     f.checkCommonType(f.nullType, f.nullType, f.nullType, true);
     // BOOLEAN
-    f.checkCommonType(f.nullType, f.booleanType, f.booleanType, true);
+    f.checkCommonType(f.nullType, f.booleanType, f.nullableBooleanType, true);
     f.checkCommonType(f.booleanType, f.booleanType, f.booleanType, true);
     f.checkCommonType(f.intType, f.booleanType, null, true);
     f.checkCommonType(f.bigintType, f.booleanType, null, true);
     // INT
-    f.checkCommonType(f.nullType, f.tinyintType, f.tinyintType, true);
-    f.checkCommonType(f.nullType, f.intType, f.intType, true);
-    f.checkCommonType(f.nullType, f.bigintType, f.bigintType, true);
+    f.checkCommonType(f.nullType, f.tinyintType, f.nullableTinyintType, true);
+    f.checkCommonType(f.nullType, f.intType, f.nullableIntType, true);
+    f.checkCommonType(f.nullType, f.bigintType, f.nullableBigintType, true);
     f.checkCommonType(f.smallintType, f.intType, f.intType, true);
     f.checkCommonType(f.smallintType, f.bigintType, f.bigintType, true);
     f.checkCommonType(f.intType, f.bigintType, f.bigintType, true);
     f.checkCommonType(f.bigintType, f.bigintType, f.bigintType, true);
     // FLOAT/DOUBLE
-    f.checkCommonType(f.nullType, f.floatType, f.floatType, true);
-    f.checkCommonType(f.nullType, f.doubleType, f.doubleType, true);
+    f.checkCommonType(f.nullType, f.floatType, f.nullableFloatType, true);
+    f.checkCommonType(f.nullType, f.doubleType, f.nullableDoubleType, true);
     // Use RelDataTypeFactory#leastRestrictive to find the common type; it's 
not
     // symmetric but it's ok because precision does not become lower.
     f.checkCommonType(f.floatType, f.doubleType, f.floatType, false);
@@ -139,12 +139,12 @@ class TypeCoercionTest {
     f.checkCommonType(decimal54, f.doubleType, null, true);
     f.checkCommonType(decimal54, f.intType, null, true);
     // CHAR/VARCHAR
-    f.checkCommonType(f.nullType, f.charType, f.charType, true);
+    f.checkCommonType(f.nullType, f.charType, f.nullableCharType, true);
     f.checkCommonType(f.charType, f.varcharType, f.varcharType, true);
     f.checkCommonType(f.intType, f.charType, null, true);
     f.checkCommonType(f.doubleType, f.charType, null, true);
     // TIMESTAMP
-    f.checkCommonType(f.nullType, f.timestampType, f.timestampType, true);
+    f.checkCommonType(f.nullType, f.timestampType, f.nullableTimestampType, 
true);
     f.checkCommonType(f.timestampType, f.timestampType, f.timestampType, true);
     f.checkCommonType(f.dateType, f.timestampType, f.timestampType, true);
     f.checkCommonType(f.intType, f.timestampType, null, true);
@@ -638,22 +638,39 @@ class TypeCoercionTest {
     // single types
     final RelDataType nullType;
     final RelDataType booleanType;
+    final RelDataType nullableBooleanType;
     final RelDataType tinyintType;
+    final RelDataType nullableTinyintType;
     final RelDataType smallintType;
+    final RelDataType nullableSmallintType;
     final RelDataType intType;
+    final RelDataType nullableIntType;
     final RelDataType bigintType;
+    final RelDataType nullableBigintType;
     final RelDataType floatType;
+    final RelDataType nullableFloatType;
     final RelDataType doubleType;
+    final RelDataType nullableDoubleType;
     final RelDataType decimalType;
+    final RelDataType nullableDecimalType;
     final RelDataType dateType;
+    final RelDataType nullableDateType;
     final RelDataType timeType;
+    final RelDataType nullableTimeType;
     final RelDataType timestampType;
+    final RelDataType nullableTimestampType;
     final RelDataType binaryType;
+    final RelDataType nullableBinaryType;
     final RelDataType varbinaryType;
+    final RelDataType nullableVarbinaryType;
     final RelDataType charType;
+    final RelDataType nullableCharType;
     final RelDataType varcharType;
+    final RelDataType nullableVarcharType;
     final RelDataType varchar20Type;
+    final RelDataType nullableVarchar20Type;
     final RelDataType geometryType;
+    final RelDataType nullableGeometryType;
 
     /** Creates a Fixture. */
     public static Fixture create(SqlTestFactory testFactory) {
@@ -669,22 +686,39 @@ class TypeCoercionTest {
       // Initialize single types
       nullType = this.typeFactory.createSqlType(SqlTypeName.NULL);
       booleanType = this.typeFactory.createSqlType(SqlTypeName.BOOLEAN);
+      nullableBooleanType = 
this.typeFactory.createTypeWithNullability(booleanType, true);
       tinyintType = this.typeFactory.createSqlType(SqlTypeName.TINYINT);
+      nullableTinyintType = 
this.typeFactory.createTypeWithNullability(tinyintType, true);
       smallintType = this.typeFactory.createSqlType(SqlTypeName.SMALLINT);
+      nullableSmallintType = 
this.typeFactory.createTypeWithNullability(smallintType, true);
       intType = this.typeFactory.createSqlType(SqlTypeName.INTEGER);
+      nullableIntType = this.typeFactory.createTypeWithNullability(intType, 
true);
       bigintType = this.typeFactory.createSqlType(SqlTypeName.BIGINT);
+      nullableBigintType = 
this.typeFactory.createTypeWithNullability(bigintType, true);
       floatType = this.typeFactory.createSqlType(SqlTypeName.FLOAT);
+      nullableFloatType = 
this.typeFactory.createTypeWithNullability(floatType, true);
       doubleType = this.typeFactory.createSqlType(SqlTypeName.DOUBLE);
+      nullableDoubleType = 
this.typeFactory.createTypeWithNullability(doubleType, true);
       decimalType = this.typeFactory.createSqlType(SqlTypeName.DECIMAL);
+      nullableDecimalType = 
this.typeFactory.createTypeWithNullability(decimalType, true);
       dateType = this.typeFactory.createSqlType(SqlTypeName.DATE);
+      nullableDateType = this.typeFactory.createTypeWithNullability(dateType, 
true);
       timeType = this.typeFactory.createSqlType(SqlTypeName.TIME);
+      nullableTimeType = this.typeFactory.createTypeWithNullability(timeType, 
true);
       timestampType = this.typeFactory.createSqlType(SqlTypeName.TIMESTAMP);
+      nullableTimestampType = 
this.typeFactory.createTypeWithNullability(timestampType, true);
       binaryType = this.typeFactory.createSqlType(SqlTypeName.BINARY);
+      nullableBinaryType = 
this.typeFactory.createTypeWithNullability(binaryType, true);
       varbinaryType = this.typeFactory.createSqlType(SqlTypeName.VARBINARY);
+      nullableVarbinaryType = 
this.typeFactory.createTypeWithNullability(varbinaryType, true);
       charType = this.typeFactory.createSqlType(SqlTypeName.CHAR);
+      nullableCharType = this.typeFactory.createTypeWithNullability(charType, 
true);
       varcharType = this.typeFactory.createSqlType(SqlTypeName.VARCHAR);
+      nullableVarcharType = 
this.typeFactory.createTypeWithNullability(varcharType, true);
       varchar20Type = this.typeFactory.createSqlType(SqlTypeName.VARCHAR, 20);
+      nullableVarchar20Type = 
this.typeFactory.createTypeWithNullability(varchar20Type, true);
       geometryType = this.typeFactory.createSqlType(SqlTypeName.GEOMETRY);
+      nullableGeometryType = 
this.typeFactory.createTypeWithNullability(geometryType, true);
 
       // Initialize category types
 
diff --git 
a/core/src/test/resources/org/apache/calcite/test/TypeCoercionConverterTest.xml 
b/core/src/test/resources/org/apache/calcite/test/TypeCoercionConverterTest.xml
index 3570360cfb..2d945b6da3 100644
--- 
a/core/src/test/resources/org/apache/calcite/test/TypeCoercionConverterTest.xml
+++ 
b/core/src/test/resources/org/apache/calcite/test/TypeCoercionConverterTest.xml
@@ -113,6 +113,24 @@ from (values (true, true, true))]]>
     <Resource name="plan">
       <![CDATA[
 LogicalValues(tuples=[[{ true, true, true }]])
+]]>
+    </Resource>
+  </TestCase>
+  <TestCase name="testInOperationWithNull">
+    <Resource name="sql">
+      <![CDATA[select
+1 in (null, '2', '3') as f0,
+1 in ('1', null, '3') as f1,
+(1, 2) in ((null, '2')) as f2,
+(1, 2) in (('1', null)) as f3,
+(1, 2) in (('1', '2'), ('1', cast(null as char))) as f4,
+(1, 2) in (('1', '3'), ('1', cast(null as char))) as f5
+from (values (null, true, null, null, true, null))]]>
+    </Resource>
+    <Resource name="plan">
+      <![CDATA[
+LogicalProject(F0=[null:BOOLEAN], F1=[CAST(true):BOOLEAN], F2=[null:BOOLEAN], 
F3=[null:BOOLEAN], F4=[CAST(true):BOOLEAN], F5=[null:BOOLEAN])
+  LogicalValues(tuples=[[{ null, true, null, null, true, null }]])
 ]]>
     </Resource>
   </TestCase>
@@ -170,6 +188,24 @@ from (values (false, false, false))]]>
     <Resource name="plan">
       <![CDATA[
 LogicalValues(tuples=[[{ false, false, false }]])
+]]>
+    </Resource>
+  </TestCase>
+  <TestCase name="testNotInOperationWithNull">
+    <Resource name="sql">
+      <![CDATA[select
+1 not in (null, '2', '3') as f0,
+1 not in ('1', null, '3') as f1,
+(1, 2) not in ((null, '2')) as f2,
+(1, 2) not in (('1', null)) as f3,
+(1, 2) not in (('1', '2'), ('1', cast(null as char))) as f4,
+(1, 2) not in (('2', '3'), ('1', cast(null as char))) as f5
+from (values (null, false, null, null, false, null))]]>
+    </Resource>
+    <Resource name="plan">
+      <![CDATA[
+LogicalProject(F0=[null:BOOLEAN], F1=[CAST(false):BOOLEAN], F2=[null:BOOLEAN], 
F3=[null:BOOLEAN], F4=[CAST(false):BOOLEAN], F5=[null:BOOLEAN])
+  LogicalValues(tuples=[[{ null, false, null, null, false, null }]])
 ]]>
     </Resource>
   </TestCase>

Reply via email to