This is an automated email from the ASF dual-hosted git repository.

twalthr pushed a commit to branch release-2.3
in repository https://gitbox.apache.org/repos/asf/flink.git


The following commit(s) were added to refs/heads/release-2.3 by this push:
     new 9d49efb4a34 [FLINK-39679][table-planner] Fix implicit casts for PTF 
calls
9d49efb4a34 is described below

commit 9d49efb4a34927315b2c6c21fb346020c80bb199
Author: Timo Walther <[email protected]>
AuthorDate: Tue Jun 16 16:26:48 2026 +0200

    [FLINK-39679][table-planner] Fix implicit casts for PTF calls
    
    This closes #28460.
---
 .../functions/casting/RowToRowCastRule.java        |  3 ++
 .../codegen/ProcessTableRunnerGenerator.scala      | 56 ++++++++++++++++++----
 .../stream/ProcessTableFunctionSemanticTests.java  |  3 +-
 .../stream/ProcessTableFunctionTestPrograms.java   | 21 ++++++++
 .../exec/stream/ProcessTableFunctionTestUtils.java | 25 ++++++++++
 5 files changed, 98 insertions(+), 10 deletions(-)

diff --git 
a/flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/functions/casting/RowToRowCastRule.java
 
b/flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/functions/casting/RowToRowCastRule.java
index 119a3b4a6b9..2b0d05be177 100644
--- 
a/flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/functions/casting/RowToRowCastRule.java
+++ 
b/flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/functions/casting/RowToRowCastRule.java
@@ -40,6 +40,9 @@ import static 
org.apache.flink.table.planner.functions.casting.CastRuleUtils.met
 /**
  * {@link LogicalTypeRoot#ROW} and {@link LogicalTypeRoot#STRUCTURED_TYPE} to 
{@link
  * LogicalTypeRoot#ROW} and {@link LogicalTypeRoot#STRUCTURED_TYPE} cast rule.
+ *
+ * <p>This rule is used for arbitrary nested ROW/STRUCTURED casts where 
carrying RowKind would be
+ * semantically wrong. This should be rather done on the caller side.
  */
 class RowToRowCastRule extends AbstractNullAwareCodeGeneratorCastRule<RowData, 
