From 88e506643b35357eb75f280d3b619707eab28b68 Mon Sep 17 00:00:00 2001 From: xuzifu666 <1206332514@qq.com> Date: Sun, 18 Oct 2026 14:09:02 +0800 Subject: [PATCH] [CALCITE-6245] A ValidationError occurs when a named parameter is used on a ScalarFunction or AggregateFunction --- .../java/org/apache/calcite/sql/SqlUtil.java | 25 ++++++- .../validate/implicit/TypeCoercionImpl.java | 2 +- .../java/org/apache/calcite/test/UdfTest.java | 72 +++++++++++++++++++ 3 files changed, 95 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/org/apache/calcite/sql/SqlUtil.java b/core/src/main/java/org/apache/calcite/sql/SqlUtil.java index e6bccfe51e3e..e1e6e23e4e43 100644 --- a/core/src/main/java/org/apache/calcite/sql/SqlUtil.java +++ b/core/src/main/java/org/apache/calcite/sql/SqlUtil.java @@ -41,6 +41,7 @@ import org.apache.calcite.sql.util.SqlBasicVisitor; import org.apache.calcite.sql.util.SqlVisitor; import org.apache.calcite.sql.validate.SqlNameMatcher; +import org.apache.calcite.sql.validate.SqlNameMatchers; import org.apache.calcite.sql.validate.SqlValidatorUtil; import org.apache.calcite.util.BarfingInvocationHandler; import org.apache.calcite.util.ConversionUtil; @@ -754,7 +755,7 @@ private static Iterator filterRoutinesByParameterTypeAndName( // parameters of all of these names. Map map = new HashMap<>(); for (Ord argName : Ord.zip(argNames)) { - int i = paramNames.indexOf(argName.e); + int i = lookupParamNameCaseInsensitive(paramNames, argName.e); if (i < 0) { return null; } @@ -766,6 +767,24 @@ private static Iterator filterRoutinesByParameterTypeAndName( }); } + /** Looks up a parameter name in a list of formal parameter names, + * case-insensitively. + * + * @param paramNames list of formal parameter names + * @param argName argument name to look up + * @return index of the first matching parameter, or -1 if not found + */ + public static int lookupParamNameCaseInsensitive(List paramNames, + String argName) { + for (int i = 0; i < paramNames.size(); i++) { + if (SqlNameMatchers.withCaseSensitive(false) + .matches(paramNames.get(i), argName)) { + return i; + } + } + return -1; + } + /** * Filters an iterator of routines, keeping only those with the best match for * the actual argument types. @@ -802,7 +821,7 @@ private static Iterator filterRoutinesByTypePrecedence( final List paramTypes = operandMetadata.paramTypes(typeFactory); int index = argNames != null - ? paramNames.indexOf(argNames.get(argType.i)) + ? lookupParamNameCaseInsensitive(paramNames, argNames.get(argType.i)) : argType.i; final RelDataType paramType = paramTypes.get(index); return precList.compareTypePrecedence(paramType, bestMatch) >= 0; @@ -828,7 +847,7 @@ private static Iterator filterRoutinesByTypePrecedence( operandMetadata.paramTypes(typeFactory); final List paramNames = operandMetadata.paramNames(); final RelDataType paramType = argNames != null - ? paramTypes.get(paramNames.indexOf(argNames.get(i))) + ? paramTypes.get(lookupParamNameCaseInsensitive(paramNames, argNames.get(i))) : paramTypes.get(i); if (bestMatch == null) { bestMatch = paramType; diff --git a/core/src/main/java/org/apache/calcite/sql/validate/implicit/TypeCoercionImpl.java b/core/src/main/java/org/apache/calcite/sql/validate/implicit/TypeCoercionImpl.java index 46dc3af5ed80..7f4d0d735611 100644 --- a/core/src/main/java/org/apache/calcite/sql/validate/implicit/TypeCoercionImpl.java +++ b/core/src/main/java/org/apache/calcite/sql/validate/implicit/TypeCoercionImpl.java @@ -798,7 +798,7 @@ && coerceOperandType(binding.getScope(), binding.getCall(), i, implicitType) final List operandList = ((SqlCall) operand).getOperandList(); String name = ((SqlIdentifier) operandList.get(1)).getSimple(); final List paramNames = operandMetadata.paramNames(); - int formalIndex = paramNames.indexOf(name); + int formalIndex = SqlUtil.lookupParamNameCaseInsensitive(paramNames, name); if (formalIndex < 0) { return false; } diff --git a/core/src/test/java/org/apache/calcite/test/UdfTest.java b/core/src/test/java/org/apache/calcite/test/UdfTest.java index 282cc0ae21b1..ec641a6e9a8e 100644 --- a/core/src/test/java/org/apache/calcite/test/UdfTest.java +++ b/core/src/test/java/org/apache/calcite/test/UdfTest.java @@ -247,6 +247,78 @@ private CalciteAssert.AssertThat withUdf() { assertThat(after, is(before + 1)); } + /** Test case for + * [CALCITE-6245] + * A ValidationError occurs when a named parameter is used on a + * ScalarFunction or AggregateFunction. */ + @Test void testScalarFunctionWithParameter() { + final String sql = "select \"adhoc\".my_det_plus(x => \"deptno\", y => 100) as p\n" + + "from \"adhoc\".EMPLOYEES"; + final AtomicInteger c = Smalls.MyDeterministicPlusFunction.INSTANCE_COUNT.get(); + final int before = c.get(); + withUdf() + .with("caseSensitive", false) + .query(sql).returnsUnordered("P=110", + "P=120", + "P=110", + "P=110"); + final int after = c.get(); + assertThat(after, is(before + 1)); + // Unquoted argument names in reverse order: the parser upper-cases 'x' and + // 'y' to 'X' and 'Y', and the arguments must still be permuted into the + // formal parameter order (x first, y second). + final String reverseSql = "select \"adhoc\".my_det_plus(y => 100, x => \"deptno\") as p\n" + + "from \"adhoc\".EMPLOYEES"; + withUdf() + .with("caseSensitive", false) + .query(reverseSql).returnsUnordered("P=110", + "P=120", + "P=110", + "P=110"); + } + + /** Test case for + * [CALCITE-6245] + * A ValidationError occurs when a named parameter is used on a + * ScalarFunction or AggregateFunction. */ + @Test void testAggFunctionWithParameter() { + final String empDept = JdbcTest.EmpDeptTableFactory.class.getName(); + final String sum = Smalls.MyStaticSumFunction.class.getName(); + final String sum2 = Smalls.MySumFunction.class.getName(); + final CalciteAssert.AssertThat with = CalciteAssert.model("{\n" + + " version: '1.0',\n" + + " schemas: [\n" + + " {\n" + + " name: 'adhoc',\n" + + " tables: [\n" + + " {\n" + + " name: 'EMPLOYEES',\n" + + " type: 'custom',\n" + + " factory: '" + empDept + "',\n" + + " operand: {'foo': true, 'bar': 345}\n" + + " }\n" + + " ],\n" + + " functions: [\n" + + " {\n" + + " name: 'MY_SUM',\n" + + " className: '" + sum + "'\n" + + " },\n" + + " {\n" + + " name: 'MY_SUM2',\n" + + " className: '" + sum2 + "'\n" + + " }\n" + + " ]\n" + + " }\n" + + " ]\n" + + "}") + .withDefaultSchema("adhoc") + .with("caseSensitive", false); + with.withDefaultSchema(null) + .query( + "select \"adhoc\".my_sum(arg1 => \"deptno\") as p from \"adhoc\".EMPLOYEES\n") + .returns("P=50\n"); + } + @Test void testUserDefinedFunctionB() { final String sql = "select \"adhoc\".my_double(\"deptno\") as p\n" + "from \"adhoc\".EMPLOYEES";