diff --git a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorUtil.java b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorUtil.java index f907f4ba1421..74e85c76f70e 100644 --- a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorUtil.java +++ b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorUtil.java @@ -1560,7 +1560,12 @@ private static void adjustTypeForMultisetConstructor( if (adjustedOperands == null) { adjustedOperands = new ArrayList<>(operands); } - adjustedOperands.set(i, castTo(operands.get(i), elementType)); + SqlCall cast = (SqlCall) castTo(operands.get(i), elementType); + // This CAST was generated with the built-in operator; validate its + // operands directly instead of resolving the function again by name. + cast.getOperator().validateOperands( + sqlCallBinding.getValidator(), sqlCallBinding.getScope(), cast); + adjustedOperands.set(i, cast); } } if (adjustedOperands != null) { diff --git a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java index 3915e5723cb8..77d6b1d7d5b0 100644 --- a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java +++ b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java @@ -41,6 +41,7 @@ import org.apache.calcite.sql.SqlOperatorTable; import org.apache.calcite.sql.SqlSelect; import org.apache.calcite.sql.SqlSpecialOperator; +import org.apache.calcite.sql.SqlSyntax; import org.apache.calcite.sql.fun.SqlCase; import org.apache.calcite.sql.fun.SqlLibrary; import org.apache.calcite.sql.fun.SqlLibraryOperatorTableFactory; @@ -62,6 +63,7 @@ import org.apache.calcite.sql.validate.SqlConformanceEnum; import org.apache.calcite.sql.validate.SqlDelegatingConformance; import org.apache.calcite.sql.validate.SqlMonotonicity; +import org.apache.calcite.sql.validate.SqlNameMatcher; import org.apache.calcite.sql.validate.SqlValidator; import org.apache.calcite.sql.validate.SqlValidatorCatalogReader; import org.apache.calcite.sql.validate.SqlValidatorImpl; @@ -9756,6 +9758,41 @@ void testGroupExpressionEquivalenceParams() { .columnType("CHAR(3) ARRAY NOT NULL"); } + @Test void testValidateOperandsCachesGeneratedCastType() + throws SqlParseException { + final int[] castLookups = {0}; + final SqlValidator validator = fixture() + .withFactory( + factory -> factory.withOperatorTable(operatorTable -> + new SqlOperatorTable() { + @Override public void lookupOperatorOverloads(SqlIdentifier opName, + SqlFunctionCategory category, SqlSyntax syntax, + List operatorList, SqlNameMatcher nameMatcher) { + if (opName.isSimple() && opName.getSimple().equals("CAST")) { + castLookups[0]++; + } + operatorTable.lookupOperatorOverloads( + opName, category, syntax, operatorList, nameMatcher); + } + + @Override public List getOperatorList() { + return operatorTable.getOperatorList(); + } + })) + .factory.createValidator(); + final SqlCall cast = (SqlCall) SqlParser + .create("cast('a' as varchar(2))", SqlParser.config()) + .parseExpression(); + final SqlValidatorScope scope = validator.getEmptyScope(); + validator.deriveType(scope, cast.getOperandList().get(0)); + // SqlValidatorUtil relies on validateOperands caching the generated CAST + // type so a later deriveType call does not resolve the operator by name. + cast.getOperator().validateOperands(validator, scope, cast); + + validator.deriveType(scope, cast); + assertThat(castLookups[0], is(0)); + } + /** * Test case for * [CALCITE-4999]