RowData> {
 
diff --git 
a/flink-table/flink-table-planner/src/main/scala/org/apache/flink/table/planner/codegen/ProcessTableRunnerGenerator.scala
 
b/flink-table/flink-table-planner/src/main/scala/org/apache/flink/table/planner/codegen/ProcessTableRunnerGenerator.scala
index 933ea65a5c4..c6b6534be45 100644
--- 
a/flink-table/flink-table-planner/src/main/scala/org/apache/flink/table/planner/codegen/ProcessTableRunnerGenerator.scala
+++ 
b/flink-table/flink-table-planner/src/main/scala/org/apache/flink/table/planner/codegen/ProcessTableRunnerGenerator.scala
@@ -30,7 +30,7 @@ import 
org.apache.flink.table.planner.calcite.{FlinkTypeFactory, RexTableArgCall
 import org.apache.flink.table.planner.codegen.CodeGenUtils._
 import org.apache.flink.table.planner.codegen.GeneratedExpression.{NEVER_NULL, 
NO_CODE}
 import org.apache.flink.table.planner.codegen.Indenter.toISC
-import org.apache.flink.table.planner.codegen.calls.BridgingFunctionGenUtil
+import org.apache.flink.table.planner.codegen.calls.{BridgingFunctionGenUtil, 
ScalarOperatorGens}
 import 
org.apache.flink.table.planner.codegen.calls.BridgingFunctionGenUtil.{verifyFunctionAwareOutputType,
 DefaultExpressionEvaluatorFactory}
 import org.apache.flink.table.planner.delegation.PlannerBase
 import org.apache.flink.table.planner.functions.bridging.BridgingSqlFunction
@@ -45,7 +45,7 @@ import org.apache.flink.table.types.extraction.ExtractionUtils
 import org.apache.flink.table.types.inference.TypeInferenceUtil
 import org.apache.flink.table.types.inference.TypeInferenceUtil.StateInfo
 import org.apache.flink.table.types.logical.LogicalType
-import org.apache.flink.table.types.logical.utils.LogicalTypeChecks
+import org.apache.flink.table.types.logical.utils.{LogicalTypeCasts, 
LogicalTypeChecks}
 import org.apache.flink.types.Row
 
 import org.apache.calcite.rex.{RexCall, RexNode}
@@ -321,24 +321,61 @@ object ProcessTableRunnerGenerator {
   private def generateEvalOperands(
       ctx: CodeGeneratorContext,
       call: RexCall,
+      enrichedArgumentDataTypes: Seq[DataType],
       inputIndexTerm: String,
       inputRowTerm: String): Seq[GeneratedExpression] = {
     val generator = new ExprCodeGenerator(ctx, false)
+
+    // Generate (potentially cast) operands
     call.getOperands.asScala
+      .zip(enrichedArgumentDataTypes)
       .map {
-        case tableArgCall: RexTableArgCall =>
+        case (tableArgCall: RexTableArgCall, targetDataType: DataType) =>
           val inputIndex = tableArgCall.getInputIndex
-          val tableType = 
FlinkTypeFactory.toLogicalType(call.getType).copy(true)
-          GeneratedExpression(
+          val tableType = 
FlinkTypeFactory.toLogicalType(tableArgCall.getType).copy(true)
+          val tableArgAccess = GeneratedExpression(
             s"$inputRowTerm",
-            s"$inputIndexTerm != $inputIndex",
+            s"($inputIndexTerm != $inputIndex)",
             NO_CODE,
             tableType)
-        case rexNode: RexNode =>
-          generator.generateExpression(rexNode)
+          val (castTableArg, hasCast) =
+            castOperand(ctx, tableArgAccess, targetDataType.getLogicalType)
+          val tableArgWithKind = if (hasCast) {
+            // Casting a table argument to the target type (potentially) 
produces a fresh row that
+            // defaults to RowKind.INSERT. We forward the table's underlying 
changelog
+            // information to the eval() here.
+            val rowKindCode =
+              s"""
+                 |if (!${castTableArg.nullTerm}) {
+                 |  
${castTableArg.resultTerm}.setRowKind($inputRowTerm.getRowKind());
+                 |}
+                 |""".stripMargin
+            castTableArg.copy(code = s"${castTableArg.code}\n$rowKindCode")
+          } else {
+            tableArgAccess
+          }
+          tableArgWithKind
+
+        case (rexNode: RexNode, targetDataType: DataType) =>
+          val scalarArg = generator.generateExpression(rexNode)
+          val (castScalarArg, _) = castOperand(ctx, scalarArg, 
targetDataType.getLogicalType)
+          castScalarArg
       }
   }
 
+  /** Applies a cast if necessary. Also returns whether a cast was necessary. 
*/
+  private def castOperand(
+      ctx: CodeGeneratorContext,
+      operand: GeneratedExpression,
+      targetType: LogicalType): (GeneratedExpression, Boolean) = {
+    if (LogicalTypeCasts.supportsAvoidingCast(operand.resultType, targetType)) 
{
+      return (operand, false)
+    }
+    val cast =
+      ScalarOperatorGens.generateCast(ctx, operand, targetType, nullOnFailure 
= false)
+    (cast, true)
+  }
+
   private def verifyMethodImplementation(
       methodName: String,
       contextClass: Class[_],
@@ -388,7 +425,8 @@ object ProcessTableRunnerGenerator {
     val inputIndexTerm = "inputIndex"
     val inputRowTerm = "inputRow"
     val collectorTerm = "evalCollector"
-    val callOperands = generateEvalOperands(ctx, call, inputIndexTerm, 
inputRowTerm)
+    val callOperands =
+      generateEvalOperands(ctx, call, enrichedArgumentDataTypes, 
inputIndexTerm, inputRowTerm)
     val externalCallOperands =
       BridgingFunctionGenUtil.prepareExternalOperands(ctx, callOperands, 
enrichedArgumentDataTypes)
     val allExternalOperands = externalStateOperands ++ externalCallOperands
diff --git 
a/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/plan/nodes/exec/stream/ProcessTableFunctionSemanticTests.java
 
b/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/plan/nodes/exec/stream/ProcessTableFunctionSemanticTests.java
index aa547037a13..e2b5e76167a 100644
--- 
a/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/plan/nodes/exec/stream/ProcessTableFunctionSemanticTests.java
+++ 
b/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/plan/nodes/exec/stream/ProcessTableFunctionSemanticTests.java
@@ -104,6 +104,7 @@ public class ProcessTableFunctionSemanticTests extends 
SemanticTestBase {
                 ProcessTableFunctionTestPrograms.PROCESS_UPDATING_MULTI_INPUT,
                 ProcessTableFunctionTestPrograms.PROCESS_ORDER_BY,
                 ProcessTableFunctionTestPrograms.PROCESS_MULTI_INPUT_ORDER_BY,
-                ProcessTableFunctionTestPrograms.PROCESS_ORDER_BY_TABLE_API);
+                ProcessTableFunctionTestPrograms.PROCESS_ORDER_BY_TABLE_API,
+                ProcessTableFunctionTestPrograms.PROCESS_IMPLICIT_CASTS);
     }
 }
diff --git 
a/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/plan/nodes/exec/stream/ProcessTableFunctionTestPrograms.java
 
b/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/plan/nodes/exec/stream/ProcessTableFunctionTestPrograms.java
index 062de5c2416..887eab68f45 100644
--- 
a/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/plan/nodes/exec/stream/ProcessTableFunctionTestPrograms.java
+++ 
b/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/plan/nodes/exec/stream/ProcessTableFunctionTestPrograms.java
@@ -28,6 +28,7 @@ import 
org.apache.flink.table.planner.plan.nodes.exec.stream.ProcessTableFunctio
 import 
org.apache.flink.table.planner.plan.nodes.exec.stream.ProcessTableFunctionTestUtils.ContextFunction;
 import 
org.apache.flink.table.planner.plan.nodes.exec.stream.ProcessTableFunctionTestUtils.DescriptorFunction;
 import 
org.apache.flink.table.planner.plan.nodes.exec.stream.ProcessTableFunctionTestUtils.EmptyArgFunction;
+import 
org.apache.flink.table.planner.plan.nodes.exec.stream.ProcessTableFunctionTestUtils.ImplicitCastingFunction;
 import 
org.apache.flink.table.planner.plan.nodes.exec.stream.ProcessTableFunctionTestUtils.IntervalDayArgFunction;
 import 
org.apache.flink.table.planner.plan.nodes.exec.stream.ProcessTableFunctionTestUtils.IntervalYearArgFunction;
 import 
org.apache.flink.table.planner.plan.nodes.exec.stream.ProcessTableFunctionTestUtils.InvalidPassThroughTimersFunction;
@@ -1920,4 +1921,24 @@ public class ProcessTableFunctionTestPrograms {
                                     + "in2 => TABLE t_large PARTITION BY name 
ORDER BY (ts ASC, score DESC), "
                                     + "on_time => DESCRIPTOR(ts))")
                     .build();
+
+    public static final TableTestProgram PROCESS_IMPLICIT_CASTS =
+            TableTestProgram.of(
+                            "process-implicit-casts",
+                            "implicits casts for both scalar and table args")
+                    .setupTemporarySystemFunction("f", 
ImplicitCastingFunction.class)
+                    .setupSql(
+                            "CREATE VIEW v AS SELECT 'Bob', 12, (42, TIMESTAMP 
'2020-01-01 12:12:12')")
+                    .setupTableSink(
+                            SinkTestStep.newBuilder("sink")
+                                    .addSchema(BASE_SINK_SCHEMA)
+                                    .consumedValues(
+                                            "+I[{CastingPojo(s='Bob', b=12, 
r=+I[42, 2020-01-01T12:12:12]), 42}]")
+                                    .build())
+                    // The function expects wider types (BIGINT and 
TIMESTAMP(6)),
+                    // implicit casts are added to fix the mismatch between 
INT vs. BIGINT and
+                    // TIMESTAMP(0) vs. TIMESTAMP(6).
+                    // Also in constructed types: ROW (table input) vs. 
STRUCTURED (expected).
+                    .runSql("INSERT INTO sink SELECT * FROM f(p => TABLE v, b 
=> 42)")
+                    .build();
 }
