From 580debaa2bb0bfc3b18ea22133c03762f9e11ca3 Mon Sep 17 00:00:00 2001 From: dssysolyatin Date: Sat, 4 Apr 2026 19:26:12 +0300 Subject: [PATCH] [CALCITE-7685] Filter unresolved function overloads by argument count during early resolution --- .../calcite/sql/SqlUnresolvedFunction.java | 5 +++ .../java/org/apache/calcite/sql/SqlUtil.java | 25 ++++++++++++++ .../calcite/sql/validate/AggVisitor.java | 10 +++--- .../sql/validate/SqlValidatorImpl.java | 9 ++--- .../apache/calcite/test/SqlValidatorTest.java | 33 +++++++++++++++++-- 5 files changed, 70 insertions(+), 12 deletions(-) diff --git a/core/src/main/java/org/apache/calcite/sql/SqlUnresolvedFunction.java b/core/src/main/java/org/apache/calcite/sql/SqlUnresolvedFunction.java index d1f3d796a9bd..5f72d7a77002 100644 --- a/core/src/main/java/org/apache/calcite/sql/SqlUnresolvedFunction.java +++ b/core/src/main/java/org/apache/calcite/sql/SqlUnresolvedFunction.java @@ -70,4 +70,9 @@ public SqlUnresolvedFunction( return typeFactory.createTypeWithNullability( typeFactory.createSqlType(SqlTypeName.ANY), true); } + + @Override public boolean argumentMustBeScalar(final int ordinal) { + // We don't know whether the argument is scalar or not for an unresolved function + return false; + } } 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 b5c91a1c7ad0..c52c221f73ba 100644 --- a/core/src/main/java/org/apache/calcite/sql/SqlUtil.java +++ b/core/src/main/java/org/apache/calcite/sql/SqlUtil.java @@ -626,6 +626,31 @@ public static Iterator lookupSubjectRoutines( return filterOperatorRoutinesByKind(routines, sqlKind); } + /** + * Finding operators matching the given name and number of arguments. + * + * @param opTab operator table to search + * @param funcName name of function being invoked + * @param argNumber number of arguments + * @param category category of routine to look up + * @param nameMatcher Whether to look up the function case-sensitively + * @return list of matching routines + */ + public static List lookupOperatorsByParameterCount( + SqlOperatorTable opTab, + SqlFunctionCategory category, + SqlSyntax syntax, + SqlIdentifier funcName, + SqlNameMatcher nameMatcher, + int argNumber) { + final List sqlOperators = new ArrayList<>(); + opTab.lookupOperatorOverloads(funcName, category, syntax, sqlOperators, + nameMatcher); + return sqlOperators.stream() + .filter(sqlOperator -> sqlOperator.getOperandCountRange().isValidCount(argNumber)) + .collect(Collectors.toList()); + } + /** * Determines whether there is a routine matching the given name and number * of arguments. diff --git a/core/src/main/java/org/apache/calcite/sql/validate/AggVisitor.java b/core/src/main/java/org/apache/calcite/sql/validate/AggVisitor.java index c8583e512019..c514048a4b17 100644 --- a/core/src/main/java/org/apache/calcite/sql/validate/AggVisitor.java +++ b/core/src/main/java/org/apache/calcite/sql/validate/AggVisitor.java @@ -23,12 +23,12 @@ import org.apache.calcite.sql.SqlOperator; import org.apache.calcite.sql.SqlOperatorTable; import org.apache.calcite.sql.SqlSyntax; +import org.apache.calcite.sql.SqlUtil; import org.apache.calcite.sql.fun.SqlAbstractGroupFunction; import org.apache.calcite.sql.util.SqlBasicVisitor; import org.checkerframework.checker.nullness.qual.Nullable; -import java.util.ArrayList; import java.util.List; import static java.util.Objects.requireNonNull; @@ -89,12 +89,12 @@ abstract class AggVisitor extends SqlBasicVisitor { if (operator instanceof SqlFunction) { final SqlFunction sqlFunction = (SqlFunction) operator; if (sqlFunction.getFunctionType().isUserDefinedNotSpecificFunction()) { - final List list = new ArrayList<>(); final SqlIdentifier identifier = sqlFunction.getSqlIdentifier(); if (identifier != null) { - opTab.lookupOperatorOverloads(identifier, - sqlFunction.getFunctionType(), SqlSyntax.FUNCTION, list, - nameMatcher); + final List list = + SqlUtil.lookupOperatorsByParameterCount(opTab, + sqlFunction.getFunctionType(), SqlSyntax.FUNCTION, + identifier, nameMatcher, call.operandCount()); for (SqlOperator operator2 : list) { if (operator2.isAggregator() && !operator2.requiresOver()) { // If nested aggregates disallowed or found aggregate at invalid diff --git a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java index 023de1b77f77..680d10e7e8cc 100644 --- a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java +++ b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java @@ -1824,10 +1824,11 @@ private void handleOffsetFetch(@Nullable SqlNode offset, @Nullable SqlNode fetch // a half-hearted resolution now in case it's a // builtin function requiring special casing. If it's // not, we'll handle it later during overload resolution. - final List overloads = new ArrayList<>(); - opTab.lookupOperatorOverloads(function.getNameAsId(), - function.getFunctionType(), SqlSyntax.FUNCTION, overloads, - catalogReader.nameMatcher()); + final List overloads = + SqlUtil.lookupOperatorsByParameterCount(opTab, + function.getFunctionType(), SqlSyntax.FUNCTION, + function.getNameAsId(), catalogReader.nameMatcher(), + call.operandCount()); if (overloads.size() == 1) { ((SqlBasicCall) call).setOperator(overloads.get(0)); } 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 8f95099ab952..879eed2fd923 100644 --- a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java +++ b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java @@ -5751,24 +5751,51 @@ private ImmutableList cube(ImmutableBitSet... sets) { .fails("GROUPING_ID operator may only occur in SELECT, HAVING or ORDER BY clause"); } + /** Test case for + * [CALCITE-7685] + * Filter unresolved function overloads by argument count during early + * resolution. + */ + @Test void testAggregateFunctionWrongNumberOfArguments() { + final String invalidArgCount = + "Invalid number of arguments to function 'MYAGG'. Was expecting 2 arguments"; + sql("select myagg(sal, comm) from emp").ok(); + sql("select deptno from emp order by ^myagg(sal, comm)^") + .fails("Aggregate expression is illegal in ORDER BY clause of " + + "non-aggregating SELECT"); + // Before CALCITE-7685 the error was "Aggregate expression is illegal in + // ORDER BY clause of non-aggregating SELECT". + sql("select deptno from emp order by ^myagg(deptno)^") + .fails(invalidArgCount); + // Before CALCITE-7685 the error was "Aggregate expression is illegal in + // WHERE clause". + sql("select deptno from emp where ^myagg(deptno)^ = 1") + .fails(invalidArgCount); + sql("select ^myagg(deptno)^ from emp") + .fails(invalidArgCount); + } + @Test void testGroupId() { final String groupIdOnlyInAggregate = "GROUP_ID operator may only occur in an aggregate query"; final String groupIdWrongClause = "GROUP_ID operator may only occur in SELECT, HAVING or ORDER BY clause"; + final String groupIdInvalidArgumentNumber = + "Invalid number of arguments to function 'GROUP_ID'. Was expecting 0 arguments"; sql("select deptno, group_id() from emp group by deptno").ok(); sql("select deptno, ^group_id^ as x from emp group by deptno") .fails("Column 'GROUP_ID' not found in any table"); sql("select deptno, ^group_id(deptno)^ from emp group by deptno") - .fails("Invalid number of arguments to function 'GROUP_ID'\\. " - + "Was expecting 0 arguments"); + .fails(groupIdInvalidArgumentNumber); // Oracle throws "GROUPING function only supported with GROUP BY CUBE or // ROLLUP" sql("select ^group_id()^ from emp") .fails(groupIdOnlyInAggregate); - sql("select deptno from emp order by ^group_id(deptno)^") + sql("select deptno from emp order by ^group_id()^") .fails(groupIdOnlyInAggregate); + sql("select deptno from emp order by ^group_id(deptno)^") + .fails(groupIdInvalidArgumentNumber); // Oracle throws "GROUPING function only supported with GROUP BY CUBE or // ROLLUP" sql("select 1 from emp order by ^group_id()^")