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

github-merge-queue[bot] pushed a commit to branch 
gh-readonly-queue/dev/pr-12580-c6185fb3c3263f6b20fd974d653098181d4b4344
in repository https://gitbox.apache.org/repos/asf/seatunnel.git

commit 5598ed7f3a88602032a18b617cb21c688aacf965
Author: hutiefang76 <[email protected]>
AuthorDate: Thu Oct 29 10:37:32 2026 +0000

    [Fix][Transform-V2] Preserve input row kind when extracting CDC metadata 
(#12580)
---
 docs/en/transforms/rowkind-extractor.md            |   2 +-
 docs/zh/transforms/rowkind-extractor.md            |   2 +-
 .../transform/TestRowKindExtractorTransformIT.java |   7 +
 .../test/resources/rowkind_extractor_fanout.conf   | 141 +++++++++++++++++++++
 .../rowkind/RowKindExtractorTransform.java         |   2 +-
 .../rowkind/RowKindExtractorTransformTest.java     |  40 ++++++
 6 files changed, 191 insertions(+), 3 deletions(-)

diff --git a/docs/en/transforms/rowkind-extractor.md 
b/docs/en/transforms/rowkind-extractor.md
index 025284db61..40714bf20d 100644
--- a/docs/en/transforms/rowkind-extractor.md
+++ b/docs/en/transforms/rowkind-extractor.md
@@ -4,7 +4,7 @@
 
 ## Description
 
-The RowKindExtractor transform plugin is used to convert CDC (Change Data 
Capture) data streams into Append-Only mode while extracting the original 
RowKind information as a new field.
+The RowKindExtractor transform plugin is used to convert CDC (Change Data 
Capture) data streams into Append-Only mode while extracting the original 
RowKind information as a new field. Only the branch handled by this plugin 
becomes INSERT, while other branches keep their original CDC RowKind.
 
 **Core Features:**
 - Converts all data rows' RowKind to `+I` (INSERT), achieving Append-Only mode
diff --git a/docs/zh/transforms/rowkind-extractor.md 
b/docs/zh/transforms/rowkind-extractor.md
index c0622f03e2..04284f68ab 100644
--- a/docs/zh/transforms/rowkind-extractor.md
+++ b/docs/zh/transforms/rowkind-extractor.md
@@ -4,7 +4,7 @@
 
 ## 描述
 
-RowKindExtractor 转换插件用于将 CDC(Change Data Capture)数据流改写为 
Append-Only(仅追加)模式,同时把原始的 RowKind 信息提取为一个新的字段。
+RowKindExtractor 转换插件用于将 CDC(Change Data Capture)数据流改写为 
Append-Only(仅追加)模式,同时把原始的 RowKind 信息提取为一个新的字段。只有该插件处理的分支会变为 INSERT,其他分支仍保留原始的 
CDC RowKind。
 
 **核心功能:**
 - 将所有数据行的 RowKind 统一改为 `+I`(INSERT),实现 Append-Only 模式
diff --git 
a/seatunnel-e2e/seatunnel-transforms-v2-e2e/seatunnel-transforms-v2-e2e-part-1/src/test/java/org/apache/seatunnel/e2e/transform/TestRowKindExtractorTransformIT.java
 
b/seatunnel-e2e/seatunnel-transforms-v2-e2e/seatunnel-transforms-v2-e2e-part-1/src/test/java/org/apache/seatunnel/e2e/transform/TestRowKindExtractorTransformIT.java
index a82ff2fb37..7302a10b00 100644
--- 
a/seatunnel-e2e/seatunnel-transforms-v2-e2e/seatunnel-transforms-v2-e2e-part-1/src/test/java/org/apache/seatunnel/e2e/transform/TestRowKindExtractorTransformIT.java
+++ 
b/seatunnel-e2e/seatunnel-transforms-v2-e2e/seatunnel-transforms-v2-e2e-part-1/src/test/java/org/apache/seatunnel/e2e/transform/TestRowKindExtractorTransformIT.java
@@ -45,4 +45,11 @@ public class TestRowKindExtractorTransformIT extends 
TestSuiteBase {
                 
container.executeJob("/rowkind_extractor_transform_case1_multi_table.conf");
         Assertions.assertEquals(0, execResult.getExitCode());
     }
+
+    @TestTemplate
+    public void testOriginalRowKindPreservedAcrossBranches(TestContainer 
container)
+            throws IOException, InterruptedException {
+        Container.ExecResult execResult = 
container.executeJob("/rowkind_extractor_fanout.conf");
+        Assertions.assertEquals(0, execResult.getExitCode());
+    }
 }
