From a1d7732ae7efbf16fa72e1a05e156c2868a8fd1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=83=A1=E7=90=B3=28HuLin=29?= Date: Thu, 6 Aug 2026 09:44:43 +0800 Subject: [PATCH 1/5] [FLINK-40338][table-runtime] ELT() throws ClassCastException when index is not INT EltFunction.eval declares index as java.lang.Number to support TINYINT/SMALLINT/INT/BIGINT, but indexes the varargs array with exprs[(int) index - 1]. Per JLS 5.5, casting a Number reference to int compiles to a checkcast to Integer followed by unboxing, so a Byte, Short, or Long value throws ClassCastException on the success path (1 <= index <= exprs.length). The out-of-range guard above uses index.longValue(), so out-of-range indices of any type still return NULL correctly; the exception only fires in the valid range. Narrow the already-unboxed long idx (computed above for the range check) via primitive narrowing (JLS 5.1.3) instead, which emits no checkcast and works for every INTEGER_NUMERIC type. Adds TINYINT/SMALLINT/BIGINT index regression cases to StringFunctionsITCase. --- .../planner/functions/StringFunctionsITCase.java | 16 ++++++++++++++++ .../runtime/functions/scalar/EltFunction.java | 5 ++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/functions/StringFunctionsITCase.java b/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/functions/StringFunctionsITCase.java index 0fd1c53dac9798..e1e6223c29dbb1 100644 --- a/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/functions/StringFunctionsITCase.java +++ b/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/functions/StringFunctionsITCase.java @@ -182,6 +182,22 @@ private Stream eltTestCases() { DataTypes.VARCHAR(5)) .testResult( lit(2).elt("a", "b"), "ELT(2, 'a', 'b')", "b", DataTypes.CHAR(1)) + // FLINK-40338: non-INT INTEGER_NUMERIC index must not throw ClassCastException + .testResult( + lit(2).cast(DataTypes.TINYINT()).elt("scala", "java"), + "ELT(CAST(2 AS TINYINT), 'scala', 'java')", + "java", + DataTypes.VARCHAR(5)) + .testResult( + lit(2).cast(DataTypes.SMALLINT()).elt("scala", "java"), + "ELT(CAST(2 AS SMALLINT), 'scala', 'java')", + "java", + DataTypes.VARCHAR(5)) + .testResult( + lit(2).cast(DataTypes.BIGINT()).elt("scala", "java"), + "ELT(CAST(2 AS BIGINT), 'scala', 'java')", + "java", + DataTypes.VARCHAR(5)) .testResult( lit(2).elt($("f2"), $("f3"), $("f3")), "ELT(2, f2, f3, f3)", diff --git a/flink-table/flink-table-runtime/src/main/java/org/apache/flink/table/runtime/functions/scalar/EltFunction.java b/flink-table/flink-table-runtime/src/main/java/org/apache/flink/table/runtime/functions/scalar/EltFunction.java index 9af2cf20367d81..9ad4ac7116dfff 100644 --- a/flink-table/flink-table-runtime/src/main/java/org/apache/flink/table/runtime/functions/scalar/EltFunction.java +++ b/flink-table/flink-table-runtime/src/main/java/org/apache/flink/table/runtime/functions/scalar/EltFunction.java @@ -40,6 +40,9 @@ public EltFunction(SpecializedContext context) { if (idx < 1 || idx > exprs.length) { return null; } - return exprs[(int) index - 1]; + // Narrow the already-unboxed long instead of casting the Number reference. + // Casting `index` (java.lang.Number) to int compiles to a checkcast to Integer + // followed by unboxing, which throws ClassCastException for Byte/Short/Long. + return exprs[(int) idx - 1]; } } From 8a6d8a415a919045ec93a9b37c3722f02dff3335 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=83=A1=E7=90=B3=28HuLin=29?= Date: Fri, 7 Aug 2026 08:40:08 +0800 Subject: [PATCH 2/5] [FLINK-40338][table-planner] Cover codegen path with field-reference ELT tests Constant test cases are folded by ExpressionReducer during optimization, so they only cover the constant-folding path, not the codegen'd operator path that a real job hits. Extend onFieldsWithData with TINYINT/SMALLINT/ BIGINT fields and add field-reference test cases (f4/f5/f6) to cover the runtime path; keep one constant case so the reducer path stays covered. Test plan suggested by @SEPURI-SAI-KRISHNA and @raminqaf during review. Co-authored-by: SEPURI-SAI-KRISHNA --- .../functions/StringFunctionsITCase.java | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/functions/StringFunctionsITCase.java b/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/functions/StringFunctionsITCase.java index e1e6223c29dbb1..076eeb5ba5a331 100644 --- a/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/functions/StringFunctionsITCase.java +++ b/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/functions/StringFunctionsITCase.java @@ -149,12 +149,15 @@ private Stream bTrimTestCases() { private Stream eltTestCases() { return Stream.of( TestSetSpec.forFunction(BuiltInFunctionDefinitions.ELT) - .onFieldsWithData(null, null, null, new byte[] {1, 2, 3}) + .onFieldsWithData(null, null, null, new byte[] {1, 2, 3}, (byte) 2, (short) 2, 2L) .andDataTypes( DataTypes.INT(), DataTypes.STRING(), DataTypes.BYTES(), - DataTypes.BYTES()) + DataTypes.BYTES(), + DataTypes.TINYINT(), + DataTypes.SMALLINT(), + DataTypes.BIGINT()) // null input .testResult( $("f0").elt("a", "b"), "ELT(f0, 'a', 'b')", null, DataTypes.CHAR(1)) @@ -182,20 +185,27 @@ private Stream eltTestCases() { DataTypes.VARCHAR(5)) .testResult( lit(2).elt("a", "b"), "ELT(2, 'a', 'b')", "b", DataTypes.CHAR(1)) - // FLINK-40338: non-INT INTEGER_NUMERIC index must not throw ClassCastException + // FLINK-40338: non-INT INTEGER_NUMERIC index must not throw ClassCastException. + // Constant case covers the ExpressionReducer (constant-folding) path; + // field-reference cases below cover the codegen'd operator path. .testResult( lit(2).cast(DataTypes.TINYINT()).elt("scala", "java"), "ELT(CAST(2 AS TINYINT), 'scala', 'java')", "java", DataTypes.VARCHAR(5)) .testResult( - lit(2).cast(DataTypes.SMALLINT()).elt("scala", "java"), - "ELT(CAST(2 AS SMALLINT), 'scala', 'java')", + $("f4").elt("scala", "java"), + "ELT(f4, 'scala', 'java')", + "java", + DataTypes.VARCHAR(5)) + .testResult( + $("f5").elt("scala", "java"), + "ELT(f5, 'scala', 'java')", "java", DataTypes.VARCHAR(5)) .testResult( - lit(2).cast(DataTypes.BIGINT()).elt("scala", "java"), - "ELT(CAST(2 AS BIGINT), 'scala', 'java')", + $("f6").elt("scala", "java"), + "ELT(f6, 'scala', 'java')", "java", DataTypes.VARCHAR(5)) .testResult( From 6991d066a3cfd6374cdc06960f1c14f5f896cc6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=83=A1=E7=90=B3=28HuLin=29?= Date: Fri, 7 Aug 2026 09:00:57 +0800 Subject: [PATCH 3/5] [FLINK-40338][table-planner] Remove redundant comment per review nit Address raminqaf review: drop the inline comment block; the JIRA context is already carried by the PR title and commit messages. --- .../flink/table/planner/functions/StringFunctionsITCase.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/functions/StringFunctionsITCase.java b/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/functions/StringFunctionsITCase.java index 076eeb5ba5a331..df4fd91dcafe9b 100644 --- a/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/functions/StringFunctionsITCase.java +++ b/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/functions/StringFunctionsITCase.java @@ -185,9 +185,6 @@ private Stream eltTestCases() { DataTypes.VARCHAR(5)) .testResult( lit(2).elt("a", "b"), "ELT(2, 'a', 'b')", "b", DataTypes.CHAR(1)) - // FLINK-40338: non-INT INTEGER_NUMERIC index must not throw ClassCastException. - // Constant case covers the ExpressionReducer (constant-folding) path; - // field-reference cases below cover the codegen'd operator path. .testResult( lit(2).cast(DataTypes.TINYINT()).elt("scala", "java"), "ELT(CAST(2 AS TINYINT), 'scala', 'java')", From f5b2495c6dee6f6a968b0509580bae70d1f4dc8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=83=A1=E7=90=B3=28HuLin=29?= Date: Fri, 7 Aug 2026 10:21:46 +0800 Subject: [PATCH 4/5] [FLINK-40338][table-planner] Wrap onFieldsWithData under 100-col limit Spotless (google-java-format 1.24.0) enforces a 100-column AOSP limit; the onFieldsWithData line added in 8ee7dd53 was 106 chars and failed the spotless-check. Wrap as mvn spotless:apply would. Thanks to @SEPURI-SAI-KRISHNA for the heads-up. --- .../flink/table/planner/functions/StringFunctionsITCase.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/functions/StringFunctionsITCase.java b/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/functions/StringFunctionsITCase.java index df4fd91dcafe9b..4cb3d15368021f 100644 --- a/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/functions/StringFunctionsITCase.java +++ b/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/functions/StringFunctionsITCase.java @@ -149,7 +149,8 @@ private Stream bTrimTestCases() { private Stream eltTestCases() { return Stream.of( TestSetSpec.forFunction(BuiltInFunctionDefinitions.ELT) - .onFieldsWithData(null, null, null, new byte[] {1, 2, 3}, (byte) 2, (short) 2, 2L) + .onFieldsWithData( + null, null, null, new byte[] {1, 2, 3}, (byte) 2, (short) 2, 2L) .andDataTypes( DataTypes.INT(), DataTypes.STRING(), From e8302d448027106a0d5560feeb56c9832d9e9e94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=83=A1=E7=90=B3=28HuLin=29?= Date: Mon, 10 Aug 2026 19:53:27 +0800 Subject: [PATCH 5/5] [FLINK-40338][table-runtime] Drop redundant inline comment per review The root cause (casting java.lang.Number to int compiles to a checkcast to Integer + unboxing, throwing ClassCastException for Byte/Short/Long) lives in commit a1d7732's message, and the field-reference tests (f4/f5/f6) catch any revert of this line. The inline comment duplicated both, so remove it. --- .../flink/table/runtime/functions/scalar/EltFunction.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/flink-table/flink-table-runtime/src/main/java/org/apache/flink/table/runtime/functions/scalar/EltFunction.java b/flink-table/flink-table-runtime/src/main/java/org/apache/flink/table/runtime/functions/scalar/EltFunction.java index 9ad4ac7116dfff..e36b4b6c029076 100644 --- a/flink-table/flink-table-runtime/src/main/java/org/apache/flink/table/runtime/functions/scalar/EltFunction.java +++ b/flink-table/flink-table-runtime/src/main/java/org/apache/flink/table/runtime/functions/scalar/EltFunction.java @@ -40,9 +40,6 @@ public EltFunction(SpecializedContext context) { if (idx < 1 || idx > exprs.length) { return null; } - // Narrow the already-unboxed long instead of casting the Number reference. - // Casting `index` (java.lang.Number) to int compiles to a checkcast to Integer - // followed by unboxing, which throws ClassCastException for Byte/Short/Long. return exprs[(int) idx - 1]; } }