From fd854fa70d32dbabc261ba375160df3b9c32bb01 Mon Sep 17 00:00:00 2001 From: "cc.cai" <2356672992@qq.com> Date: Fri, 27 Mar 2026 21:31:24 +0800 Subject: [PATCH 1/2] [CALCITE-6300] Function MAP_VALUES/MAP_KEYS gives exception when mapVauleType and mapKeyType not equals map Biggest mapKeytype or mapValueType --- .../apache/calcite/sql/type/OperandTypes.java | 27 ++++++++++++++++ .../apache/calcite/test/SqlOperatorTest.java | 32 ++++++++++++++++++- 2 files changed, 58 insertions(+), 1 deletion(-) 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 b67899d761a0..c3e04792dbaa 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 @@ -31,6 +31,8 @@ import org.apache.calcite.sql.SqlOperator; import org.apache.calcite.sql.SqlOperatorBinding; import org.apache.calcite.sql.SqlUtil; +import org.apache.calcite.sql.fun.SqlStdOperatorTable; +import org.apache.calcite.sql.parser.SqlParserPos; import org.apache.calcite.sql.util.SqlBasicVisitor; import org.apache.calcite.sql.validate.SqlLambdaScope; import org.apache.calcite.sql.validate.SqlValidator; @@ -1555,9 +1557,34 @@ private static class MapFunctionOperandTypeChecker } return false; } + // Insert implicit casts for operands whose SqlTypeName differs + // from the inferred key/value type. + coerceOperands(callBinding, argTypes, + componentType.left, componentType.right); return true; } + /** Casts operands whose {@code SqlTypeName} differs from the + * target key or value type. Operands at even positions are keys, + * odd positions are values. */ + private static void coerceOperands(SqlCallBinding callBinding, + List operandTypes, + RelDataType keyType, RelDataType valueType) { + final SqlCall call = callBinding.getCall(); + final List operands = call.getOperandList(); + for (int i = 0; i < operands.size(); i++) { + final RelDataType targetType = i % 2 == 0 ? keyType : valueType; + if (operandTypes.get(i).getSqlTypeName() + != targetType.getSqlTypeName()) { + call.setOperand(i, + SqlStdOperatorTable.CAST.createCall(SqlParserPos.ZERO, + operands.get(i), + SqlTypeUtil.convertTypeToSpec(targetType) + .withNullable(targetType.isNullable()))); + } + } + } + /** * Extract the key type and value type of arg types. */ 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 41b45d3c761a..6509463499a9 100644 --- a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java +++ b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java @@ -9355,6 +9355,20 @@ void checkArrayReverseFunc(SqlOperatorFixture f0, SqlFunction function, f1.checkScalar("map_keys(map('foo', 1, 'bar', 2))", "[foo, bar]", "CHAR(3) NOT NULL ARRAY NOT NULL"); + // [CALCITE-6300] MAP function with mixed key/value types + f1.checkScalar("map_keys(map(cast(1 as tinyint), 1, 2, 2))", "[1, 2]", + "INTEGER NOT NULL ARRAY NOT NULL"); + f1.checkScalar("map_keys(map(cast(1 as tinyint), 1, cast(2 as double), 2))", + "[1.0, 2.0]", + "DOUBLE NOT NULL ARRAY NOT NULL"); + f1.checkScalar("map_keys(map(cast(1 as tinyint), 1, cast(2 as float), 2))", + "[1.0, 2.0]", + "FLOAT NOT NULL ARRAY NOT NULL"); + f1.checkFails("map_keys(map(cast(1 as tinyint), 1, cast(null as float), 2))", + "Illegal arguments for MAP_KEYS function: " + + "using a map with a null key is not allowed", + true); + f.checkFails("map_keys(map['foo', 1, null, 2])", "Illegal arguments for MAP_KEYS function: using a map with a null key is not allowed", true); @@ -9394,6 +9408,21 @@ void checkArrayReverseFunc(SqlOperatorFixture f0, SqlFunction function, f1.checkScalar("map_values(map('foo', 1, 'bar', cast(null as integer)))", "[1, null]", "INTEGER ARRAY NOT NULL"); + // [CALCITE-6300] MAP function with mixed key/value types + f1.checkScalar("map_values(map('foo', null))", "[null]", + "NULL ARRAY NOT NULL"); + f1.checkScalar("map_values(map('foo', 1, 'bar', cast(1 as tinyint)))", "[1, 1]", + "INTEGER NOT NULL ARRAY NOT NULL"); + f1.checkScalar("map_values(map('foo', 1, 'bar', cast(1 as double)))", + "[1.0, 1.0]", + "DOUBLE NOT NULL ARRAY NOT NULL"); + f1.checkScalar("map_values(map('foo', 1, 'bar', cast(1 as float)))", + "[1.0, 1.0]", + "FLOAT NOT NULL ARRAY NOT NULL"); + f1.checkScalar("map_values(map('foo', 1, 'bar', cast(null as float)))", + "[1.0, null]", + "FLOAT ARRAY NOT NULL"); + f.checkFails("map_values(map['foo', 1, null, 2])", "Illegal arguments for MAP_VALUES function: using a map with a null key is not allowed", true); @@ -13879,8 +13908,9 @@ private static void checkArrayConcatAggFuncFails(SqlOperatorFixture t) { f1.checkScalar("map('washington', 1, 'obama', 44)", "{washington=1, obama=44}", "(CHAR(10) NOT NULL, INTEGER NOT NULL) MAP NOT NULL"); + // [CALCITE-6300] values are coerced to DECIMAL(11, 1) f1.checkScalar("map('k1', 1, 'k2', 2.0)", - "{k1=1, k2=2.0}", + "{k1=1.0, k2=2.0}", "(CHAR(2) NOT NULL, DECIMAL(11, 1) NOT NULL) MAP NOT NULL"); } From c01f0c3ada5f9970a8e3846c261008ac3ddf5352 Mon Sep 17 00:00:00 2001 From: "cc.cai" <2356672992@qq.com> Date: Sat, 28 Mar 2026 11:22:04 +0800 Subject: [PATCH 2/2] Add node type to list --- .../java/org/apache/calcite/sql/type/OperandTypes.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) 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 c3e04792dbaa..66b4aab9b66d 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 @@ -1570,17 +1570,20 @@ private static class MapFunctionOperandTypeChecker private static void coerceOperands(SqlCallBinding callBinding, List operandTypes, RelDataType keyType, RelDataType valueType) { + final SqlValidator validator = callBinding.getValidator(); final SqlCall call = callBinding.getCall(); final List operands = call.getOperandList(); for (int i = 0; i < operands.size(); i++) { final RelDataType targetType = i % 2 == 0 ? keyType : valueType; if (operandTypes.get(i).getSqlTypeName() != targetType.getSqlTypeName()) { - call.setOperand(i, + final SqlNode castNode = SqlStdOperatorTable.CAST.createCall(SqlParserPos.ZERO, operands.get(i), SqlTypeUtil.convertTypeToSpec(targetType) - .withNullable(targetType.isNullable()))); + .withNullable(targetType.isNullable())); + call.setOperand(i, castNode); + validator.setValidatedNodeType(castNode, targetType); } } }