diff --git 
a/seatunnel-e2e/seatunnel-transforms-v2-e2e/seatunnel-transforms-v2-e2e-part-1/src/test/resources/rowkind_extractor_fanout.conf
 
b/seatunnel-e2e/seatunnel-transforms-v2-e2e/seatunnel-transforms-v2-e2e-part-1/src/test/resources/rowkind_extractor_fanout.conf
new file mode 100644
index 0000000000..99709047e4
--- /dev/null
+++ 
b/seatunnel-e2e/seatunnel-transforms-v2-e2e/seatunnel-transforms-v2-e2e-part-1/src/test/resources/rowkind_extractor_fanout.conf
@@ -0,0 +1,141 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+env {
+  parallelism = 1
+  job.mode = "BATCH"
+}
+
+source {
+  FakeSource {
+    plugin_output = "source1"
+    row.num = 1
+    schema = {
+      table = "test.events"
+      fields {
+        pk_id = bigint
+        name = string
+      }
+      primaryKey {
+        name = "pk_id"
+        columnNames = [pk_id]
+      }
+    }
+    rows = [
+      {
+        kind = DELETE
+        fields = [1, "A"]
+      }
+    ]
+  }
+}
+
+transform {
+  RowKindExtractor {
+    plugin_input = "source1"
+    plugin_output = "extracted_a"
+    custom_field_name = "original_kind"
+    transform_type = FULL
+  }
+
+  TableRename {
+    plugin_input = "extracted_a"
+    plugin_output = "branch_a"
+    prefix = "a_"
+  }
+
+  RowKindExtractor {
+    plugin_input = "source1"
+    plugin_output = "extracted_b"
+    custom_field_name = "original_kind"
+    transform_type = FULL
+  }
+
+  TableRename {
+    plugin_input = "extracted_b"
+    plugin_output = "branch_b"
+    prefix = "b_"
+  }
+}
+
+sink {
+  Assert {
+    plugin_input = "branch_a"
+    rules = {
+      tables_configs = [
+        {
+          table_path = "test.a_events"
+          row_rules = [
+            {
+              rule_type = MAX_ROW
+              rule_value = 1
+            },
+            {
+              rule_type = MIN_ROW
+              rule_value = 1
+            }
+          ]
+          field_rules = [
+            {
+              field_name = original_kind
+              field_type = string
+              field_value = [
+                {
+                  rule_type = NOT_NULL
+                  equals_to = "DELETE"
+                }
+              ]
+            }
+          ]
+        }
+      ]
+    }
+  }
+
+  Assert {
+    plugin_input = "branch_b"
+    rules = {
+      tables_configs = [
+        {
+          table_path = "test.b_events"
+          row_rules = [
+            {
+              rule_type = MAX_ROW
+              rule_value = 1
+            },
+            {
+              rule_type = MIN_ROW
+              rule_value = 1
+            }
+          ]
+          field_rules = [
+            {
+              field_name = original_kind
+              field_type = string
+              field_value = [
+                {
+                  rule_type = NOT_NULL
+                  equals_to = "DELETE"
+                }
+              ]
+            }
+          ]
+        }
+      ]
+    }
+  }
+}
diff --git 
a/seatunnel-transforms-v2/src/main/java/org/apache/seatunnel/transform/rowkind/RowKindExtractorTransform.java
 
