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

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


The following commit(s) were added to refs/heads/dev by this push:
     new eceeb0abad [Fix][Connector-V2][Jdbc] Write the net effect of each key 
when reducing the sink batch (#12808)
eceeb0abad is described below

commit eceeb0abad2ef598330da06fdfcb1f2450c8bd6b
Author: Ricky Makhija <[email protected]>
AuthorDate: Mon Oct 26 05:23:26 2026 +0000

    [Fix][Connector-V2][Jdbc] Write the net effect of each key when reducing 
the sink batch (#12808)
---
 .../BufferReducedBatchStatementExecutor.java       | 24 +++++++++++--
 .../BufferReducedBatchStatementExecutorTest.java   | 40 ++++++++++++++++++++++
 2 files changed, 61 insertions(+), 3 deletions(-)

diff --git 
a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/executor/BufferReducedBatchStatementExecutor.java
 
b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/executor/BufferReducedBatchStatementExecutor.java
index 845b114475..3f0bff4735 100644
--- 
a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/executor/BufferReducedBatchStatementExecutor.java
+++ 
b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/executor/BufferReducedBatchStatementExecutor.java
@@ -29,7 +29,9 @@ import lombok.RequiredArgsConstructor;
 
 import java.sql.Connection;
 import java.sql.SQLException;
+import java.util.HashMap;
 import java.util.LinkedHashMap;
+import java.util.Map;
 import java.util.function.Function;
 
 @RequiredArgsConstructor
@@ -48,6 +50,11 @@ public class BufferReducedBatchStatementExecutor
     @NonNull private final LinkedHashMap<Object, Triple<Boolean, SeaTunnelRow, 
SeaTunnelRow>> buffer =
             new LinkedHashMap<>();
 
+    // Whether each buffered key existed before this batch, judged from its 
first change. Only the
+    // last change per key is kept, so an INSERT followed by an UPDATE must 
still be written as an
+    // INSERT, and a DELETE followed by an INSERT as an UPDATE.
+    @NonNull private final Map<Object, Boolean> existedBeforeBatch = new 
HashMap<>();
+
     private boolean flushFailed;
 
     @Override
@@ -64,9 +71,11 @@ public class BufferReducedBatchStatementExecutor
         }
 
         SeaTunnelRow key = keyExtractor.apply(record);
+        Object bufferKey = bufferKey(key);
+        existedBeforeBatch.putIfAbsent(bufferKey, 
!RowKind.INSERT.equals(record.getRowKind()));
         boolean changeFlag = changeFlag(record.getRowKind());
         SeaTunnelRow value = valueTransform.apply(record);
-        buffer.put(bufferKey(key), Triple.of(changeFlag, key, value));
+        buffer.put(bufferKey, Triple.of(changeFlag, key, value));
     }
 
     /**
@@ -88,13 +97,20 @@ public class BufferReducedBatchStatementExecutor
     public void executeBatch() throws SQLException {
         try {
             Boolean preChangeFlag = null;
-            for (Triple<Boolean, SeaTunnelRow, SeaTunnelRow> bufferedRow : 
buffer.values()) {
+            for (Map.Entry<Object, Triple<Boolean, SeaTunnelRow, 
SeaTunnelRow>> entry :
+                    buffer.entrySet()) {
+                Triple<Boolean, SeaTunnelRow, SeaTunnelRow> bufferedRow = 
entry.getValue();
                 Boolean currentChangeFlag = bufferedRow.getLeft();
                 if (currentChangeFlag) {
                     if (preChangeFlag != null && !preChangeFlag) {
                         deleteExecutor.executeBatch();
                     }
-                    upsertExecutor.addToBatch(bufferedRow.getRight());
+                    SeaTunnelRow value = bufferedRow.getRight().copy();
+                    value.setRowKind(
+                            existedBeforeBatch.getOrDefault(entry.getKey(), 
true)
+                                    ? RowKind.UPDATE_AFTER
+                                    : RowKind.INSERT);
+                    upsertExecutor.addToBatch(value);
                 } else {
                     if (preChangeFlag != null && preChangeFlag) {
                         upsertExecutor.executeBatch();
@@ -112,6 +128,7 @@ public class BufferReducedBatchStatementExecutor
                 }
             }
             buffer.clear();
+            existedBeforeBatch.clear();
             flushFailed = false;
         } catch (SQLException e) {
             flushFailed = true;
@@ -134,6 +151,7 @@ public class BufferReducedBatchStatementExecutor
     @Override
     public void clearBatch() throws SQLException {
         buffer.clear();
+        existedBeforeBatch.clear();
         upsertExecutor.clearBatch();
         deleteExecutor.clearBatch();
     }
diff --git 
a/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/executor/BufferReducedBatchStatementExecutorTest.java
 
b/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/executor/BufferReducedBatchStatementExecutorTest.java
index b3c3e437c7..54cf97793a 100644
--- 
a/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/executor/BufferReducedBatchStatementExecutorTest.java
+++ 
b/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/executor/BufferReducedBatchStatementExecutorTest.java
@@ -27,6 +27,7 @@ import java.sql.Connection;
 import java.sql.SQLException;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 import java.util.function.Function;
 
@@ -73,6 +74,45 @@ public class BufferReducedBatchStatementExecutorTest extends 
BufferExecutorTest
                 Function.identity());
     }
 
+    @Test
+    void testReducedRowKeepsTheNetEffectOfTheBatch() throws SQLException {
+        List<SeaTunnelRow> upserts = new ArrayList<>();
+        List<SeaTunnelRow> deletes = new ArrayList<>();
+        JdbcBatchStatementExecutor<SeaTunnelRow> executor = 
newKeyedExecutor(upserts, deletes);
+        // Created then updated in one batch: still has to be inserted.
+        executor.addToBatch(row(RowKind.INSERT, 1, 1, "a"));
+        executor.addToBatch(row(RowKind.UPDATE_BEFORE, 1, 1, "a"));
+        executor.addToBatch(row(RowKind.UPDATE_AFTER, 1, 1, "b"));
+        // Existing row updated twice.
+        executor.addToBatch(row(RowKind.UPDATE_AFTER, 2, 2, "x"));
+        executor.addToBatch(row(RowKind.UPDATE_AFTER, 2, 2, "y"));
+        // Existing row deleted and re-inserted: the row is still there, so 
update it.
+        executor.addToBatch(row(RowKind.DELETE, 3, 3, "p"));
+        executor.addToBatch(row(RowKind.INSERT, 3, 3, "q"));
+        // Existing row updated then deleted.
+        executor.addToBatch(row(RowKind.UPDATE_AFTER, 4, 4, "m"));
+        executor.addToBatch(row(RowKind.DELETE, 4, 4, "m"));
+        executor.executeBatch();
+
+        Assertions.assertEquals(
+                Arrays.asList("+I[1, 1, b]", "+U[2, 2, y]", "+U[3, 3, q]"), 
describe(upserts));
+        Assertions.assertEquals(Collections.singletonList("+I[4]"), 
describe(deletes));
+
+        // A new batch judges existence afresh.
+        upserts.clear();
+        executor.addToBatch(row(RowKind.UPDATE_AFTER, 1, 1, "c"));
+        executor.executeBatch();
+        Assertions.assertEquals(Collections.singletonList("+U[1, 1, c]"), 
describe(upserts));
+    }
+
+    private static List<String> describe(List<SeaTunnelRow> rows) {
+        List<String> described = new ArrayList<>();
+        for (SeaTunnelRow row : rows) {
+            described.add(row.getRowKind().shortString() + 
Arrays.toString(row.getFields()));
+        }
+        return described;
+    }
+
     @Test
     void testRowsWithSameKeyAreReduced() throws SQLException {
         List<SeaTunnelRow> upserts = new ArrayList<>();

Reply via email to