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 95089c86fa [CALCITE-5744] Add MAP_FROM_ARRAYS, STR_TO_MAP function (enabled in Spark library) 95089c86fa is described below commit 95089c86fafc504a34edda85915433adac419443 Author: yongen.ly <yongen...@alibaba-inc.com> AuthorDate: Thu Jun 1 19:13:38 2023 +0800 [CALCITE-5744] Add MAP_FROM_ARRAYS, STR_TO_MAP function (enabled in Spark library) --- .../calcite/adapter/enumerable/RexImpTable.java | 37 +++++++++++ .../apache/calcite/runtime/CalciteResource.java | 3 + .../org/apache/calcite/runtime/SqlFunctions.java | 28 ++++++++ .../main/java/org/apache/calcite/sql/SqlKind.java | 6 ++ .../calcite/sql/fun/SqlLibraryOperators.java | 25 +++++++ .../org/apache/calcite/sql/type/OperandTypes.java | 10 +++ .../org/apache/calcite/sql/type/ReturnTypes.java | 16 +++++ .../apache/calcite/sql/type/SqlTypeTransforms.java | 11 ++++ .../org/apache/calcite/util/BuiltInMethod.java | 2 + .../calcite/runtime/CalciteResource.properties | 1 + site/_docs/reference.md | 2 + .../org/apache/calcite/test/SqlOperatorTest.java | 77 ++++++++++++++++++++++ 12 files changed, 218 insertions(+) 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 f5a27adb3f..f1585e4e30 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 @@ -189,6 +189,7 @@ import static org.apache.calcite.sql.fun.SqlLibraryOperators.LOGICAL_AND; import static org.apache.calcite.sql.fun.SqlLibraryOperators.LOGICAL_OR; import static org.apache.calcite.sql.fun.SqlLibraryOperators.LPAD; import static org.apache.calcite.sql.fun.SqlLibraryOperators.MAP_ENTRIES; +import static org.apache.calcite.sql.fun.SqlLibraryOperators.MAP_FROM_ARRAYS; import static org.apache.calcite.sql.fun.SqlLibraryOperators.MAP_KEYS; import static org.apache.calcite.sql.fun.SqlLibraryOperators.MAP_VALUES; import static org.apache.calcite.sql.fun.SqlLibraryOperators.MAX_BY; @@ -223,6 +224,7 @@ import static org.apache.calcite.sql.fun.SqlLibraryOperators.SPACE; import static org.apache.calcite.sql.fun.SqlLibraryOperators.SPLIT; import static org.apache.calcite.sql.fun.SqlLibraryOperators.STARTS_WITH; import static org.apache.calcite.sql.fun.SqlLibraryOperators.STRCMP; +import static org.apache.calcite.sql.fun.SqlLibraryOperators.STR_TO_MAP; import static org.apache.calcite.sql.fun.SqlLibraryOperators.TANH; import static org.apache.calcite.sql.fun.SqlLibraryOperators.TIME; import static org.apache.calcite.sql.fun.SqlLibraryOperators.TIMESTAMP; @@ -436,6 +438,8 @@ public class RexImpTable { Expressions.constant(true); public static final ConstantExpression COMMA_EXPR = Expressions.constant(","); + public static final ConstantExpression COLON_EXPR = + Expressions.constant(":"); public static final MemberExpression BOXED_FALSE_EXPR = Expressions.field(null, Boolean.class, "FALSE"); public static final MemberExpression BOXED_TRUE_EXPR = @@ -736,6 +740,8 @@ public class RexImpTable { defineMethod(MAP_ENTRIES, BuiltInMethod.MAP_ENTRIES.method, NullPolicy.STRICT); defineMethod(MAP_KEYS, BuiltInMethod.MAP_KEYS.method, NullPolicy.STRICT); defineMethod(MAP_VALUES, BuiltInMethod.MAP_VALUES.method, NullPolicy.STRICT); + defineMethod(MAP_FROM_ARRAYS, BuiltInMethod.MAP_FROM_ARRAYS.method, NullPolicy.ANY); + map.put(STR_TO_MAP, new StringToMapImplementor()); map.put(ARRAY_CONCAT, new ArrayConcatImplementor()); map.put(SORT_ARRAY, new SortArrayImplementor()); final MethodImplementor isEmptyImplementor = @@ -3161,6 +3167,37 @@ public class RexImpTable { } } + /** Implementor for str_to_map. */ + private static class StringToMapImplementor extends AbstractRexCallImplementor { + StringToMapImplementor() { + super("str_to_map", NullPolicy.STRICT, false); + } + + @Override Expression implementSafe(RexToLixTranslator translator, + RexCall call, List<Expression> argValueList) { + switch (call.getOperands().size()) { + case 1: + return Expressions.call( + BuiltInMethod.STR_TO_MAP.method, + argValueList.get(0), + COMMA_EXPR, + COLON_EXPR); + case 2: + return Expressions.call( + BuiltInMethod.STR_TO_MAP.method, + argValueList.get(0), + argValueList.get(1), + COLON_EXPR); + case 3: + return Expressions.call( + BuiltInMethod.STR_TO_MAP.method, + argValueList); + default: + throw new AssertionError(); + } + } + } + /** Implementor for a array or string concat. */ private static class ConcatImplementor extends AbstractRexCallImplementor { final ArrayConcatImplementor arrayConcatImplementor = diff --git a/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java b/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java index fc8fd6fb1e..27024331bc 100644 --- a/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java +++ b/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java @@ -900,6 +900,9 @@ public interface CalciteResource { @BaseMessage("Substring error: negative substring length not allowed") ExInst<CalciteException> illegalNegativeSubstringLength(); + @BaseMessage("Illegal arguments: The length of the keys array {0,number,#} is not equal to the length of the values array {1,number,#} in MAP_FROM_ARRAYS function") + ExInst<CalciteException> illegalArgumentsInMapFromArraysFunc(int arg0, int arg1); + @BaseMessage("Trim error: trim character must be exactly 1 character") ExInst<CalciteException> trimError(); diff --git a/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java b/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java index 26990d03f6..0638481ae2 100644 --- a/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java +++ b/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java @@ -92,6 +92,7 @@ import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; @@ -4058,6 +4059,33 @@ public class SqlFunctions { return new ArrayList<>(map.values()); } + /** Support the MAP_FROM_ARRAYS function. */ + public static Map mapFromArrays(List keysArray, List valuesArray) { + if (keysArray.size() != valuesArray.size()) { + throw RESOURCE.illegalArgumentsInMapFromArraysFunc(keysArray.size(), valuesArray.size()).ex(); + } + final Map map = new LinkedHashMap<>(); + for (int i = 0; i < keysArray.size(); i++) { + map.put(keysArray.get(i), valuesArray.get(i)); + } + return map; + } + + /** Support the STR_TO_MAP function. */ + public static Map strToMap(String string, String stringDelimiter, String keyValueDelimiter) { + final Map map = new LinkedHashMap(); + final String[] keyValues = string.split(stringDelimiter, -1); + for (String s : keyValues) { + String[] keyValueArray = s.split(keyValueDelimiter, 2); + String key = keyValueArray[0]; + String value = keyValueArray.length < 2 + ? null + : keyValueArray[1]; + map.put(key, value); + } + return map; + } + /** Support the SLICE function. */ public static List slice(List list) { List result = new ArrayList(list.size()); diff --git a/core/src/main/java/org/apache/calcite/sql/SqlKind.java b/core/src/main/java/org/apache/calcite/sql/SqlKind.java index a49423d684..ebeeb7f839 100644 --- a/core/src/main/java/org/apache/calcite/sql/SqlKind.java +++ b/core/src/main/java/org/apache/calcite/sql/SqlKind.java @@ -752,6 +752,12 @@ public enum SqlKind { /** {@code MAP_VALUES} function (Spark semantics). */ MAP_VALUES, + /** {@code MAP_FROM_ARRAYS} function (Spark semantics). */ + MAP_FROM_ARRAYS, + + /** {@code STR_TO_MAP} function (Spark semantics). */ + STR_TO_MAP, + /** {@code REVERSE} function (SQL Server, MySQL). */ REVERSE, 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 4ceffd43e7..d42beb1ec5 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 @@ -1146,6 +1146,31 @@ public abstract class SqlLibraryOperators { ReturnTypes.TO_MAP_VALUES_NULLABLE, OperandTypes.MAP); + private static RelDataType deriveTypeMapFromArrays(SqlOperatorBinding opBinding) { + final RelDataType keysArrayType = opBinding.getOperandType(0); + final RelDataType valuesArrayType = opBinding.getOperandType(1); + final boolean nullable = keysArrayType.isNullable() || valuesArrayType.isNullable(); + return SqlTypeUtil.createMapType( + opBinding.getTypeFactory(), + requireNonNull(keysArrayType.getComponentType(), "inferred key type"), + requireNonNull(valuesArrayType.getComponentType(), "inferred value type"), + nullable); + } + + /** The "MAP_FROM_ARRAYS(keysArray, valuesArray)" function. */ + @LibraryOperator(libraries = {SPARK}) + public static final SqlFunction MAP_FROM_ARRAYS = + SqlBasicFunction.create(SqlKind.MAP_FROM_ARRAYS, + SqlLibraryOperators::deriveTypeMapFromArrays, + OperandTypes.ARRAY_ARRAY); + + /** The "STR_TO_MAP(string[, stringDelimiter[, keyValueDelimiter]])" function. */ + @LibraryOperator(libraries = {SPARK}) + public static final SqlFunction STR_TO_MAP = + SqlBasicFunction.create(SqlKind.STR_TO_MAP, + ReturnTypes.IDENTITY_TO_MAP_NULLABLE, + OperandTypes.STRING_OPTIONAL_STRING_OPTIONAL_STRING); + @LibraryOperator(libraries = {BIG_QUERY, MYSQL}) public static final SqlFunction REVERSE = SqlBasicFunction.create(SqlKind.REVERSE, diff --git a/core/src/main/java/org/apache/calcite/sql/type/OperandTypes.java b/core/src/main/java/org/apache/calcite/sql/type/OperandTypes.java index 6e85fa8c0c..37f0f8325d 100644 --- a/core/src/main/java/org/apache/calcite/sql/type/OperandTypes.java +++ b/core/src/main/java/org/apache/calcite/sql/type/OperandTypes.java @@ -404,6 +404,13 @@ public abstract class OperandTypes { // Third operand optional (operand index 0, 1, 2) number -> number == 2); + public static final FamilyOperandTypeChecker STRING_OPTIONAL_STRING_OPTIONAL_STRING = + family( + ImmutableList.of(SqlTypeFamily.STRING, SqlTypeFamily.STRING, + SqlTypeFamily.STRING), + // Second and Third operand both are optional (operand index 0, 1, 2) + number -> number == 1 || number == 2); + public static final FamilyOperandTypeChecker STRING_NUMERIC_OPTIONAL_STRING = family( ImmutableList.of(SqlTypeFamily.STRING, SqlTypeFamily.NUMERIC, @@ -469,6 +476,9 @@ public abstract class OperandTypes { public static final SqlSingleOperandTypeChecker ARRAY = family(SqlTypeFamily.ARRAY); + public static final SqlSingleOperandTypeChecker ARRAY_ARRAY = + family(SqlTypeFamily.ARRAY, SqlTypeFamily.ARRAY); + public static final SqlSingleOperandTypeChecker ARRAY_OR_MAP = OperandTypes.family(SqlTypeFamily.ARRAY) .or(OperandTypes.family(SqlTypeFamily.MAP)) diff --git a/core/src/main/java/org/apache/calcite/sql/type/ReturnTypes.java b/core/src/main/java/org/apache/calcite/sql/type/ReturnTypes.java index 901d3dc395..5b9d7aa73c 100644 --- a/core/src/main/java/org/apache/calcite/sql/type/ReturnTypes.java +++ b/core/src/main/java/org/apache/calcite/sql/type/ReturnTypes.java @@ -628,6 +628,22 @@ public abstract class ReturnTypes { public static final SqlReturnTypeInference TO_MAP = ARG0.andThen(SqlTypeTransforms.TO_MAP); + /** + * Returns a MAP type. + * + * <p>For example, given {@code STRING}, returns + * {@code (STRING, STRING) MAP}. + */ + public static final SqlReturnTypeInference IDENTITY_TO_MAP = + ARG0.andThen(SqlTypeTransforms.IDENTITY_TO_MAP); + + /** + * Same as {@link #IDENTITY_TO_MAP} but returns with nullability if any of the + * operands is nullable. + */ + public static final SqlReturnTypeInference IDENTITY_TO_MAP_NULLABLE = + IDENTITY_TO_MAP.andThen(SqlTypeTransforms.TO_NULLABLE); + /** * Returns a ROW type. * 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 97562713c9..b9c6f80aa1 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 @@ -244,6 +244,17 @@ public abstract class SqlTypeTransforms { SqlTypeUtil.createMapTypeFromRecord(opBinding.getTypeFactory(), typeToTransform); + /** + * Parameter type-inference transform strategy that converts a type to a MAP type, + * which key and value type is same. + * + * @see org.apache.calcite.rel.type.RelDataTypeFactory#createMapType + */ + public static final SqlTypeTransform IDENTITY_TO_MAP = + (opBinding, typeToTransform) -> + SqlTypeUtil.createMapType(opBinding.getTypeFactory(), + typeToTransform, typeToTransform, false); + /** * Parameter type-inference transform strategy that converts a MAP type * to a two-field record type. diff --git a/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java b/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java index 6dda17513f..64073b3977 100644 --- a/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java +++ b/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java @@ -655,6 +655,8 @@ public enum BuiltInMethod { MAP_ENTRIES(SqlFunctions.class, "mapEntries", Map.class), MAP_KEYS(SqlFunctions.class, "mapKeys", Map.class), MAP_VALUES(SqlFunctions.class, "mapValues", Map.class), + MAP_FROM_ARRAYS(SqlFunctions.class, "mapFromArrays", List.class, List.class), + STR_TO_MAP(SqlFunctions.class, "strToMap", String.class, String.class, String.class), SELECTIVITY(Selectivity.class, "getSelectivity", RexNode.class), UNIQUE_KEYS(UniqueKeys.class, "getUniqueKeys", boolean.class), AVERAGE_ROW_SIZE(Size.class, "averageRowSize"), diff --git a/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties b/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties index c402732059..4b18925e69 100644 --- a/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties +++ b/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties @@ -294,6 +294,7 @@ DialectDoesNotSupportFeature=Dialect does not support feature: ''{0}'' IllegalNegativePadLength=Second argument for LPAD/RPAD must not be negative IllegalEmptyPadPattern=Third argument (pad pattern) for LPAD/RPAD must not be empty IllegalNegativeSubstringLength=Substring error: negative substring length not allowed +IllegalArgumentsInMapFromArraysFunc=Illegal arguments: The length of the keys array {0,number,#} is not equal to the length of the values array {1,number,#} in MAP_FROM_ARRAYS function TrimError=Trim error: trim character must be exactly 1 character InvalidTypesForArithmetic=Invalid types for arithmetic: {0} {1} {2} InvalidTypesForComparison=Invalid types for comparison: {0} {1} {2} diff --git a/site/_docs/reference.md b/site/_docs/reference.md index a701fe9eda..c45baae259 100644 --- a/site/_docs/reference.md +++ b/site/_docs/reference.md @@ -2751,6 +2751,8 @@ BigQuery's type system uses confusingly different names for types and functions: | s | MAP_ENTRIES(map) | Returns the entries of the *map* as an array, the order of the entries is not defined | s | MAP_KEYS(map) | Returns the keys of the *map* as an array, the order of the entries is not defined | s | MAP_VALUES(map) | Returns the values of the *map* as an array, the order of the entries is not defined +| s | MAP_FROM_ARRAYS(array1, array2) | Returns a map created from an *array1* and *array2*. Note that the lengths of two arrays should be the same +| s | STR_TO_MAP(string [, stringDelimiter [, keyValueDelimiter]]) | Returns a map after splitting the *string* into key/value pairs using delimiters. Default delimiters are ',' for *stringDelimiter* and ':' for *keyValueDelimiter* | b m p | MD5(string) | Calculates an MD5 128-bit checksum of *string* and returns it as a hex string | m | MONTHNAME(date) | Returns the name, in the connection's locale, of the month in *datetime*; for example, it returns '二月' for both DATE '2020-02-10' and TIMESTAMP '2020-02-10 10:10:10' | o | NVL(value1, value2) | Returns *value1* if *value1* is not null, otherwise *value2* 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 2add67eb87..cd11a0f8fb 100644 --- a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java +++ b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java @@ -5866,6 +5866,82 @@ public class SqlOperatorTest { "INTEGER ARRAY NOT NULL"); } + /** Tests {@code MAP_FROM_ARRAYS} function from Spark. */ + @Test void testMapFromArraysFunc() { + final SqlOperatorFixture f0 = fixture(); + f0.setFor(SqlLibraryOperators.MAP_FROM_ARRAYS); + f0.checkFails("^map_from_arrays(array[1, 2], array['foo', 'bar'])^", + "No match found for function signature MAP_FROM_ARRAYS\\(<INTEGER ARRAY>, " + + "<CHAR\\(3\\) ARRAY>\\)", false); + + final SqlOperatorFixture f = f0.withLibrary(SqlLibrary.SPARK); + f.checkScalar("map_from_arrays(array[1, 2], array['foo', 'bar'])", "{1=foo, 2=bar}", + "(INTEGER NOT NULL, CHAR(3) NOT NULL) MAP NOT NULL"); + f.checkScalar("map_from_arrays(array[1, 1, null], array['foo', 'bar', 'name'])", + "{1=bar, null=name}", "(INTEGER, CHAR(4) NOT NULL) MAP NOT NULL"); + f.checkScalar("map_from_arrays(array(), array())", + "{}", "(UNKNOWN NOT NULL, UNKNOWN NOT NULL) MAP NOT NULL"); + f.checkType("map_from_arrays(cast(null as integer array), array['foo', 'bar'])", + "(INTEGER NOT NULL, CHAR(3) NOT NULL) MAP"); + f.checkNull("map_from_arrays(cast(null as integer array), array['foo', 'bar'])"); + + f.checkFails("^map_from_arrays(array[1, 2], 2)^", + "Cannot apply 'MAP_FROM_ARRAYS' to arguments of type 'MAP_FROM_ARRAYS\\(<INTEGER ARRAY>," + + " <INTEGER>\\)'. Supported form\\(s\\): 'MAP_FROM_ARRAYS\\(<ARRAY>, <ARRAY>\\)'", + false); + f.checkFails("^map_from_arrays(2, array[1, 2])^", + "Cannot apply 'MAP_FROM_ARRAYS' to arguments of type 'MAP_FROM_ARRAYS\\(<INTEGER>," + + " <INTEGER ARRAY>\\)'. Supported form\\(s\\): 'MAP_FROM_ARRAYS\\(<ARRAY>, <ARRAY>\\)'", + false); + f.checkFails("map_from_arrays(^array[1, '1', true]^, array['a', 'b', 'c'])", + "Parameters must be of the same type", + false); + f.checkFails("map_from_arrays(array['a', 'b', 'c'], ^array[1, '1', true]^)", + "Parameters must be of the same type", + false); + f.checkFails("map_from_arrays(array[1, 2], array['foo'])", + "Illegal arguments: The length of the keys array 2 is not equal to the length " + + "of the values array 1 in MAP_FROM_ARRAYS function", + true); + } + + /** Tests {@code STR_TO_MAP} function from Spark. */ + @Test void testStrToMapFunc() { + final SqlOperatorFixture f0 = fixture(); + f0.setFor(SqlLibraryOperators.STR_TO_MAP); + f0.checkFails("^str_to_map('a=1,b=2', ',', '=')^", + "No match found for function signature STR_TO_MAP\\(" + + "<CHARACTER>, <CHARACTER>, <CHARACTER>\\)", false); + + final SqlOperatorFixture f = f0.withLibrary(SqlLibrary.SPARK); + f.checkScalar("str_to_map('a=1,b=2', ',', '=')", "{a=1, b=2}", + "(CHAR(7) NOT NULL, CHAR(7) NOT NULL) MAP NOT NULL"); + f.checkScalar("str_to_map('a:1,b:2')", "{a=1, b=2}", + "(CHAR(7) NOT NULL, CHAR(7) NOT NULL) MAP NOT NULL"); + f.checkScalar("str_to_map('a:1,b:2', ',')", "{a=1, b=2}", + "(CHAR(7) NOT NULL, CHAR(7) NOT NULL) MAP NOT NULL"); + f.checkScalar("str_to_map('a=1&b=2', '&', '=')", "{a=1, b=2}", + "(CHAR(7) NOT NULL, CHAR(7) NOT NULL) MAP NOT NULL"); + f.checkScalar("str_to_map('k#2%v#3', '%', '#')", "{k=2, v=3}", + "(CHAR(7) NOT NULL, CHAR(7) NOT NULL) MAP NOT NULL"); + f.checkScalar("str_to_map('a:1&b:2', '&')", "{a=1, b=2}", + "(CHAR(7) NOT NULL, CHAR(7) NOT NULL) MAP NOT NULL"); + f.checkScalar("str_to_map('k:2%v:3', '%')", "{k=2, v=3}", + "(CHAR(7) NOT NULL, CHAR(7) NOT NULL) MAP NOT NULL"); + f.checkScalar("str_to_map('a')", "{a=null}", + "(CHAR(1) NOT NULL, CHAR(1) NOT NULL) MAP NOT NULL"); + f.checkScalar("str_to_map('a,b,c')", "{a=null, b=null, c=null}", + "(CHAR(5) NOT NULL, CHAR(5) NOT NULL) MAP NOT NULL"); + f.checkScalar("str_to_map('a-b--c', '--')", "{a-b=null, c=null}", + "(CHAR(6) NOT NULL, CHAR(6) NOT NULL) MAP NOT NULL"); + f.checkType("str_to_map(cast(null as varchar))", + "(VARCHAR, VARCHAR) MAP"); + f.checkNull("str_to_map(cast(null as varchar))"); + f.checkNull("str_to_map(null, ',', ':')"); + f.checkNull("str_to_map('a:1,b:2,c:3', null, ':')"); + f.checkNull("str_to_map('a:1,b:2,c:3', ',',null)"); + } + /** Tests {@code UNIX_SECONDS} and other datetime functions from BigQuery. */ @Test void testUnixSecondsFunc() { SqlOperatorFixture f = fixture() @@ -8510,6 +8586,7 @@ public class SqlOperatorTest { // empty array is illegal per SQL spec. presumably because one can't // infer type f.checkFails("^Array[]^", "Require at least 1 argument", false); + f.checkFails("^array[1, '1', true]^", "Parameters must be of the same type", false); } /** Test case for {@link SqlLibraryOperators#ARRAY} (Spark). */