b/seatunnel-transforms-v2/src/main/java/org/apache/seatunnel/transform/rowkind/RowKindExtractorTransform.java
index 58b80c50c1..ce9d98384a 100644
--- 
a/seatunnel-transforms-v2/src/main/java/org/apache/seatunnel/transform/rowkind/RowKindExtractorTransform.java
+++ 
b/seatunnel-transforms-v2/src/main/java/org/apache/seatunnel/transform/rowkind/RowKindExtractorTransform.java
@@ -54,8 +54,8 @@ public class RowKindExtractorTransform extends 
SingleFieldOutputTransform {
     @Override
     protected SeaTunnelRow transformRow(SeaTunnelRow inputRow) {
         Object fieldValue = getOutputFieldValue(new 
SeaTunnelRowAccessor(inputRow));
-        inputRow.setRowKind(RowKind.INSERT);
         SeaTunnelRow outputRow = getRowContainerGenerator().apply(inputRow);
+        outputRow.setRowKind(RowKind.INSERT);
         outputRow.setField(getFieldIndex(), fieldValue);
         return outputRow;
     }
diff --git 
a/seatunnel-transforms-v2/src/test/java/org/apache/seatunnel/transform/rowkind/RowKindExtractorTransformTest.java
 
b/seatunnel-transforms-v2/src/test/java/org/apache/seatunnel/transform/rowkind/RowKindExtractorTransformTest.java
index b958dab7d0..0aa143dac4 100644
--- 
a/seatunnel-transforms-v2/src/test/java/org/apache/seatunnel/transform/rowkind/RowKindExtractorTransformTest.java
+++ 
b/seatunnel-transforms-v2/src/test/java/org/apache/seatunnel/transform/rowkind/RowKindExtractorTransformTest.java
@@ -150,4 +150,44 @@ class RowKindExtractorTransformTest {
                 "SeaTunnelRow{tableId=, kind=+I, fields=[value1, 1, 
896657703886127105, 3.1415916, 3.14, DELETE]}",
                 rowKindExtractorTransform.transformRow(deleteRow).toString());
     }
+
+    @Test
+    void testOriginalRowKindIsPreservedForSiblingBranches() {
+        RowKind[] allKinds = RowKind.values();
+        for (RowKindExtractorTransformType transformType : 
RowKindExtractorTransformType.values()) {
+            HashMap<String, Object> conf = new HashMap<>();
+            conf.put("transform_type", transformType.name());
+            RowKindExtractorTransform rowKindExtractorTransform =
+                    new 
RowKindExtractorTransform(ReadonlyConfig.fromMap(conf), catalogTable);
+            rowKindExtractorTransform.initRowContainerGenerator();
+            for (RowKind originalKind : allKinds) {
+                SeaTunnelRow originalRow = inputRow.copy();
+                originalRow.setRowKind(originalKind);
+                originalRow.setTableId("original");
+                HashMap<String, Object> options = new HashMap<>();
+                options.put("marker", "kept");
+                originalRow.setOptions(options);
+
+                SeaTunnelRow outputRow = 
rowKindExtractorTransform.map(originalRow);
+
+                Assertions.assertEquals(RowKind.INSERT, 
outputRow.getRowKind());
+                String expectedKind =
+                        transformType == RowKindExtractorTransformType.SHORT
+                                ? originalKind.shortString()
+                                : originalKind.name();
+                Assertions.assertEquals(
+                        expectedKind,
+                        
outputRow.getField(rowKindExtractorTransform.getFieldIndex()));
+                Assertions.assertEquals(values.length + 1, 
outputRow.getArity());
+                Assertions.assertEquals(values[0], outputRow.getField(0));
+                Assertions.assertEquals("original", outputRow.getTableId());
+                Assertions.assertEquals("kept", 
outputRow.getOptionsOrNull().get("marker"));
+
+                Assertions.assertEquals(originalKind, 
originalRow.getRowKind());
+                Assertions.assertArrayEquals(values, originalRow.getFields());
+                Assertions.assertEquals("original", originalRow.getTableId());
+                Assertions.assertEquals("kept", 
originalRow.getOptionsOrNull().get("marker"));
+            }
+        }
+    }
 }

Reply via email to