This is an automated email from the ASF dual-hosted git repository. liyafan pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/calcite.git
commit 6d05e10924c959fb73adefd4d315a4ad94b2635f Author: Talat Uyarer <[email protected]> AuthorDate: Wed Dec 29 23:08:28 2021 -0800 [CALCITE-3627] Incorrect null semantic for ROW function --- .../calcite/adapter/enumerable/NullPolicy.java | 3 +++ .../calcite/adapter/enumerable/RexImpTable.java | 9 ++++++++- .../org/apache/calcite/sql/fun/SqlRowOperator.java | 11 ++++++++++- core/src/test/resources/sql/struct.iq | 21 +++++++++++++++++++++ .../java/org/apache/calcite/test/PigRelOpTest.java | 4 ++-- 5 files changed, 44 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/org/apache/calcite/adapter/enumerable/NullPolicy.java b/core/src/main/java/org/apache/calcite/adapter/enumerable/NullPolicy.java index 2e2e873..8a9c78b 100644 --- a/core/src/main/java/org/apache/calcite/adapter/enumerable/NullPolicy.java +++ b/core/src/main/java/org/apache/calcite/adapter/enumerable/NullPolicy.java @@ -25,6 +25,9 @@ package org.apache.calcite.adapter.enumerable; * ANY whenever possible.</p> */ public enum NullPolicy { + /** Returns null if and only if all of the arguments are null; + * If all of the arguments are false return false otherwise true. */ + ALL, /** Returns null if and only if one of the arguments are null. */ STRICT, /** Returns null if one of the arguments is null, and possibly other times. */ diff --git a/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java b/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java index 4921339..d0200a9 100644 --- a/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java +++ b/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java @@ -352,7 +352,7 @@ public class RexImpTable { @SuppressWarnings("method.invocation.invalid") RexImpTable() { defineMethod(THROW_UNLESS, BuiltInMethod.THROW_UNLESS.method, NullPolicy.NONE); - defineMethod(ROW, BuiltInMethod.ARRAY.method, NullPolicy.NONE); + defineMethod(ROW, BuiltInMethod.ARRAY.method, NullPolicy.ALL); defineMethod(UPPER, BuiltInMethod.UPPER.method, NullPolicy.STRICT); defineMethod(LOWER, BuiltInMethod.LOWER.method, NullPolicy.STRICT); defineMethod(INITCAP, BuiltInMethod.INITCAP.method, NullPolicy.STRICT); @@ -3107,6 +3107,13 @@ public class RexImpTable { if (nullPolicy == NullPolicy.ARG0) { return argIsNullList.get(0); } + + if (nullPolicy == NullPolicy.ALL) { + // Condition for NullPolicy.ALL: v0 == null && v1 == null + return Expressions.foldAnd(argIsNullList); + } + + // Condition for regular cases: v0 == null || v1 == null return Expressions.foldOr(argIsNullList); } diff --git a/core/src/main/java/org/apache/calcite/sql/fun/SqlRowOperator.java b/core/src/main/java/org/apache/calcite/sql/fun/SqlRowOperator.java index d9fff76..b73ceb4 100644 --- a/core/src/main/java/org/apache/calcite/sql/fun/SqlRowOperator.java +++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlRowOperator.java @@ -17,6 +17,7 @@ package org.apache.calcite.sql.fun; import org.apache.calcite.rel.type.RelDataType; +import org.apache.calcite.rel.type.RelDataTypeFactory; import org.apache.calcite.sql.SqlCall; import org.apache.calcite.sql.SqlKind; import org.apache.calcite.sql.SqlOperatorBinding; @@ -55,7 +56,8 @@ public class SqlRowOperator extends SqlSpecialOperator { // The type of a ROW(e1,e2) expression is a record with the types // {e1type,e2type}. According to the standard, field names are // implementation-defined. - return opBinding.getTypeFactory().createStructType( + final RelDataTypeFactory typeFactory = opBinding.getTypeFactory(); + final RelDataType recordType = typeFactory.createStructType( new AbstractList<Map.Entry<String, RelDataType>>() { @Override public Map.Entry<String, RelDataType> get(int index) { return Pair.of( @@ -67,6 +69,13 @@ public class SqlRowOperator extends SqlSpecialOperator { return opBinding.getOperandCount(); } }); + + // The value of ROW(e1,e2) is considered null if and only all of its + // fields (i.e., e1, e2) are null. Otherwise ROW can not be null. + final boolean nullable = + recordType.getFieldList().stream() + .allMatch(f -> f.getType().isNullable()); + return typeFactory.createTypeWithNullability(recordType, nullable); } @Override public void unparse( diff --git a/core/src/test/resources/sql/struct.iq b/core/src/test/resources/sql/struct.iq index eaa429e..0706980 100644 --- a/core/src/test/resources/sql/struct.iq +++ b/core/src/test/resources/sql/struct.iq @@ -127,4 +127,25 @@ WHERE empno < 7600; !ok + +# [CALCITE-3627] Null check if all fields of ROW are null +select + ROW(null, null, null) is null AS all_null_is_null, + ROW(null, null, null) is not null AS all_null_is_not_null, + ROW(null, 1, null) is null AS except_one_all_null_is_null, + ROW(null, 1, null) is not null AS except_one_all_null_is_not_null, + NOT(ROW(null, 1, null) is null) AS reverse_null_check_except_one_all_null, + ROW(null, ROW(null, null), null) is null AS all_null_including_nested_row_is_null, + ROW(null, ROW(null, 1), null) is null AS all_null_except_nested_row_is_null; ++------------------+----------------------+-----------------------------+---------------------------------+----------------------------------------+---------------------------------------+------------------------------------+ +| ALL_NULL_IS_NULL | ALL_NULL_IS_NOT_NULL | EXCEPT_ONE_ALL_NULL_IS_NULL | EXCEPT_ONE_ALL_NULL_IS_NOT_NULL | REVERSE_NULL_CHECK_EXCEPT_ONE_ALL_NULL | ALL_NULL_INCLUDING_NESTED_ROW_IS_NULL | ALL_NULL_EXCEPT_NESTED_ROW_IS_NULL | ++------------------+----------------------+-----------------------------+---------------------------------+----------------------------------------+---------------------------------------+------------------------------------+ +| true | false | false | true | true | true | false | ++------------------+----------------------+-----------------------------+---------------------------------+----------------------------------------+---------------------------------------+------------------------------------+ +(1 row) + +!ok + + + # End struct.iq diff --git a/piglet/src/test/java/org/apache/calcite/test/PigRelOpTest.java b/piglet/src/test/java/org/apache/calcite/test/PigRelOpTest.java index a457214..1af4565 100644 --- a/piglet/src/test/java/org/apache/calcite/test/PigRelOpTest.java +++ b/piglet/src/test/java/org/apache/calcite/test/PigRelOpTest.java @@ -977,7 +977,7 @@ class PigRelOpTest extends PigRelTestBase { + "({20, ANALYST},2)\n" + "({10, MANAGER},1)\n" + "({null, CLERK},4)\n" - + "({null, null},14)\n" + + "(null,14)\n" + "({20, null},5)\n" + "({10, PRESIDENT},1)\n" + "({null, ANALYST},2)\n" @@ -1028,7 +1028,7 @@ class PigRelOpTest extends PigRelTestBase { + "({20, ANALYST},2)\n" + "({10, MANAGER},1)\n" + "({null, CLERK},4)\n" - + "({null, null},14)\n" + + "(null,14)\n" + "({10, PRESIDENT},1)\n" + "({null, ANALYST},2)\n" + "({null, SALESMAN},4)\n"
