diff --git a/core/src/main/java/org/apache/calcite/sql/fun/SqlMapValueConstructor.java b/core/src/main/java/org/apache/calcite/sql/fun/SqlMapValueConstructor.java index 7f3da3bdb23..a4974712006 100644 --- a/core/src/main/java/org/apache/calcite/sql/fun/SqlMapValueConstructor.java +++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlMapValueConstructor.java @@ -42,7 +42,8 @@ */ public class SqlMapValueConstructor extends SqlMultisetValueConstructor { public SqlMapValueConstructor() { - super("MAP", SqlKind.MAP_VALUE_CONSTRUCTOR); + // no need to deduce NULL operand type + super("MAP", SqlKind.MAP_VALUE_CONSTRUCTOR, null); } @SuppressWarnings("argument.type.incompatible") diff --git a/core/src/main/java/org/apache/calcite/sql/fun/SqlMultisetValueConstructor.java b/core/src/main/java/org/apache/calcite/sql/fun/SqlMultisetValueConstructor.java index 8efd45a3eb3..acbafc97e01 100644 --- a/core/src/main/java/org/apache/calcite/sql/fun/SqlMultisetValueConstructor.java +++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlMultisetValueConstructor.java @@ -28,6 +28,7 @@ import org.apache.calcite.sql.type.InferTypes; import org.apache.calcite.sql.type.OperandTypes; import org.apache.calcite.sql.type.ReturnTypes; +import org.apache.calcite.sql.type.SqlOperandTypeInference; import org.apache.calcite.sql.type.SqlTypeUtil; import org.checkerframework.checker.nullness.qual.Nullable; @@ -54,12 +55,18 @@ public SqlMultisetValueConstructor() { } protected SqlMultisetValueConstructor(String name, SqlKind kind) { + this(name, kind, InferTypes.FIRST_KNOWN); + } + + protected SqlMultisetValueConstructor( + String name, SqlKind kind, + @Nullable SqlOperandTypeInference operandTypeInference) { super( name, kind, MDX_PRECEDENCE, false, ReturnTypes.ARG0, - InferTypes.FIRST_KNOWN, + operandTypeInference, OperandTypes.VARIADIC); } diff --git a/core/src/test/resources/sql/misc.iq b/core/src/test/resources/sql/misc.iq index 8bdb345b22f..74ee82abb8f 100644 --- a/core/src/test/resources/sql/misc.iq +++ b/core/src/test/resources/sql/misc.iq @@ -2602,4 +2602,20 @@ FROM ( (1 row) !ok + +# [CALCITE-6040] The operand type inference of SqlMapValueConstructor is incorrect +SELECT + map['foo', null], + map[null, 'foo'], + map[1, 'foo', 2, null], + map[1, null, 2, 'foo']; ++------------+------------+-----------------+-----------------+ +| EXPR$0 | EXPR$1 | EXPR$2 | EXPR$3 | ++------------+------------+-----------------+-----------------+ +| {foo=null} | {null=foo} | {1=foo, 2=null} | {1=null, 2=foo} | ++------------+------------+-----------------+-----------------+ +(1 row) + +!ok + # End misc.iq 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 fd11a31f052..bfc14daab70 100644 --- a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java +++ b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java @@ -10442,6 +10442,27 @@ private static void checkArrayConcatAggFuncFails(SqlOperatorFixture t) { f.checkScalar("map['washington', 1, 'obama', 44]", "{washington=1, obama =44}", "(CHAR(10) NOT NULL, INTEGER NOT NULL) MAP NOT NULL"); + + // check null key or null value + f.checkScalar("map['foo', null]", + "{foo=null}", + "(CHAR(3) NOT NULL, NULL) MAP NOT NULL"); + f.checkScalar("map[null, 'foo']", + "{null=foo}", + "(NULL, CHAR(3) NOT NULL) MAP NOT NULL"); + f.checkScalar("map[1, 'foo', 2, null]", + "{1=foo, 2=null}", + "(INTEGER NOT NULL, CHAR(3)) MAP NOT NULL"); + f.checkScalar("map[1, null, 2, 'foo']", + "{1=null, 2=foo}", + "(INTEGER NOT NULL, CHAR(3)) MAP NOT NULL"); + f.checkScalar("map[1, null, 2, null]", + "{1=null, 2=null}", + "(INTEGER NOT NULL, NULL) MAP NOT NULL"); + f.checkScalar("map[null, 1, null, 2]", + "{null=2}", + "(NULL, INTEGER NOT NULL) MAP NOT NULL"); + // elements cast f.checkScalar("map['A', 1, 'ABC', 2]", "{A =1, ABC=2}", "(CHAR(3) NOT NULL, INTEGER NOT NULL) MAP NOT NULL");