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

danny0405 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/hudi.git


The following commit(s) were added to refs/heads/master by this push:
     new 8f5f9ba6f92c feat(flink): enable Sink V2 on Flink 1.18 via 
WithPreWriteTopology (#19982)
8f5f9ba6f92c is described below

commit 8f5f9ba6f92c0cd02b175d846b98dc2f4dea518f
Author: ericyuan915 <[email protected]>
AuthorDate: Thu Sep 17 01:44:53 2026 -0700

    feat(flink): enable Sink V2 on Flink 1.18 via WithPreWriteTopology (#19982)
    
    HoodieSink builds its whole write pipeline in addPreWriteTopology, which 
Flink
    only invokes for a sink implementing the pre-write-topology interface of the
    running version. The Flink 1.18 SupportsPreWriteTopologyAdapter extended
    nothing -- it existed only to keep the shared hudi-flink module compiling 
-- so
    on 1.18 the hook was never called and PipelinesV2.sink fenced Sink V2 off
    behind a runtime version check.
    
    Flink 1.18 does have the capability, named WithPreWriteTopology, which
    SinkTransformationTranslator dispatches on there. Flink 1.19 split the
    identical method out into SupportsPreWriteTopology and deprecated
    WithPreWriteTopology, so only the type name and its Sink supertype differ.
    
    Extend the real interface in the 1.18 adapter and inherit 
addPreWriteTopology
    instead of redeclaring it. HoodieSink then reaches Sink<RowData> through 
both
    SinkAdapter and WithPreWriteTopology, which is legal: same type argument, 
and
    SinkAdapter's createWriter(InitContext) default overrides the abstract
    declaration inherited from Sink. Drop the version guard accordingly --
    hudi-flink1.18.x is the oldest Flink profile on master, so it is 
unreachable.
    
    Add TestPipelinesV2#testSinkLetsFlinkExpandTheHudiWritePipeline, which runs
    PipelinesV2.sink through Flink's own SinkTransformationTranslator by 
generating
    the StreamGraph, then asserts the Hudi write operator is in the graph with 
the
    sink uid prefixed to its own. It names no version-specific interface, so it
    covers the pre-write hook on every Flink profile.
    
    closes #19966
---
 .../org/apache/hudi/sink/v2/utils/PipelinesV2.java |  5 ----
 .../apache/hudi/sink/v2/utils/TestPipelinesV2.java | 28 ++++++++++++++++++++++
 .../adapter/SupportsPreWriteTopologyAdapter.java   | 21 +++++++---------
 3 files changed, 36 insertions(+), 18 deletions(-)

diff --git 
a/hudi-flink-datasource/hudi-flink/src/main/java/org/apache/hudi/sink/v2/utils/PipelinesV2.java
 
b/hudi-flink-datasource/hudi-flink/src/main/java/org/apache/hudi/sink/v2/utils/PipelinesV2.java
index 5cf19bfd958a..60b3d875dbd1 100644
--- 
a/hudi-flink-datasource/hudi-flink/src/main/java/org/apache/hudi/sink/v2/utils/PipelinesV2.java
+++ 
b/hudi-flink-datasource/hudi-flink/src/main/java/org/apache/hudi/sink/v2/utils/PipelinesV2.java
@@ -19,7 +19,6 @@
 package org.apache.hudi.sink.v2.utils;
 
 import org.apache.hudi.client.model.HoodieFlinkInternalRow;
-import org.apache.hudi.common.util.ValidationUtils;
 import org.apache.hudi.configuration.FlinkOptions;
 import org.apache.hudi.configuration.OptionsResolver;
 import org.apache.hudi.exception.HoodieException;
@@ -37,7 +36,6 @@ import org.apache.hudi.sink.v2.HoodieSink;
 import org.apache.hudi.sink.v2.clustering.ClusteringCommitSinkV2;
 import org.apache.hudi.sink.v2.compact.CompactionCommitSinkV2;
 
-import org.apache.flink.FlinkVersion;
 import org.apache.flink.api.common.typeinfo.TypeInformation;
 import org.apache.flink.configuration.Configuration;
 import org.apache.flink.streaming.api.datastream.DataStream;
@@ -55,7 +53,6 @@ import static org.apache.hudi.sink.utils.Pipelines.opUID;
 public class PipelinesV2 {
 
   private static final String SINK_V2_NAME = "sink_v2";
-  private static final String FLINK_1_18_VERSION = "1.18";
 
   /**
    * Construct a write pipeline based on {@link HoodieSink}, which is 
implemented
@@ -75,8 +72,6 @@ public class PipelinesV2 {
       RowType rowType,
       boolean overwrite,
       boolean isBounded) {
-    
ValidationUtils.checkArgument(FlinkVersion.current().toString().compareTo(FLINK_1_18_VERSION)
 > 0,
-        "Hudi sink v2 is not supported with Flink version: " + 
FlinkVersion.current());
     HoodieSink hoodieSink = new HoodieSink(conf, rowType, overwrite, 
isBounded);
     return dataStream.sinkTo(hoodieSink)
         .setParallelism(getParallelismForSinkV2(conf))
diff --git 
a/hudi-flink-datasource/hudi-flink/src/test/java/org/apache/hudi/sink/v2/utils/TestPipelinesV2.java
 
b/hudi-flink-datasource/hudi-flink/src/test/java/org/apache/hudi/sink/v2/utils/TestPipelinesV2.java
index a56cc694cc91..5ade4b2b1c21 100644
--- 
a/hudi-flink-datasource/hudi-flink/src/test/java/org/apache/hudi/sink/v2/utils/TestPipelinesV2.java
+++ 
b/hudi-flink-datasource/hudi-flink/src/test/java/org/apache/hudi/sink/v2/utils/TestPipelinesV2.java
@@ -31,6 +31,7 @@ import org.apache.flink.configuration.Configuration;
 import org.apache.flink.streaming.api.datastream.DataStream;
 import org.apache.flink.streaming.api.datastream.DataStreamSink;
 import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
+import org.apache.flink.streaming.api.graph.StreamNode;
 import org.apache.flink.streaming.api.transformations.PartitionTransformation;
 import org.apache.flink.streaming.runtime.partitioner.CustomPartitionerWrapper;
 import org.apache.flink.streaming.runtime.partitioner.StreamPartitioner;
@@ -83,6 +84,33 @@ class TestPipelinesV2 {
         .matches("uid_sink_v2(?:_\\d+)?_sink_v2_test"));
   }
 
+  @Test
+  void testSinkLetsFlinkExpandTheHudiWritePipeline() {
+    conf.set(FlinkOptions.OPERATION, "insert");
+    conf.set(FlinkOptions.TABLE_NAME, "sink_v2_test");
+    conf.set(FlinkOptions.WRITE_TASKS, 4);
+
+    DataStreamSink<RowData> sink = PipelinesV2.sink(
+        input, conf, TestConfigurations.ROW_TYPE, false, true);
+    String sinkUid = sink.getTransformation().getUid();
+
+    // HoodieSink builds the whole write pipeline in addPreWriteTopology, and 
Flink only calls that
+    // for a sink implementing the pre-write topology interface of the running 
version
+    // (WithPreWriteTopology on 1.18, SupportsPreWriteTopology since 1.19). 
Without it the job
+    // keeps nothing but the no-op sink writer and silently writes no data.
+    List<StreamNode> writeNodes = 
input.getExecutionEnvironment().getStreamGraph()
+        .getStreamNodes().stream()
+        .filter(node -> node.getOperatorName().contains("hoodie_append_write"))
+        .collect(Collectors.toList());
+
+    assertEquals(1, writeNodes.size(), "Flink did not expand the pre-write 
topology of the sink");
+    // the sink uid is prepended to the uid of every operator the sink expands 
into, which is what
+    // keeps the expanded pipeline addressable across state restores.
+    assertTrue(writeNodes.get(0).getTransformationUID().startsWith(sinkUid + 
": "),
+        "Expected the write operator uid to be prefixed with the sink uid " + 
sinkUid
+            + " but got " + writeNodes.get(0).getTransformationUID());
+  }
+
   @Test
   void testServiceTopologiesUseExpectedSingletonOperatorsAndPartitioners() 
throws Exception {
     conf.set(FlinkOptions.CLUSTERING_TASKS, 3);
diff --git 
a/hudi-flink-datasource/hudi-flink1.18.x/src/main/java/org/apache/hudi/adapter/SupportsPreWriteTopologyAdapter.java
 
b/hudi-flink-datasource/hudi-flink1.18.x/src/main/java/org/apache/hudi/adapter/SupportsPreWriteTopologyAdapter.java
index 45e552ed2231..f6cfe0aa83a4 100644
--- 
a/hudi-flink-datasource/hudi-flink1.18.x/src/main/java/org/apache/hudi/adapter/SupportsPreWriteTopologyAdapter.java
+++ 
b/hudi-flink-datasource/hudi-flink1.18.x/src/main/java/org/apache/hudi/adapter/SupportsPreWriteTopologyAdapter.java
@@ -18,20 +18,15 @@
 
 package org.apache.hudi.adapter;
 
-import org.apache.flink.streaming.api.datastream.DataStream;
+import org.apache.flink.streaming.api.connector.sink2.WithPreWriteTopology;
 
 /**
- * {@code SupportsPreWriteTopology} is introduced for Sink V2 since Flink 1.19,
- * We add the adapter here to just make the compilation successful for earlier
- * Flink versions (< 1.19).
+ * Adapter clazz for {@code SupportsPreWriteTopology}.
+ *
+ * <p>{@code SupportsPreWriteTopology} only exists since Flink 1.19, where it 
was split out of
+ * {@link WithPreWriteTopology}; both declare the same {@code 
addPreWriteTopology} method, and
+ * Flink 1.18 dispatches on {@link WithPreWriteTopology} in {@code 
SinkTransformationTranslator}.
+ * Extending it here keeps the pre-write topology hook wired for Flink 1.18 as 
well.
  */
-public interface SupportsPreWriteTopologyAdapter<InputT> {
-  /**
-   * Adds an arbitrary topology before the writer. The topology may be used to 
repartition the
-   * data.
-   *
-   * @param inputDataStream the stream of input records.
-   * @return the custom topology before {@code SinkWriter}.
-   */
-  DataStream<InputT> addPreWriteTopology(DataStream<InputT> inputDataStream);
+public interface SupportsPreWriteTopologyAdapter<InputT> extends 
WithPreWriteTopology<InputT> {
 }

Reply via email to