This is an automated email from the ASF dual-hosted git repository. danny0405 pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/calcite.git
The following commit(s) were added to refs/heads/master by this push: new e2942fe [CALCITE-4085] Improve return type nullability for SqlDotOperator & SqlItemOperator (Dawid Wysakowicz) e2942fe is described below commit e2942feef4d52c577a46cc3f2b92fa462035bb20 Author: Dawid Wysakowicz <dwysakow...@apache.org> AuthorDate: Mon Jun 22 12:28:37 2020 +0200 [CALCITE-4085] Improve return type nullability for SqlDotOperator & SqlItemOperator (Dawid Wysakowicz) This PR introduces a logic that makes the accesed field nullable if the accessed row is nullable. Make the AliasNamespace keep the original nullability & StructKing of the aliased record. close apache/calcite#2042 --- .../org/apache/calcite/sql/fun/SqlDotOperator.java | 3 ++ .../apache/calcite/sql/fun/SqlItemOperator.java | 6 +++- .../calcite/sql/validate/AliasNamespace.java | 12 ++++++- .../apache/calcite/test/MockSqlOperatorTable.java | 41 ++++++++++++++++++++++ .../org/apache/calcite/test/SqlValidatorTest.java | 15 ++++++++ .../test/catalog/MockCatalogReaderExtended.java | 33 +++++++++++++++++ 6 files changed, 108 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/org/apache/calcite/sql/fun/SqlDotOperator.java b/core/src/main/java/org/apache/calcite/sql/fun/SqlDotOperator.java index 6855df1..7e7af24 100644 --- a/core/src/main/java/org/apache/calcite/sql/fun/SqlDotOperator.java +++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlDotOperator.java @@ -111,6 +111,9 @@ public class SqlDotOperator extends SqlSpecialOperator { Static.RESOURCE.unknownField(fieldName)); } RelDataType type = field.getType(); + if (nodeType.isNullable()) { + type = validator.getTypeFactory().createTypeWithNullability(type, true); + } // Validate and determine coercibility and resulting collation // name of binary operator if needed. diff --git a/core/src/main/java/org/apache/calcite/sql/fun/SqlItemOperator.java b/core/src/main/java/org/apache/calcite/sql/fun/SqlItemOperator.java index 8bbcdcd..cd2ffe6 100644 --- a/core/src/main/java/org/apache/calcite/sql/fun/SqlItemOperator.java +++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlItemOperator.java @@ -135,7 +135,11 @@ class SqlItemOperator extends SqlSpecialOperator { throw new AssertionError("Cannot infer type of field '" + fieldName + "' within ROW type: " + operandType); } else { - return field.getType(); + RelDataType fieldType = field.getType(); + if (operandType.isNullable()) { + fieldType = typeFactory.createTypeWithNullability(fieldType, true); + } + return fieldType; } case ANY: case DYNAMIC_STAR: diff --git a/core/src/main/java/org/apache/calcite/sql/validate/AliasNamespace.java b/core/src/main/java/org/apache/calcite/sql/validate/AliasNamespace.java index 0cc24af..4abd72e 100644 --- a/core/src/main/java/org/apache/calcite/sql/validate/AliasNamespace.java +++ b/core/src/main/java/org/apache/calcite/sql/validate/AliasNamespace.java @@ -17,6 +17,7 @@ package org.apache.calcite.sql.validate; import org.apache.calcite.rel.type.RelDataType; +import org.apache.calcite.rel.type.RelDataTypeFactoryImpl; import org.apache.calcite.rel.type.RelDataTypeField; import org.apache.calcite.sql.SqlCall; import org.apache.calcite.sql.SqlIdentifier; @@ -91,9 +92,18 @@ public class AliasNamespace extends AbstractNamespace { for (RelDataTypeField field : rowType.getFieldList()) { typeList.add(field.getType()); } - return validator.getTypeFactory().createStructType( + final RelDataType aliasedType = validator.getTypeFactory().createStructType( + rowType.getStructKind(), typeList, nameList); + + // As per suggestion in CALCITE-4085, JavaType has its special nullability handling. + if (rowType instanceof RelDataTypeFactoryImpl.JavaType) { + return aliasedType; + } else { + return validator.getTypeFactory() + .createTypeWithNullability(aliasedType, rowType.isNullable()); + } } private String getString(RelDataType rowType) { diff --git a/core/src/test/java/org/apache/calcite/test/MockSqlOperatorTable.java b/core/src/test/java/org/apache/calcite/test/MockSqlOperatorTable.java index b1ac440..0da69b8 100644 --- a/core/src/test/java/org/apache/calcite/test/MockSqlOperatorTable.java +++ b/core/src/test/java/org/apache/calcite/test/MockSqlOperatorTable.java @@ -18,6 +18,9 @@ package org.apache.calcite.test; import org.apache.calcite.rel.type.RelDataType; import org.apache.calcite.rel.type.RelDataTypeFactory; +import org.apache.calcite.rel.type.RelDataTypeFieldImpl; +import org.apache.calcite.rel.type.RelRecordType; +import org.apache.calcite.rel.type.StructKind; import org.apache.calcite.sql.SqlAggFunction; import org.apache.calcite.sql.SqlFunction; import org.apache.calcite.sql.SqlFunctionCategory; @@ -37,6 +40,8 @@ import org.apache.calcite.util.Optionality; import com.google.common.collect.ImmutableList; +import java.util.Arrays; + /** * Mock operator table for testing purposes. Contains the standard SQL operator * table, plus a list of operators. @@ -63,6 +68,7 @@ public class MockSqlOperatorTable extends ChainedSqlOperatorTable { opTab.addOperator(new DedupFunction()); opTab.addOperator(new MyFunction()); opTab.addOperator(new MyAvgAggFunction()); + opTab.addOperator(new RowFunction()); } /** "RAMP" user-defined function. */ @@ -168,4 +174,39 @@ public class MockSqlOperatorTable extends ChainedSqlOperatorTable { return false; } } + + /** "ROW_FUNC" user-defined function whose return type is + * nullable row type with non-nullable fields. */ + public static class RowFunction extends SqlFunction { + public RowFunction() { + super("ROW_FUNC", + SqlKind.OTHER_FUNCTION, + null, + null, + OperandTypes.NILADIC, + SqlFunctionCategory.USER_DEFINED_FUNCTION); + } + + public RelDataType inferReturnType(SqlOperatorBinding opBinding) { + final RelDataTypeFactory typeFactory = + opBinding.getTypeFactory(); + final RelDataType bigIntNotNull = typeFactory.createSqlType(SqlTypeName.BIGINT); + final RelDataType bigIntNullable = + typeFactory.createTypeWithNullability(bigIntNotNull, true); + return new RelRecordType( + StructKind.FULLY_QUALIFIED, + Arrays.asList( + new RelDataTypeFieldImpl( + "NOT_NULL_FIELD", + 0, + bigIntNotNull), + new RelDataTypeFieldImpl( + "NULLABLE_FIELD", + 0, + bigIntNullable) + ), + true + ); + } + } } 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 60da2b9..9a0a513 100644 --- a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java +++ b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java @@ -11491,4 +11491,19 @@ class SqlValidatorTest extends SqlValidatorTestCase { assertThat(resultType.toString(), is("INTEGER")); } + @Test void testAccessingNestedFieldsOfNullableRecord() { + sql("select ROW_COLUMN_ARRAY[0].NOT_NULL_FIELD from NULLABLEROWS.NR_T1") + .withExtendedCatalog() + .type("RecordType(BIGINT EXPR$0) NOT NULL"); + sql("select ROW_COLUMN_ARRAY[0]['NOT_NULL_FIELD'] from NULLABLEROWS.NR_T1") + .withExtendedCatalog() + .type("RecordType(BIGINT EXPR$0) NOT NULL"); + + final MockSqlOperatorTable operatorTable = + new MockSqlOperatorTable(SqlStdOperatorTable.instance()); + MockSqlOperatorTable.addRamp(operatorTable); + sql("select * FROM TABLE(ROW_FUNC()) AS T(a, b)") + .withOperatorTable(operatorTable) + .type("RecordType(BIGINT A, BIGINT B) NOT NULL"); + } } diff --git a/core/src/test/java/org/apache/calcite/test/catalog/MockCatalogReaderExtended.java b/core/src/test/java/org/apache/calcite/test/catalog/MockCatalogReaderExtended.java index a12cf3b..9246113 100644 --- a/core/src/test/java/org/apache/calcite/test/catalog/MockCatalogReaderExtended.java +++ b/core/src/test/java/org/apache/calcite/test/catalog/MockCatalogReaderExtended.java @@ -16,9 +16,14 @@ */ package org.apache.calcite.test.catalog; +import org.apache.calcite.rel.type.RelDataType; import org.apache.calcite.rel.type.RelDataTypeFactory; +import org.apache.calcite.rel.type.RelDataTypeFieldImpl; +import org.apache.calcite.rel.type.RelRecordType; +import org.apache.calcite.rel.type.StructKind; import org.apache.calcite.schema.TableMacro; import org.apache.calcite.schema.TranslatableTable; +import org.apache.calcite.sql.type.SqlTypeName; import com.google.common.collect.ImmutableList; @@ -163,6 +168,34 @@ public class MockCatalogReaderExtended extends MockCatalogReaderSimple { complexTypeColumnsTable.addColumn("rowArrayMultisetType", f.rowArrayMultisetType); registerTable(complexTypeColumnsTable); + MockSchema nullableRowsSchema = new MockSchema("NULLABLEROWS"); + registerSchema(nullableRowsSchema); + final MockTable nullableRowsTable = + MockTable.create(this, nullableRowsSchema, "NR_T1", false, 100); + RelDataType bigIntNotNull = typeFactory.createSqlType(SqlTypeName.BIGINT); + RelDataType nullableRecordType = new RelRecordType( + StructKind.FULLY_QUALIFIED, + Arrays.asList( + new RelDataTypeFieldImpl( + "NOT_NULL_FIELD", + 0, + bigIntNotNull), + new RelDataTypeFieldImpl( + "NULLABLE_FIELD", + 0, + typeFactory.createTypeWithNullability(bigIntNotNull, true) + ) + ), + true + ); + + nullableRowsTable.addColumn("ROW_COLUMN", nullableRecordType, false); + nullableRowsTable.addColumn( + "ROW_COLUMN_ARRAY", + typeFactory.createArrayType(nullableRecordType, -1), + true); + registerTable(nullableRowsTable); + return this; } }