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 c456f7888b [CALCITE-6813] UNNEST infers incorrect nullability for the 
result when applied to an array that contains nullable ROW values
c456f7888b is described below

commit c456f7888b6aff3c3de6eda4fc16405b0b98be30
Author: Mihai Budiu <[email protected]>
AuthorDate: Wed Feb 5 22:58:51 2025 -0800

    [CALCITE-6813] UNNEST infers incorrect nullability for the result when 
applied to an array that contains nullable ROW values
    
    Signed-off-by: Mihai Budiu <[email protected]>
---
 .../org/apache/calcite/rel/core/Uncollect.java     | 17 ++--------
 .../org/apache/calcite/sql/SqlUnnestOperator.java  | 18 +++--------
 .../org/apache/calcite/test/SqlValidatorTest.java  | 37 ++++++++++++++++++++++
 3 files changed, 44 insertions(+), 28 deletions(-)

diff --git a/core/src/main/java/org/apache/calcite/rel/core/Uncollect.java 
b/core/src/main/java/org/apache/calcite/rel/core/Uncollect.java
index 7a6040d942..d919884252 100644
--- a/core/src/main/java/org/apache/calcite/rel/core/Uncollect.java
+++ b/core/src/main/java/org/apache/calcite/rel/core/Uncollect.java
@@ -166,34 +166,23 @@ public static RelDataType deriveUncollectRowType(RelNode 
rel,
 
     for (int i = 0; i < fields.size(); i++) {
       RelDataTypeField field = fields.get(i);
-      boolean containerIsNullable = field.getType().isNullable();
       if (field.getType() instanceof MapSqlType) {
         // This code is similar to SqlUnnestOperator::inferReturnType.
         MapSqlType mapType = (MapSqlType) field.getType();
-        RelDataType keyType = mapType.getKeyType();
-        RelDataType valueType = mapType.getValueType();
-        if (containerIsNullable) {
-          keyType = typeFactory.enforceTypeWithNullability(keyType, true);
-          valueType = typeFactory.enforceTypeWithNullability(valueType, true);
-        }
-        builder.add(SqlUnnestOperator.MAP_KEY_COLUMN_NAME, keyType);
-        builder.add(SqlUnnestOperator.MAP_VALUE_COLUMN_NAME, valueType);
+        builder.add(SqlUnnestOperator.MAP_KEY_COLUMN_NAME, 
mapType.getKeyType());
+        builder.add(SqlUnnestOperator.MAP_VALUE_COLUMN_NAME, 
mapType.getValueType());
       } else {
         RelDataType componentType = field.getType().getComponentType();
         if (null == componentType) {
           throw RESOURCE.unnestArgument().ex();
         }
         boolean isNullable = componentType.isNullable();
-        if (containerIsNullable || isNullable) {
-          componentType = 
typeFactory.enforceTypeWithNullability(componentType, true);
-        }
-
         if (requireAlias) {
           builder.add(itemAliases.get(i), componentType);
         } else if (componentType.isStruct()) {
           for (RelDataTypeField fieldInfo : componentType.getFieldList()) {
             RelDataType fieldType = fieldInfo.getType();
-            if (containerIsNullable || isNullable) {
+            if (isNullable) {
               fieldType = typeFactory.enforceTypeWithNullability(fieldType, 
true);
             }
             builder.add(fieldInfo.getName(), fieldType);
diff --git a/core/src/main/java/org/apache/calcite/sql/SqlUnnestOperator.java 
b/core/src/main/java/org/apache/calcite/sql/SqlUnnestOperator.java
index 887bf1e6d8..182ad78374 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlUnnestOperator.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlUnnestOperator.java
@@ -68,7 +68,7 @@ public SqlUnnestOperator(boolean withOrdinality) {
       RelDataType type = opBinding.getOperandType(operand);
       if (type.getSqlTypeName() == SqlTypeName.ANY) {
         // Unnest Operator in schema less systems returns one column as the 
output
-        // $unnest is a place holder to specify that one column with type ANY 
is output.
+        // $unnest is a placeholder to specify that one column with type ANY 
is output.
         return builder
             .add("$unnest",
                 SqlTypeName.ANY)
@@ -83,32 +83,22 @@ public SqlUnnestOperator(boolean withOrdinality) {
       assert type instanceof ArraySqlType || type instanceof MultisetSqlType
           || type instanceof MapSqlType;
       // If a type is nullable, all field accesses inside the type are also 
nullable
-      boolean containerIsNullable = type.isNullable();
       if (type instanceof MapSqlType) {
         MapSqlType mapType = (MapSqlType) type;
-        RelDataType keyType = mapType.getKeyType();
-        RelDataType valueType = mapType.getValueType();
-        if (containerIsNullable) {
-          keyType = typeFactory.enforceTypeWithNullability(keyType, true);
-          valueType = typeFactory.enforceTypeWithNullability(valueType, true);
-        }
-        builder.add(MAP_KEY_COLUMN_NAME, keyType);
-        builder.add(MAP_VALUE_COLUMN_NAME, valueType);
+        builder.add(MAP_KEY_COLUMN_NAME, mapType.getKeyType());
+        builder.add(MAP_VALUE_COLUMN_NAME, mapType.getValueType());
       } else {
         RelDataType componentType = requireNonNull(type.getComponentType(), 
"componentType");
         boolean isNullable = componentType.isNullable();
         if (!allowAliasUnnestItems(opBinding) && componentType.isStruct()) {
           for (RelDataTypeField field : componentType.getFieldList()) {
             RelDataType fieldType = field.getType();
-            if (containerIsNullable || isNullable) {
+            if (isNullable) {
               fieldType = typeFactory.enforceTypeWithNullability(fieldType, 
true);
             }
             builder.add(field.getName(), fieldType);
           }
         } else {
-          if (containerIsNullable) {
-            componentType = 
typeFactory.enforceTypeWithNullability(componentType, true);
-          }
           builder.add(SqlUtil.deriveAliasFromOrdinal(operand),
               componentType);
         }
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 0f0a9f9a84..f4b632993a 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
@@ -1066,8 +1066,45 @@ void testDyadicCollateOperator() {
         + "select e.EXPR$0\n"
         + "from orders, UNNEST(orders.data) as e")
         .type(actualType -> {
+          // Unfortunately the string representation does not contain 
nullability information
+          assertThat(actualType, hasToString("RecordType(INTEGER EXPR$0)"));
+          assertTrue(actualType.isStruct());
+          assertThat(actualType.getFieldCount(), is(1));
+          // The field type should be nullable
           assertTrue(actualType.getFieldList().get(0).getType().isNullable());
         });
+    sql("with orders(data) as\n"
+        + "  (values (ARRAY[ROW(1, 'Alice'), ROW(2, NULL), ROW(NULL, 'Bob'), 
NULL]))\n"
+        + "select e.*\n"
+        + "from orders, UNNEST(orders.data) as e")
+        .type(actualType -> {
+          assertThat(actualType, hasToString("RecordType(INTEGER EXPR$0, 
CHAR(5) EXPR$1)"));
+          assertTrue(actualType.isStruct());
+          assertTrue(actualType.getFieldList().get(0).getType().isNullable());
+          assertTrue(actualType.getFieldList().get(1).getType().isNullable());
+        });
+    sql("with orders(data) as\n"
+        + "  (values (ARRAY[ROW(1, 'Alice'), ROW(2, NULL)]))\n"
+        + "select e.*\n"
+        + "from orders, UNNEST(orders.data) as e")
+        .type(actualType -> {
+          // The array unnested is not nullable
+          assertThat(actualType, hasToString("RecordType(INTEGER EXPR$0, 
CHAR(5) EXPR$1)"));
+          assertTrue(actualType.isStruct());
+          assertThat(actualType.getFieldList().get(0).getType().isNullable(), 
is(false));
+          assertTrue(actualType.getFieldList().get(1).getType().isNullable());
+        });
+    sql("with orders(data) as\n"
+        + "  (values (ARRAY[ARRAY[ROW(1, 'Alice'), ROW(2, NULL)], NULL]))\n"
+        + "select e.*\n"
+        + "from orders, UNNEST(orders.data[1]) as e")
+        .type(actualType -> {
+          // The inner array that is unnested is nullable in this example
+          assertThat(actualType, hasToString("RecordType(INTEGER EXPR$0, 
CHAR(5) EXPR$1)"));
+          assertTrue(actualType.isStruct());
+          assertThat(actualType.getFieldList().get(0).getType().isNullable(), 
is(false));
+          assertTrue(actualType.getFieldList().get(1).getType().isNullable());
+        });
   }
 
   @Test void testOverlay() {

Reply via email to