This is an automated email from the ASF dual-hosted git repository. snuyanzin pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/flink.git
commit 27bed1c68b5fe6149a2cdc6b29634a671d10431e Author: Sergey Nuyanzin <[email protected]> AuthorDate: Thu Oct 8 23:06:52 2026 +0200 [FLINK-40965][table] Treat `IF` as conditional --- .../table/planner/codegen/ExprCodeGenerator.scala | 3 +++ .../planner/runtime/stream/sql/FunctionITCase.java | 25 ++++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/flink-table/flink-table-planner/src/main/scala/org/apache/flink/table/planner/codegen/ExprCodeGenerator.scala b/flink-table/flink-table-planner/src/main/scala/org/apache/flink/table/planner/codegen/ExprCodeGenerator.scala index 86ffa7b08e9..e6feb660c18 100644 --- a/flink-table/flink-table-planner/src/main/scala/org/apache/flink/table/planner/codegen/ExprCodeGenerator.scala +++ b/flink-table/flink-table-planner/src/main/scala/org/apache/flink/table/planner/codegen/ExprCodeGenerator.scala @@ -565,10 +565,13 @@ class ExprCodeGenerator( * - `CASE(when_1, then_1, when_2, then_2, ..., else)`: only `when_1` is unconditional. * - `AND(a_0, a_1, ..., a_n)` / `OR(...)`: only `a_0` is unconditional; subsequent operands are * short-circuited by the operator semantics and the codegen. + * - `IF(cond, then, else)`: only `cond` is unconditional; `IfCallGen` guards `then`/`else`. */ private def conditionalOperandIndices(call: RexCall): Set[Int] = call.getKind match { case SqlKind.CASE | SqlKind.AND | SqlKind.OR | SqlKind.COALESCE => (1 until call.getOperands.size).toSet + case SqlKind.OTHER_FUNCTION if call.getOperator == IF => + (1 until call.getOperands.size).toSet case _ => Set.empty } diff --git a/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/runtime/stream/sql/FunctionITCase.java b/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/runtime/stream/sql/FunctionITCase.java index c9d31af6e97..01d2e8f5f06 100644 --- a/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/runtime/stream/sql/FunctionITCase.java +++ b/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/runtime/stream/sql/FunctionITCase.java @@ -1346,6 +1346,31 @@ public class FunctionITCase extends StreamingTestBase { assertThat(actual).containsExactly(Row.of(10), Row.of(30)); } + /** + * Pins the IF guard interaction with the RexLocalRef cache. Without scoping the ELSE-branch + * {@code CAST(s AS DOUBLE)} is hoisted to the method top and throws on {@code s = ""}; with + * scoping it stays inside the branch and runs only when the guard is false. + */ + @Test + void testCalcIfGuardShortCircuit() { + final List<Row> sourceData = List.of(Row.of(""), Row.of("1.5")); + + TestCollectionTableFactory.reset(); + TestCollectionTableFactory.initData(sourceData); + tEnv().executeSql("CREATE TABLE SourceTable (s STRING) WITH ('connector' = 'COLLECTION')"); + + final List<Row> actual = + CollectionUtil.iteratorToList( + tEnv().executeSql( + "SELECT IF(s IS NULL OR CHAR_LENGTH(s) = 0," + + " CAST(NULL AS DOUBLE)," + + " CAST(s AS DOUBLE))" + + " FROM SourceTable") + .collect()); + + assertThat(actual).containsExactly(Row.of((Object) null), Row.of(1.5d)); + } + @Test void testStructuredScalarFunction() throws Exception { final List<Row> sourceData =