diff --git 
a/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/plan/nodes/exec/stream/ProcessTableFunctionTestUtils.java
 
b/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/plan/nodes/exec/stream/ProcessTableFunctionTestUtils.java
index 495293c9244..8a75cf35416 100644
--- 
a/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/plan/nodes/exec/stream/ProcessTableFunctionTestUtils.java
+++ 
b/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/plan/nodes/exec/stream/ProcessTableFunctionTestUtils.java
@@ -1148,6 +1148,13 @@ public class ProcessTableFunctionTestUtils {
         }
     }
 
+    /** Forces implicit casts on the input. */
+    public static class ImplicitCastingFunction extends 
AppendProcessTableFunctionBase {
+        public void eval(@ArgumentHint(ROW_SEMANTIC_TABLE) CastingPojo p, long 
b) {
+            collectObjects(p, b);
+        }
+    }
+
     // 
--------------------------------------------------------------------------------------------
     // Helpers
     // 
--------------------------------------------------------------------------------------------
@@ -1207,4 +1214,22 @@ public class ProcessTableFunctionTestUtils {
     private static String toModeSummary(ChangelogMode mode) {
         return MODE_SUMMARY.get(mode.toString());
     }
+
+    /** POJO expecting BIGINT. */
+    public static class CastingPojo {
+        public String s;
+        public long b;
+        public @DataTypeHint("ROW<b BIGINT, t TIMESTAMP(6)>") Row r;
+
+        public CastingPojo(String s, long b, Row r) {
+            this.s = s;
+            this.b = b;
+            this.r = r;
+        }
+
+        @Override
+        public String toString() {
+            return String.format("CastingPojo(s='%s', b=%s, r=%s)", s, b, r);
+        }
+    }
 }

Reply via email to