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 e2e7ce177d [CALCITE-6711] Functions whose output value can be null
should return a nullable type
e2e7ce177d is described below
commit e2e7ce177d828445cd0b34ee51388264131d02d2
Author: Xiong Duan <[email protected]>
AuthorDate: Fri Dec 6 18:55:56 2024 +0800
[CALCITE-6711] Functions whose output value can be null should return a
nullable type
---
.../calcite/sql/fun/SqlLibraryOperators.java | 31 +++++----
.../apache/calcite/sql/type/SqlTypeTransforms.java | 24 +++++++
.../calcite/sql/test/SqlOperatorFixture.java | 12 ----
.../calcite/test/SqlOperatorFixtureImpl.java | 5 --
.../org/apache/calcite/test/SqlOperatorTest.java | 75 ++++++++++------------
5 files changed, 76 insertions(+), 71 deletions(-)
diff --git
a/core/src/main/java/org/apache/calcite/sql/fun/SqlLibraryOperators.java
b/core/src/main/java/org/apache/calcite/sql/fun/SqlLibraryOperators.java
index b4f0a17d31..8403354713 100644
--- a/core/src/main/java/org/apache/calcite/sql/fun/SqlLibraryOperators.java
+++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlLibraryOperators.java
@@ -575,7 +575,7 @@ public abstract class SqlLibraryOperators {
* Returns the substring in value that matches the regexp. Returns NULL if
there is no match. */
@LibraryOperator(libraries = {BIG_QUERY})
public static final SqlBasicFunction REGEXP_EXTRACT =
- SqlBasicFunction.create("REGEXP_EXTRACT", ReturnTypes.VARCHAR_NULLABLE,
+ SqlBasicFunction.create("REGEXP_EXTRACT",
ReturnTypes.VARCHAR_FORCE_NULLABLE,
OperandTypes.STRING_STRING_OPTIONAL_INTEGER_OPTIONAL_INTEGER,
SqlFunctionCategory.STRING);
@@ -583,8 +583,8 @@ public abstract class SqlLibraryOperators {
* Returns the substring in value that matches the regexp. Returns NULL if
there is no match. */
@LibraryOperator(libraries = {BIG_QUERY})
public static final SqlBasicFunction REGEXP_EXTRACT_ALL =
- SqlBasicFunction.create("REGEXP_EXTRACT_ALL", ReturnTypes.ARG0_NULLABLE
- .andThen(SqlTypeTransforms.TO_ARRAY),
+ SqlBasicFunction.create("REGEXP_EXTRACT_ALL", ReturnTypes.ARG0
+
.andThen(SqlTypeTransforms.TO_ARRAY).andThen(SqlTypeTransforms.TO_NULLABLE),
OperandTypes.STRING_STRING,
SqlFunctionCategory.STRING);
@@ -1522,9 +1522,11 @@ public abstract class SqlLibraryOperators {
@SuppressWarnings("argument.type.incompatible")
private static RelDataType arrayInsertReturnType(SqlOperatorBinding
opBinding) {
- final RelDataType arrayType = opBinding.collectOperandTypes().get(0);
+ final List<RelDataType> operandTypes = opBinding.collectOperandTypes();
+ final RelDataType arrayType = operandTypes.get(0);
final RelDataType componentType = arrayType.getComponentType();
- final RelDataType elementType = opBinding.collectOperandTypes().get(2);
+ final RelDataType elementType1 = operandTypes.get(1);
+ final RelDataType elementType2 = operandTypes.get(2);
requireNonNull(componentType, () -> "componentType of " + arrayType);
// we don't need to do leastRestrictive on componentType and elementType,
@@ -1532,15 +1534,15 @@ public abstract class SqlLibraryOperators {
// So we use componentType directly.
RelDataType type =
opBinding.getTypeFactory().leastRestrictive(
- ImmutableList.of(componentType, elementType));
+ ImmutableList.of(componentType, elementType2));
requireNonNull(type, "inferred array element type");
- if (elementType.isNullable()) {
+ if (elementType2.isNullable()) {
type = opBinding.getTypeFactory().createTypeWithNullability(type, true);
}
// make explicit CAST for array elements and inserted element to the
biggest type
// if array component type not equals to inserted element type
- if (!componentType.equalsSansFieldNames(elementType)) {
+ if (!componentType.equalsSansFieldNames(elementType2)) {
// 0, 2 is the operand index to be CAST
// For array_insert, 0 is the array arg and 2 is the inserted element
if (componentType.equalsSansFieldNames(type)) {
@@ -1551,7 +1553,8 @@ public abstract class SqlLibraryOperators {
adjustTypeForArrayFunctions(type, opBinding, 0);
}
}
- return SqlTypeUtil.createArrayType(opBinding.getTypeFactory(), type,
arrayType.isNullable());
+ boolean nullable = arrayType.isNullable() || elementType1.isNullable();
+ return SqlTypeUtil.createArrayType(opBinding.getTypeFactory(), type,
nullable);
}
/** The "ARRAY_INSERT(array, pos, val)" function (Spark). */
@@ -2292,7 +2295,9 @@ public abstract class SqlLibraryOperators {
@LibraryOperator(libraries = {BIG_QUERY})
public static final SqlFunction CODE_POINTS_TO_BYTES =
SqlBasicFunction.create("CODE_POINTS_TO_BYTES",
- ReturnTypes.VARBINARY_NULLABLE,
+ ReturnTypes.VARBINARY
+ .andThen(SqlTypeTransforms.TO_NULLABLE)
+
.andThen(SqlTypeTransforms.TO_NULLABLE_IF_ARRAY_CONTAINS_NULLABLE),
OperandTypes.ARRAY_OF_INTEGER,
SqlFunctionCategory.STRING);
@@ -2301,7 +2306,9 @@ public abstract class SqlLibraryOperators {
@LibraryOperator(libraries = {BIG_QUERY})
public static final SqlFunction CODE_POINTS_TO_STRING =
SqlBasicFunction.create("CODE_POINTS_TO_STRING",
- ReturnTypes.VARCHAR_NULLABLE,
+ ReturnTypes.VARCHAR
+ .andThen(SqlTypeTransforms.TO_NULLABLE)
+
.andThen(SqlTypeTransforms.TO_NULLABLE_IF_ARRAY_CONTAINS_NULLABLE),
OperandTypes.ARRAY_OF_INTEGER,
SqlFunctionCategory.STRING);
@@ -2311,7 +2318,7 @@ public abstract class SqlLibraryOperators {
@LibraryOperator(libraries = {BIG_QUERY})
public static final SqlFunction TO_CODE_POINTS =
SqlBasicFunction.create("TO_CODE_POINTS",
- ReturnTypes.INTEGER.andThen(SqlTypeTransforms.TO_ARRAY_NULLABLE),
+
ReturnTypes.INTEGER.andThen(SqlTypeTransforms.TO_ARRAY_FORCE_NULLABLE),
OperandTypes.STRING.or(OperandTypes.BINARY),
SqlFunctionCategory.STRING);
diff --git
a/core/src/main/java/org/apache/calcite/sql/type/SqlTypeTransforms.java
b/core/src/main/java/org/apache/calcite/sql/type/SqlTypeTransforms.java
index d6677266ae..f1a699e9f3 100644
--- a/core/src/main/java/org/apache/calcite/sql/type/SqlTypeTransforms.java
+++ b/core/src/main/java/org/apache/calcite/sql/type/SqlTypeTransforms.java
@@ -53,6 +53,22 @@ public abstract class SqlTypeTransforms {
opBinding.collectOperandTypes(),
requireNonNull(typeToTransform, "typeToTransform"));
+ /**
+ * Parameter type-inference transform strategy where a derived type is
+ * transformed into the same type, but nullable if and only if the type
+ * is a ARRAY type and contains nullable elements.
+ */
+ public static final SqlTypeTransform TO_NULLABLE_IF_ARRAY_CONTAINS_NULLABLE =
+ (opBinding, typeToTransform) -> {
+ RelDataType relDataType = opBinding.getOperandType(0);
+ if (SqlTypeUtil.isArray(relDataType)) {
+ RelDataType componentRelDataType = relDataType.getComponentType();
+ assert componentRelDataType != null;
+ return
opBinding.getTypeFactory().createTypeWithNullability(typeToTransform,
+ componentRelDataType.isNullable());
+ }
+ return typeToTransform;
+ };
/**
* Parameter type-inference transform strategy where a derived type is
* transformed into the same type, but nullable if and only if all of a
call's
@@ -244,6 +260,14 @@ public abstract class SqlTypeTransforms {
(opBinding, typeToTransform) ->
TO_NULLABLE.transformType(opBinding,
TO_ARRAY.transformType(opBinding, typeToTransform));
+ /**
+ * Parameter type-inference transform strategy that wraps a given type in a
nullabe array.
+ */
+ public static final SqlTypeTransform TO_ARRAY_FORCE_NULLABLE =
+ (opBinding, typeToTransform) ->
+ FORCE_NULLABLE.transformType(opBinding,
+ TO_ARRAY.transformType(opBinding, typeToTransform));
+
/** Parameter type-inference transform that transforms {@code T} to
* {@code MEASURE<T>} for some type T. */
public static final SqlTypeTransform TO_MEASURE =
diff --git
a/testkit/src/main/java/org/apache/calcite/sql/test/SqlOperatorFixture.java
b/testkit/src/main/java/org/apache/calcite/sql/test/SqlOperatorFixture.java
index 3c3a825af6..94436699b2 100644
--- a/testkit/src/main/java/org/apache/calcite/sql/test/SqlOperatorFixture.java
+++ b/testkit/src/main/java/org/apache/calcite/sql/test/SqlOperatorFixture.java
@@ -344,18 +344,6 @@ public interface SqlOperatorFixture extends AutoCloseable {
*/
void checkNull(String expression);
- /**
- * Tests that a SQL expression returns the SQL NULL value and
- * the data type of the returned result is nullable. For example,
- *
- * <blockquote>
- * <pre>checkNullValueWithNullableType("CHAR_LENGTH(CAST(NULL AS
VARCHAR(3))");</pre>
- * </blockquote>
- *
- * @param expression Scalar expression
- */
- void checkNullValueWithNullableType(String expression);
-
/**
* Tests that a SQL expression has a given type. For example,
*
diff --git
a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorFixtureImpl.java
b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorFixtureImpl.java
index 8663ebc0aa..58cf5c2d10 100644
--- a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorFixtureImpl.java
+++ b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorFixtureImpl.java
@@ -285,11 +285,6 @@ class SqlOperatorFixtureImpl implements SqlOperatorFixture
{
}
@Override public void checkNull(String expression) {
- tester.forEachQuery(factory, expression, sql ->
- tester.check(factory, sql, SqlTests.ANY_TYPE_CHECKER, isNullValue()));
- }
-
- @Override public void checkNullValueWithNullableType(String expression) {
tester.forEachQuery(factory, expression, sql ->
tester.check(factory, sql, SqlTests.ANY_NULLABLE_TYPE_CHECKER,
isNullValue()));
}
diff --git a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
index 9c0cd589c1..8f1b0d3c0b 100644
--- a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
+++ b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
@@ -2305,15 +2305,15 @@ public class SqlOperatorTest {
+ "'TO_CODE_POINTS\\(<BINARY>\\)'", false);
f.checkScalar("to_code_points(_UTF8'ÿþЀ\uD804\uDD70A')", "[255, 254, 1024,
70000, 65]",
- "INTEGER NOT NULL ARRAY NOT NULL");
+ "INTEGER NOT NULL ARRAY");
f.checkScalar("to_code_points('ABCD')", "[65, 66, 67, 68]",
- "INTEGER NOT NULL ARRAY NOT NULL");
+ "INTEGER NOT NULL ARRAY");
f.checkScalar("to_code_points(x'11223344')", "[17, 34, 51, 68]",
- "INTEGER NOT NULL ARRAY NOT NULL");
+ "INTEGER NOT NULL ARRAY");
f.checkScalar("to_code_points(code_points_to_string(array[255, 254, 1024,
70000, 65]))",
- "[255, 254, 1024, 70000, 65]", "INTEGER NOT NULL ARRAY NOT NULL");
+ "[255, 254, 1024, 70000, 65]", "INTEGER NOT NULL ARRAY");
f.checkScalar("to_code_points(code_points_to_bytes(array[64, 65, 66,
67]))",
- "[64, 65, 66, 67]", "INTEGER NOT NULL ARRAY NOT NULL");
+ "[64, 65, 66, 67]", "INTEGER NOT NULL ARRAY");
f.checkNull("to_code_points(null)");
f.checkNull("to_code_points('')");
@@ -4821,8 +4821,8 @@ public class SqlOperatorTest {
+ " 'QUERY', 'k1')",
"v1",
"VARCHAR");
- f.checkNullValueWithNullableType(
- "parse_url('http://calcite.apache.org/path1/p.php?k1=v1&k2=v2#Ref1',
'QUERY', 'k3')");
+
f.checkNull("parse_url('http://calcite.apache.org/path1/p.php?k1=v1&k2=v2#Ref1',"
+ + " 'QUERY', 'k3')");
f.checkString("parse_url('http://calcite.apache.org/path1/p.php?k1=v1&k2=v2#Ref1',"
+ " 'FILE')",
"/path1/p.php?k1=v1&k2=v2",
@@ -4843,39 +4843,31 @@ public class SqlOperatorTest {
+ " 'USERINFO')",
"bob",
"VARCHAR");
- f.checkNullValueWithNullableType(
- "parse_url('http://calcite.apache.org/path1/p.php?k1=v1&k2=v2#Ref1',
'USERINFO')");
+
f.checkNull("parse_url('http://calcite.apache.org/path1/p.php?k1=v1&k2=v2#Ref1',"
+ + " 'USERINFO')");
f.checkString("parse_url('http://calcite.apache.org/path1/p.php?k1=v1&k2=v2#Ref1',"
+ " 'AUTHORITY')",
"calcite.apache.org",
"VARCHAR");
// test with invalid partToExtract
- f.checkNullValueWithNullableType(
- "parse_url('http://calcite.apache.org/path1/p.php?k1=v1&k2=v2#Ref1',"
- + " 'INVALID_PART_TO_EXTRACT')");
- f.checkNullValueWithNullableType(
- "parse_url('http://calcite.apache.org/path1/p.php?k1=v1&k2=v2#Ref1',
'HOST', 'k1')");
+
f.checkNull("parse_url('http://calcite.apache.org/path1/p.php?k1=v1&k2=v2#Ref1',"
+ + " 'INVALID_PART_TO_EXTRACT')");
+
f.checkNull("parse_url('http://calcite.apache.org/path1/p.php?k1=v1&k2=v2#Ref1',"
+ + " 'HOST', 'k1')");
// test with invalid urlString
- f.checkNullValueWithNullableType(
- "parse_url('http:calcite.apache.org/path1/p.php?k1=v1&k2=v2#Ref1',
'HOST')");
- f.checkNullValueWithNullableType(
- "parse_url('calcite.apache.org/path1/p.php?k1=v1&k2=v2#Ref1',
'HOST')");
- f.checkNullValueWithNullableType(
- "parse_url('/path1/p.php?k1=v1&k2=v2#Ref1', 'HOST')");
+
f.checkNull("parse_url('http:calcite.apache.org/path1/p.php?k1=v1&k2=v2#Ref1',
'HOST')");
+
f.checkNull("parse_url('calcite.apache.org/path1/p.php?k1=v1&k2=v2#Ref1',
'HOST')");
+ f.checkNull("parse_url('/path1/p.php?k1=v1&k2=v2#Ref1', 'HOST')");
// test with operands with null values
- f.checkNullValueWithNullableType(
- "parse_url('http://calcite.apache.org/path1/p.php?k1=v1&k2=v2#Ref1',"
+
f.checkNull("parse_url('http://calcite.apache.org/path1/p.php?k1=v1&k2=v2#Ref1',"
+ " cast(null as varchar))");
- f.checkNullValueWithNullableType(
- "parse_url('http://calcite.apache.org/path1/p.php?k1=v1&k2=v2#Ref1',"
+
f.checkNull("parse_url('http://calcite.apache.org/path1/p.php?k1=v1&k2=v2#Ref1',"
+ " cast(null as varchar), cast(null as varchar))");
- f.checkNullValueWithNullableType(
- "parse_url(cast(null as varchar), cast(null as varchar))");
- f.checkNullValueWithNullableType(
- "parse_url(cast(null as varchar), cast(null as varchar), cast(null
as varchar))");
+ f.checkNull("parse_url(cast(null as varchar), cast(null as varchar))");
+ f.checkNull("parse_url(cast(null as varchar), cast(null as varchar),
cast(null as varchar))");
};
f0.forEachLibrary(list(SqlLibrary.HIVE, SqlLibrary.SPARK), consumer);
}
@@ -5437,8 +5429,8 @@ public class SqlOperatorTest {
f.checkString("from_base64('VGhpcyB pcyBhIHRlc3Qg\tU3Ry\naW5nLg==')",
"546869732069732061207465737420537472696e672e",
"VARBINARY");
- f.checkNullValueWithNullableType("from_base64('-1')");
- f.checkNullValueWithNullableType("from_base64('-100')");
+ f.checkNull("from_base64('-1')");
+ f.checkNull("from_base64('-100')");
};
f0.forEachLibrary(list(SqlLibrary.BIG_QUERY, SqlLibrary.MYSQL), consumer);
}
@@ -6164,15 +6156,14 @@ public class SqlOperatorTest {
final SqlFunction function = functionAlias.function;
final String fn = function.getName();
final Consumer<SqlOperatorFixture> consumer = f -> {
- f.checkString(fn + "('abc def ghi', 'def')", "def", "VARCHAR NOT NULL");
- f.checkString(fn + "('abcadcaecghi', 'a.c', 1, 3)", "aec", "VARCHAR NOT
NULL");
- f.checkString(fn + "('abcadcaecghi', 'abc(a.c)')", "adc", "VARCHAR NOT
NULL");
- f.checkString(fn + "('55as56664as422', '\\d{3}')", "566", "VARCHAR NOT
NULL");
- f.checkString(fn + "('abcadcabcaecghi', 'c(a.c)', 4)", "abc", "VARCHAR
NOT NULL");
- f.checkString(fn + "('abcadcabcaecghi', 'a.c(a.c)', 1, 2)", "aec",
"VARCHAR NOT NULL");
- f.checkString(fn + "('a9cadca5c4aecghi', 'a[0-9]c', 6)", "a5c", "VARCHAR
NOT NULL");
- f.checkString(fn + "('a9cadca5ca4cecghi', 'a[0-9]c', 1, 3)", "a4c",
"VARCHAR NOT "
- + "NULL");
+ f.checkString(fn + "('abc def ghi', 'def')", "def", "VARCHAR");
+ f.checkString(fn + "('abcadcaecghi', 'a.c', 1, 3)", "aec", "VARCHAR");
+ f.checkString(fn + "('abcadcaecghi', 'abc(a.c)')", "adc", "VARCHAR");
+ f.checkString(fn + "('55as56664as422', '\\d{3}')", "566", "VARCHAR");
+ f.checkString(fn + "('abcadcabcaecghi', 'c(a.c)', 4)", "abc", "VARCHAR");
+ f.checkString(fn + "('abcadcabcaecghi', 'a.c(a.c)', 1, 2)", "aec",
"VARCHAR");
+ f.checkString(fn + "('a9cadca5c4aecghi', 'a[0-9]c', 6)", "a5c",
"VARCHAR");
+ f.checkString(fn + "('a9cadca5ca4cecghi', 'a[0-9]c', 1, 3)", "a4c",
"VARCHAR");
f.checkNull(fn + "('abc def ghi', 'asd')");
f.checkNull(fn + "('abc def ghi', 'abc', 25)");
@@ -11692,9 +11683,9 @@ public class SqlOperatorTest {
f12.checkScalar("NVL2(NULL, 3.0, 2.111)", "2.111", "DECIMAL(4, 3) NOT
NULL");
f12.checkScalar("NVL2(3.111, 3.1415926, 2.111)", "3.1415926",
"DECIMAL(8, 7) NOT NULL");
- f12.checkNullValueWithNullableType("nvl2('ab', CAST(NULL AS VARCHAR(6)),
'def')");
- f12.checkNullValueWithNullableType("nvl2(NULL, 'abc', NULL)");
- f12.checkNullValueWithNullableType("nvl2(NULL, NULL, NULL)");
+ f12.checkNull("nvl2('ab', CAST(NULL AS VARCHAR(6)), 'def')");
+ f12.checkNull("nvl2(NULL, 'abc', NULL)");
+ f12.checkNull("nvl2(NULL, NULL, NULL)");
f12.checkFails("^NVL2(2.0, 2.0, true)^", "Parameters must be of the same
type", false);
f12.checkFails("^NVL2(NULL, 2.0, true)^", "Parameters must be of the
same type", false);