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 d561bc8abb01..78b3890f4190 100644 --- a/core/src/main/java/org/apache/calcite/sql/SqlUtil.java +++ b/core/src/main/java/org/apache/calcite/sql/SqlUtil.java @@ -519,9 +519,12 @@ public static SqlLiteral concatenateLiterals(List lits) { private static Iterator filterOperatorRoutinesByKind( Iterator routines, final SqlKind sqlKind) { + // Mirror getFunctionKind() on both sides, or an operator whose kind maps to + // something else (e.g. POSITION -> OTHER_FUNCTION) can fail to match itself. + final SqlKind sqlFunctionKind = sqlKind.getFunctionKind(); return Iterators.filter(routines, operator -> requireNonNull(operator, "operator") - .getKind().getFunctionKind() == sqlKind); + .getKind().getFunctionKind() == sqlFunctionKind); } /** 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 5198762c106b..01d50a464ecf 100644 --- a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java +++ b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java @@ -1025,6 +1025,30 @@ void testDyadicCollateOperator() { .fails("Parameters must be of the same type"); } + /** Test case for + * [CALCITE-7724] SqlUtil#lookupSubjectRoutines rejects a valid operator when its + * SqlKind is remapped by SqlKind#getFunctionKind() and two operator-table entries + * resolve to it. + * + *

The kind-based fourth pass in {@code filterOperatorRoutinesByKind} maps only + * the candidate's kind through {@code getFunctionKind()}, not the call's own kind - + * so an operator whose kind is remapped (e.g. {@link SqlKind#POSITION}) can fail to + * match itself once a second candidate for the same name exists. */ + @Test void testFunctionKindMismatchWithDuplicateOperatorTableEntry() { + // Chaining the operator table with itself ensures that each appears twice. + final SqlOperatorTable duplicated = + SqlOperatorTables.chain(SqlStdOperatorTable.instance(), SqlStdOperatorTable.instance()); + expr("position('mouse' in 'house')") + .withOperatorTable(duplicated) + .ok(); + expr("char_length('string')") + .withOperatorTable(duplicated) + .ok(); + expr("character_length('string')") + .withOperatorTable(duplicated) + .ok(); + } + @Test void testTrim() { expr("trim('mustache' FROM 'beard')").ok(); expr("trim(both 'mustache' FROM 'beard')").ok();