From 7b5bbdf5034d77382055bf43a078629b52f668d3 Mon Sep 17 00:00:00 2001 From: Gustavo de Morais Date: Thu, 11 Jun 2026 20:34:37 +0200 Subject: [PATCH 1/2] [FLINK-39915][table] Reject mixing positional and named function arguments --- .../calcite/FlinkCalciteSqlValidator.java | 22 +++++++++++++++++++ .../calcite/FlinkCalciteSqlValidatorTest.java | 7 ++++++ .../stream/sql/ProcessTableFunctionTest.java | 5 +++++ 3 files changed, 34 insertions(+) diff --git a/flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/calcite/FlinkCalciteSqlValidator.java b/flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/calcite/FlinkCalciteSqlValidator.java index 77a1634fa0bc2..6d8ad8d259103 100644 --- a/flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/calcite/FlinkCalciteSqlValidator.java +++ b/flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/calcite/FlinkCalciteSqlValidator.java @@ -380,6 +380,7 @@ protected void addToSelectList( } final SqlBasicCall call = (SqlBasicCall) node; + checkNoNamedAndPositionalMixedArgs(call); // Special case for MODEL if (node instanceof SqlExplicitModelCall) { @@ -432,6 +433,27 @@ protected void addToSelectList( return rewritten; } + /** Mixing positional and named arguments is not supported and crashes operand permutation. */ + private static void checkNoNamedAndPositionalMixedArgs(SqlBasicCall call) { + if (!(call.getOperator() instanceof SqlFunction)) { + return; + } + final List operands = call.getOperandList(); + final boolean anyNamed = + operands.stream() + .anyMatch(op -> op != null && op.getKind() == SqlKind.ARGUMENT_ASSIGNMENT); + final boolean anyPositional = + operands.stream() + .anyMatch(op -> op != null && op.getKind() != SqlKind.ARGUMENT_ASSIGNMENT); + if (anyNamed && anyPositional) { + throw new ValidationException( + "Cannot mix positional and named arguments when calling function '" + + call.getOperator().getName() + + "'. Use either all positional arguments or all named arguments " + + "(e.g. arg => value)."); + } + } + @Override public SqlNode maybeCast(SqlNode node, RelDataType currentType, RelDataType desiredType) { return super.maybeCast(node, currentType, desiredType); diff --git a/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/calcite/FlinkCalciteSqlValidatorTest.java b/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/calcite/FlinkCalciteSqlValidatorTest.java index 7da22c97d5e2f..a9e9bce3ab194 100644 --- a/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/calcite/FlinkCalciteSqlValidatorTest.java +++ b/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/calcite/FlinkCalciteSqlValidatorTest.java @@ -138,4 +138,11 @@ void testExplainUpsertInto() { .hasMessageContaining( "UPSERT INTO statement is not supported. Please use INSERT INTO instead."); } + + @Test + void testMixedPositionalAndNamedArguments() { + assertThatThrownBy(() -> plannerMocks.getParser().parse("SELECT myFunc(1, b => 2) FROM t1")) + .isInstanceOf(ValidationException.class) + .hasMessageContaining("Cannot mix positional and named arguments"); + } } diff --git a/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/plan/stream/sql/ProcessTableFunctionTest.java b/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/plan/stream/sql/ProcessTableFunctionTest.java index 411b6fc2b75df..d0d150759aef9 100644 --- a/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/plan/stream/sql/ProcessTableFunctionTest.java +++ b/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/plan/stream/sql/ProcessTableFunctionTest.java @@ -349,6 +349,11 @@ void testErrorBehavior(ErrorSpec spec) { private static Stream errorSpecs() { return Stream.of( + ErrorSpec.ofSelect( + "mixed positional and named arguments", + ScalarArgsFunction.class, + "SELECT * FROM f(1, b => true)", + "Cannot mix positional and named arguments when calling function 'f'"), ErrorSpec.ofSelect( "invalid uid", ScalarArgsFunction.class, From 4a4d514a9fc7794ca897b0fd08ae54cc6caaf631 Mon Sep 17 00:00:00 2001 From: Gustavo de Morais Date: Fri, 12 Jun 2026 12:17:55 +0200 Subject: [PATCH 2/2] [FLINK-39915][table] Add positive assertions for all-positional and all-named arguments --- .../calcite/FlinkCalciteSqlValidatorTest.java | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/calcite/FlinkCalciteSqlValidatorTest.java b/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/calcite/FlinkCalciteSqlValidatorTest.java index a9e9bce3ab194..831b3b6ecc480 100644 --- a/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/calcite/FlinkCalciteSqlValidatorTest.java +++ b/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/calcite/FlinkCalciteSqlValidatorTest.java @@ -18,9 +18,13 @@ package org.apache.flink.table.planner.calcite; +import org.apache.flink.table.annotation.ArgumentHint; +import org.apache.flink.table.annotation.DataTypeHint; +import org.apache.flink.table.annotation.FunctionHint; import org.apache.flink.table.api.DataTypes; import org.apache.flink.table.api.Schema; import org.apache.flink.table.api.ValidationException; +import org.apache.flink.table.functions.ScalarFunction; import org.apache.flink.table.planner.utils.PlannerMocks; import org.junit.jupiter.api.Test; @@ -141,8 +145,29 @@ void testExplainUpsertInto() { @Test void testMixedPositionalAndNamedArguments() { - assertThatThrownBy(() -> plannerMocks.getParser().parse("SELECT myFunc(1, b => 2) FROM t1")) + plannerMocks + .getFunctionCatalog() + .registerTemporarySystemFunction("myFunc", new NamedArgsScalarFunction(), false); + + assertDoesNotThrow(() -> plannerMocks.getParser().parse("SELECT myFunc(1, 2) FROM t1")); + assertDoesNotThrow( + () -> plannerMocks.getParser().parse("SELECT myFunc(in1 => 1, in2 => 2) FROM t1")); + assertThatThrownBy( + () -> plannerMocks.getParser().parse("SELECT myFunc(1, in2 => 2) FROM t1")) .isInstanceOf(ValidationException.class) .hasMessageContaining("Cannot mix positional and named arguments"); } + + /** Scalar function with named arguments for the mixed-argument validation test. */ + public static class NamedArgsScalarFunction extends ScalarFunction { + @FunctionHint( + output = @DataTypeHint("INT"), + arguments = { + @ArgumentHint(name = "in1", type = @DataTypeHint("INT")), + @ArgumentHint(name = "in2", type = @DataTypeHint("INT")) + }) + public Integer eval(Integer in1, Integer in2) { + return null; + } + } }