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

zhangyv pushed a commit to branch 2.x
in repository https://gitbox.apache.org/repos/asf/incubator-seata.git


The following commit(s) were added to refs/heads/2.x by this push:
     new 57d170db06 test: add UT for dm module  (#7709)
57d170db06 is described below

commit 57d170db06c4c6e572f3aca3de58eaa4eef4d5c6
Author: maple <[email protected]>
AuthorDate: Sat Oct 18 19:22:35 2025 +0800

    test: add UT for dm module  (#7709)
---
 changes/en-us/2.x.md                               |   2 +
 changes/zh-cn/2.x.md                               |   2 +
 .../datasource/undo/dm/DmUndoDeleteExecutor.java   |  13 +-
 .../undo/dm/DmUndoDeleteExecutorTest.java          | 158 +++++++++++
 .../undo/dm/DmUndoExecutorHolderTest.java          | 107 ++++++++
 .../undo/dm/DmUndoInsertExecutorTest.java          | 185 +++++++++++++
 .../datasource/undo/dm/DmUndoLogManagerTest.java   | 289 +++++++++++++++++++++
 .../undo/dm/DmUndoUpdateExecutorTest.java          | 173 ++++++++++++
 8 files changed, 922 insertions(+), 7 deletions(-)

diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md
index ecbabfb7b7..4670599c2d 100644
--- a/changes/en-us/2.x.md
+++ b/changes/en-us/2.x.md
@@ -46,6 +46,7 @@ Add changes here for all PR submitted to the 2.x branch.
 - [[#7644](https://github.com/apache/incubator-seata/pull/7644)] fix the 
compatibility issue of spotless when java 25
 - [[#7662](https://github.com/apache/incubator-seata/pull/7662)] ensure 
visibility of rm and The methods in MockTest are executed in order
 - [[#7683](https://github.com/apache/incubator-seata/pull/7683)] Override 
XABranchXid equals() and hashCode() to fix memory leak in mysql driver
+- [[#7643](https://github.com/apache/incubator-seata/pull/7643)] fix DM 
transaction rollback not using database auto-increment primary keys
 
 
 ### optimize:
@@ -82,6 +83,7 @@ Add changes here for all PR submitted to the 2.x branch.
 - [[#7672](https://github.com/apache/incubator-seata/pull/7672)] Add unit 
tests for the `seata-common` module
 - [[#7679](https://github.com/apache/incubator-seata/pull/7679)] fix old 
version connect timeout
 - [[#7638](https://github.com/apache/incubator-seata/pull/7638)]Add unit tests 
for the `seata-common` module and remove a todo
+- [[#7709](https://github.com/apache/incubator-seata/pull/7709)] add UT for dm 
module
 
 
 ### refactor:
diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md
index dc6c1a8459..8fb0e909a8 100644
--- a/changes/zh-cn/2.x.md
+++ b/changes/zh-cn/2.x.md
@@ -46,6 +46,7 @@
 - [[#7644](https://github.com/apache/incubator-seata/pull/7644)] 
修复JAVA25时spotless的兼容性问题
 - [[#7662](https://github.com/apache/incubator-seata/pull/7662)] 确保 rm 的可见性,并且 
MockTest 中的方法按顺序执行
 - [[#7683](https://github.com/apache/incubator-seata/pull/7683)] 重写 
XABranchXid的equals和hashCode,解决mysql driver内存泄漏问题
+- [[#7643](https://github.com/apache/incubator-seata/pull/7643)] 修复 DM 
事务回滚不使用数据库自动增量主键
 
 
 ### optimize:
@@ -81,6 +82,7 @@
 - [[#7672](https://github.com/apache/incubator-seata/pull/7672)] 增加 
`seata-common` 模块的测试用例
 - [[#7679](https://github.com/apache/incubator-seata/pull/7679)] 修复旧版本协议测试超时问题
 - [[#7638](https://github.com/apache/incubator-seata/pull/7638)] 增加了 
`seata-common` 模块的测试用例,删去了一个todo
+- [[#7709](https://github.com/apache/incubator-seata/pull/7709)] 为dm模块增加单测
 
 ### refactor:
 
diff --git 
a/rm-datasource/src/main/java/org/apache/seata/rm/datasource/undo/dm/DmUndoDeleteExecutor.java
 
b/rm-datasource/src/main/java/org/apache/seata/rm/datasource/undo/dm/DmUndoDeleteExecutor.java
index 61e3347eb3..c10d728b55 100644
--- 
a/rm-datasource/src/main/java/org/apache/seata/rm/datasource/undo/dm/DmUndoDeleteExecutor.java
+++ 
b/rm-datasource/src/main/java/org/apache/seata/rm/datasource/undo/dm/DmUndoDeleteExecutor.java
@@ -95,13 +95,12 @@ public class DmUndoDeleteExecutor extends 
AbstractUndoExecutor {
                     + sqlUndoLog.getTableName()
                     + " OFF;";
         } else {
-            return " INSERT INTO " +
-                    sqlUndoLog.getTableName() +
-                    " (" +
-                    insertColumns +
-                    ") VALUES (" +
-                    insertValues +
-                    ");";
+            return " INSERT INTO " + sqlUndoLog.getTableName()
+                    + " ("
+                    + insertColumns
+                    + ") VALUES ("
+                    + insertValues
+                    + ");";
         }
     }
 
diff --git 
a/rm-datasource/src/test/java/org/apache/seata/rm/datasource/undo/dm/DmUndoDeleteExecutorTest.java
 
b/rm-datasource/src/test/java/org/apache/seata/rm/datasource/undo/dm/DmUndoDeleteExecutorTest.java
new file mode 100644
index 0000000000..4f3934825a
--- /dev/null
+++ 
b/rm-datasource/src/test/java/org/apache/seata/rm/datasource/undo/dm/DmUndoDeleteExecutorTest.java
@@ -0,0 +1,158 @@
+/*
+ * 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.
+ */
+package org.apache.seata.rm.datasource.undo.dm;
+
+import org.apache.seata.rm.datasource.sql.struct.Row;
+import org.apache.seata.rm.datasource.sql.struct.TableRecords;
+import org.apache.seata.rm.datasource.undo.BaseExecutorTest;
+import org.apache.seata.rm.datasource.undo.SQLUndoLog;
+import org.apache.seata.sqlparser.SQLType;
+import org.apache.seata.sqlparser.struct.ColumnMeta;
+import org.apache.seata.sqlparser.struct.TableMeta;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * The type DmUndoDeleteExecutor test.
+ */
+public class DmUndoDeleteExecutorTest extends BaseExecutorTest {
+
+    @Test
+    public void buildUndoSQL() {
+        DmUndoDeleteExecutor executor = createExecutor();
+
+        String sql = executor.buildUndoSQL();
+        Assertions.assertNotNull(sql);
+        Assertions.assertTrue(sql.contains("INSERT INTO"));
+        Assertions.assertTrue(sql.contains("\"id\""));
+        Assertions.assertTrue(sql.contains("\"age\""));
+        Assertions.assertTrue(sql.contains("table_name"));
+    }
+
+    @Test
+    public void buildUndoSQLWithAutoIncrement() {
+        DmUndoDeleteExecutor executor = createExecutorWithAutoIncrement();
+
+        String sql = executor.buildUndoSQL();
+        Assertions.assertNotNull(sql);
+        Assertions.assertTrue(sql.contains("SET IDENTITY_INSERT"));
+        Assertions.assertTrue(sql.contains("INSERT INTO"));
+        Assertions.assertTrue(sql.contains("\"id\""));
+        Assertions.assertTrue(sql.contains("\"age\""));
+        Assertions.assertTrue(sql.contains("table_name"));
+    }
+
+    @Test
+    public void getUndoRows() {
+        DmUndoDeleteExecutor executor = createExecutor();
+        Assertions.assertEquals(executor.getUndoRows(), 
executor.getSqlUndoLog().getBeforeImage());
+    }
+
+    @Test
+    public void testInvalidUndoLog() {
+        TableMeta tableMeta = Mockito.mock(TableMeta.class);
+        
Mockito.when(tableMeta.getPrimaryKeyOnlyName()).thenReturn(Arrays.asList("id"));
+        Mockito.when(tableMeta.getTableName()).thenReturn("table_name");
+
+        TableRecords beforeImage = new TableRecords();
+        beforeImage.setTableName("table_name");
+        beforeImage.setTableMeta(tableMeta);
+        beforeImage.setRows(new ArrayList<>());
+
+        SQLUndoLog sqlUndoLog = new SQLUndoLog();
+        sqlUndoLog.setSqlType(SQLType.DELETE);
+        sqlUndoLog.setTableMeta(tableMeta);
+        sqlUndoLog.setTableName("table_name");
+        sqlUndoLog.setBeforeImage(beforeImage);
+        sqlUndoLog.setAfterImage(new TableRecords());
+
+        DmUndoDeleteExecutor executor = new DmUndoDeleteExecutor(sqlUndoLog);
+
+        Assertions.assertThrows(Exception.class, executor::buildUndoSQL);
+    }
+
+    private DmUndoDeleteExecutor createExecutor() {
+        TableMeta tableMeta = Mockito.mock(TableMeta.class);
+        
Mockito.when(tableMeta.getPrimaryKeyOnlyName()).thenReturn(Arrays.asList("id"));
+        Mockito.when(tableMeta.getTableName()).thenReturn("table_name");
+
+        TableRecords beforeImage = new TableRecords();
+        beforeImage.setTableName("table_name");
+        beforeImage.setTableMeta(tableMeta);
+        List<Row> beforeRows = new ArrayList<>();
+        Row row0 = new Row();
+        addField(row0, "id", 1, "12345");
+        addField(row0, "age", 1, "1");
+        beforeRows.add(row0);
+        Row row1 = new Row();
+        addField(row1, "id", 1, "12346");
+        addField(row1, "age", 1, "1");
+        beforeRows.add(row1);
+        beforeImage.setRows(beforeRows);
+
+        TableRecords afterImage = new TableRecords();
+        afterImage.setTableName("table_name");
+        afterImage.setTableMeta(tableMeta);
+
+        SQLUndoLog sqlUndoLog = new SQLUndoLog();
+        sqlUndoLog.setSqlType(SQLType.DELETE);
+        sqlUndoLog.setTableMeta(tableMeta);
+        sqlUndoLog.setTableName("table_name");
+        sqlUndoLog.setBeforeImage(beforeImage);
+        sqlUndoLog.setAfterImage(afterImage);
+
+        return new DmUndoDeleteExecutor(sqlUndoLog);
+    }
+
+    private DmUndoDeleteExecutor createExecutorWithAutoIncrement() {
+        TableMeta tableMeta = Mockito.mock(TableMeta.class);
+        
Mockito.when(tableMeta.getPrimaryKeyOnlyName()).thenReturn(Arrays.asList("id"));
+        Mockito.when(tableMeta.getTableName()).thenReturn("table_name");
+
+        ColumnMeta idColumnMeta = Mockito.mock(ColumnMeta.class);
+        Mockito.when(idColumnMeta.isAutoincrement()).thenReturn(true);
+        Mockito.when(tableMeta.getColumnMeta("id")).thenReturn(idColumnMeta);
+
+        TableRecords beforeImage = new TableRecords();
+        beforeImage.setTableName("table_name");
+        beforeImage.setTableMeta(tableMeta);
+        List<Row> beforeRows = new ArrayList<>();
+        Row row0 = new Row();
+        addField(row0, "id", 1, "12345");
+        addField(row0, "age", 1, "1");
+        beforeRows.add(row0);
+        beforeImage.setRows(beforeRows);
+
+        TableRecords afterImage = new TableRecords();
+        afterImage.setTableName("table_name");
+        afterImage.setTableMeta(tableMeta);
+
+        SQLUndoLog sqlUndoLog = new SQLUndoLog();
+        sqlUndoLog.setSqlType(SQLType.DELETE);
+        sqlUndoLog.setTableMeta(tableMeta);
+        sqlUndoLog.setTableName("table_name");
+        sqlUndoLog.setBeforeImage(beforeImage);
+        sqlUndoLog.setAfterImage(afterImage);
+
+        return new DmUndoDeleteExecutor(sqlUndoLog);
+    }
+}
diff --git 
a/rm-datasource/src/test/java/org/apache/seata/rm/datasource/undo/dm/DmUndoExecutorHolderTest.java
 
b/rm-datasource/src/test/java/org/apache/seata/rm/datasource/undo/dm/DmUndoExecutorHolderTest.java
new file mode 100644
index 0000000000..4fe7ab5b83
--- /dev/null
+++ 
b/rm-datasource/src/test/java/org/apache/seata/rm/datasource/undo/dm/DmUndoExecutorHolderTest.java
@@ -0,0 +1,107 @@
+/*
+ * 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.
+ */
+package org.apache.seata.rm.datasource.undo.dm;
+
+import org.apache.seata.rm.datasource.undo.AbstractUndoExecutor;
+import org.apache.seata.rm.datasource.undo.SQLUndoLog;
+import org.apache.seata.sqlparser.SQLType;
+import org.apache.seata.sqlparser.struct.TableMeta;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+/**
+ * The type DmUndoExecutorHolder test.
+ */
+public class DmUndoExecutorHolderTest {
+
+    private DmUndoExecutorHolder holder;
+    private SQLUndoLog sqlUndoLog;
+
+    @BeforeEach
+    public void setup() {
+        holder = new DmUndoExecutorHolder();
+
+        sqlUndoLog = new SQLUndoLog();
+        TableMeta tableMeta = Mockito.mock(TableMeta.class);
+        Mockito.when(tableMeta.getTableName()).thenReturn("test_table");
+
+        sqlUndoLog.setTableMeta(tableMeta);
+        sqlUndoLog.setTableName("test_table");
+    }
+
+    @Test
+    public void testGetInsertExecutor() {
+        sqlUndoLog.setSqlType(SQLType.INSERT);
+
+        AbstractUndoExecutor executor = holder.getInsertExecutor(sqlUndoLog);
+
+        Assertions.assertNotNull(executor);
+        Assertions.assertTrue(executor instanceof DmUndoInsertExecutor);
+        Assertions.assertEquals(sqlUndoLog, executor.getSqlUndoLog());
+    }
+
+    @Test
+    public void testGetUpdateExecutor() {
+        sqlUndoLog.setSqlType(SQLType.UPDATE);
+
+        AbstractUndoExecutor executor = holder.getUpdateExecutor(sqlUndoLog);
+
+        Assertions.assertNotNull(executor);
+        Assertions.assertTrue(executor instanceof DmUndoUpdateExecutor);
+        Assertions.assertEquals(sqlUndoLog, executor.getSqlUndoLog());
+    }
+
+    @Test
+    public void testGetDeleteExecutor() {
+        sqlUndoLog.setSqlType(SQLType.DELETE);
+
+        AbstractUndoExecutor executor = holder.getDeleteExecutor(sqlUndoLog);
+
+        Assertions.assertNotNull(executor);
+        Assertions.assertTrue(executor instanceof DmUndoDeleteExecutor);
+        Assertions.assertEquals(sqlUndoLog, executor.getSqlUndoLog());
+    }
+
+    @Test
+    public void testAllExecutorsWithNullUndoLog() {
+        // These methods don't throw NullPointerException, they just pass null 
to constructors
+        AbstractUndoExecutor insertExecutor = holder.getInsertExecutor(null);
+        AbstractUndoExecutor updateExecutor = holder.getUpdateExecutor(null);
+        AbstractUndoExecutor deleteExecutor = holder.getDeleteExecutor(null);
+
+        Assertions.assertNotNull(insertExecutor);
+        Assertions.assertNotNull(updateExecutor);
+        Assertions.assertNotNull(deleteExecutor);
+        Assertions.assertTrue(insertExecutor instanceof DmUndoInsertExecutor);
+        Assertions.assertTrue(updateExecutor instanceof DmUndoUpdateExecutor);
+        Assertions.assertTrue(deleteExecutor instanceof DmUndoDeleteExecutor);
+    }
+
+    @Test
+    public void testExecutorReturnsDifferentInstances() {
+        sqlUndoLog.setSqlType(SQLType.INSERT);
+
+        AbstractUndoExecutor executor1 = holder.getInsertExecutor(sqlUndoLog);
+        AbstractUndoExecutor executor2 = holder.getInsertExecutor(sqlUndoLog);
+
+        Assertions.assertNotSame(executor1, executor2);
+        Assertions.assertTrue(executor1 instanceof DmUndoInsertExecutor);
+        Assertions.assertTrue(executor2 instanceof DmUndoInsertExecutor);
+    }
+}
diff --git 
a/rm-datasource/src/test/java/org/apache/seata/rm/datasource/undo/dm/DmUndoInsertExecutorTest.java
 
b/rm-datasource/src/test/java/org/apache/seata/rm/datasource/undo/dm/DmUndoInsertExecutorTest.java
new file mode 100644
index 0000000000..05e63cd8c7
--- /dev/null
+++ 
b/rm-datasource/src/test/java/org/apache/seata/rm/datasource/undo/dm/DmUndoInsertExecutorTest.java
@@ -0,0 +1,185 @@
+/*
+ * 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.
+ */
+package org.apache.seata.rm.datasource.undo.dm;
+
+import org.apache.seata.rm.datasource.sql.struct.Field;
+import org.apache.seata.rm.datasource.sql.struct.KeyType;
+import org.apache.seata.rm.datasource.sql.struct.Row;
+import org.apache.seata.rm.datasource.sql.struct.TableRecords;
+import org.apache.seata.rm.datasource.undo.BaseExecutorTest;
+import org.apache.seata.rm.datasource.undo.SQLUndoLog;
+import org.apache.seata.sqlparser.SQLType;
+import org.apache.seata.sqlparser.struct.TableMeta;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * The type DmUndoInsertExecutor test.
+ */
+public class DmUndoInsertExecutorTest extends BaseExecutorTest {
+
+    @Test
+    public void buildUndoSQL() {
+        DmUndoInsertExecutor executor = createExecutor();
+
+        String sql = executor.buildUndoSQL();
+        Assertions.assertNotNull(sql);
+        Assertions.assertTrue(sql.contains("DELETE FROM"));
+        Assertions.assertTrue(sql.contains("WHERE"));
+        Assertions.assertTrue(sql.contains("\"id\" = ?"));
+        Assertions.assertTrue(sql.contains("table_name"));
+    }
+
+    @Test
+    public void getUndoRows() {
+        DmUndoInsertExecutor executor = createExecutor();
+        Assertions.assertEquals(executor.getUndoRows(), 
executor.getSqlUndoLog().getAfterImage());
+    }
+
+    @Test
+    public void testInvalidUndoLog() {
+        TableMeta tableMeta = Mockito.mock(TableMeta.class);
+        
Mockito.when(tableMeta.getPrimaryKeyOnlyName()).thenReturn(Arrays.asList("id"));
+        Mockito.when(tableMeta.getTableName()).thenReturn("table_name");
+
+        TableRecords afterImage = new TableRecords();
+        afterImage.setTableName("table_name");
+        afterImage.setTableMeta(tableMeta);
+        afterImage.setRows(new ArrayList<>());
+
+        SQLUndoLog sqlUndoLog = new SQLUndoLog();
+        sqlUndoLog.setSqlType(SQLType.INSERT);
+        sqlUndoLog.setTableMeta(tableMeta);
+        sqlUndoLog.setTableName("table_name");
+        sqlUndoLog.setBeforeImage(new TableRecords());
+        sqlUndoLog.setAfterImage(afterImage);
+
+        DmUndoInsertExecutor executor = new DmUndoInsertExecutor(sqlUndoLog);
+
+        Assertions.assertThrows(Exception.class, executor::buildUndoSQL);
+    }
+
+    @Test
+    public void testCompositePrimaryKey() {
+        DmUndoInsertExecutor executor = 
createExecutorWithCompositePrimaryKey();
+
+        String sql = executor.buildUndoSQL();
+        Assertions.assertNotNull(sql);
+        Assertions.assertTrue(sql.contains("DELETE FROM"));
+        Assertions.assertTrue(sql.contains("WHERE"));
+
+        // Check that both primary key columns appear in the WHERE clause
+        Assertions.assertTrue(sql.contains("id1"));
+        Assertions.assertTrue(sql.contains("id2"));
+        Assertions.assertTrue(sql.contains(" = ?"));
+        Assertions.assertTrue(sql.contains(" and "));
+    }
+
+    @Test
+    public void testUndoPrepare() throws SQLException {
+        DmUndoInsertExecutor executor = createExecutor();
+        PreparedStatement mockStatement = 
Mockito.mock(PreparedStatement.class);
+
+        ArrayList<Field> undoValues = new ArrayList<>();
+        List<Field> pkValueList = new ArrayList<>();
+        Field pkField = new Field("id", 1, "12345");
+        pkValueList.add(pkField);
+
+        Assertions.assertDoesNotThrow(() -> 
executor.undoPrepare(mockStatement, undoValues, pkValueList));
+
+        // Verify that setObject was called
+        Mockito.verify(mockStatement, Mockito.times(1)).setObject(1, "12345", 
1);
+    }
+
+    private DmUndoInsertExecutor createExecutor() {
+        TableMeta tableMeta = Mockito.mock(TableMeta.class);
+        
Mockito.when(tableMeta.getPrimaryKeyOnlyName()).thenReturn(Arrays.asList("id"));
+        Mockito.when(tableMeta.getTableName()).thenReturn("table_name");
+
+        TableRecords beforeImage = new TableRecords();
+        beforeImage.setTableName("table_name");
+        beforeImage.setTableMeta(tableMeta);
+
+        TableRecords afterImage = new TableRecords();
+        afterImage.setTableName("table_name");
+        afterImage.setTableMeta(tableMeta);
+        List<Row> afterRows = new ArrayList<>();
+        Row row0 = new Row();
+        addField(row0, "id", 1, "12345");
+        addField(row0, "age", 1, "1");
+        afterRows.add(row0);
+        Row row1 = new Row();
+        addField(row1, "id", 1, "12346");
+        addField(row1, "age", 1, "1");
+        afterRows.add(row1);
+        afterImage.setRows(afterRows);
+
+        SQLUndoLog sqlUndoLog = new SQLUndoLog();
+        sqlUndoLog.setSqlType(SQLType.INSERT);
+        sqlUndoLog.setTableMeta(tableMeta);
+        sqlUndoLog.setTableName("table_name");
+        sqlUndoLog.setBeforeImage(beforeImage);
+        sqlUndoLog.setAfterImage(afterImage);
+
+        return new DmUndoInsertExecutor(sqlUndoLog);
+    }
+
+    private DmUndoInsertExecutor createExecutorWithCompositePrimaryKey() {
+        TableMeta tableMeta = Mockito.mock(TableMeta.class);
+        
Mockito.when(tableMeta.getPrimaryKeyOnlyName()).thenReturn(Arrays.asList("id1", 
"id2"));
+        Mockito.when(tableMeta.getTableName()).thenReturn("table_name");
+
+        TableRecords beforeImage = new TableRecords();
+        beforeImage.setTableName("table_name");
+        beforeImage.setTableMeta(tableMeta);
+
+        TableRecords afterImage = new TableRecords();
+        afterImage.setTableName("table_name");
+        afterImage.setTableMeta(tableMeta);
+        List<Row> afterRows = new ArrayList<>();
+        Row row0 = new Row();
+
+        // Manually create primary key fields since addField only sets "id" as 
primary key
+        Field id1Field = new Field("id1", 1, "123");
+        id1Field.setKeyType(KeyType.PRIMARY_KEY);
+        row0.add(id1Field);
+
+        Field id2Field = new Field("id2", 1, "456");
+        id2Field.setKeyType(KeyType.PRIMARY_KEY);
+        row0.add(id2Field);
+
+        addField(row0, "age", 1, "25");
+        afterRows.add(row0);
+        afterImage.setRows(afterRows);
+
+        SQLUndoLog sqlUndoLog = new SQLUndoLog();
+        sqlUndoLog.setSqlType(SQLType.INSERT);
+        sqlUndoLog.setTableMeta(tableMeta);
+        sqlUndoLog.setTableName("table_name");
+        sqlUndoLog.setBeforeImage(beforeImage);
+        sqlUndoLog.setAfterImage(afterImage);
+
+        return new DmUndoInsertExecutor(sqlUndoLog);
+    }
+}
diff --git 
a/rm-datasource/src/test/java/org/apache/seata/rm/datasource/undo/dm/DmUndoLogManagerTest.java
 
b/rm-datasource/src/test/java/org/apache/seata/rm/datasource/undo/dm/DmUndoLogManagerTest.java
new file mode 100644
index 0000000000..90af788097
--- /dev/null
+++ 
b/rm-datasource/src/test/java/org/apache/seata/rm/datasource/undo/dm/DmUndoLogManagerTest.java
@@ -0,0 +1,289 @@
+/*
+ * 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.
+ */
+package org.apache.seata.rm.datasource.undo.dm;
+
+import com.alibaba.druid.pool.DruidDataSource;
+import com.alibaba.druid.pool.DruidStatementConnection;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import org.apache.seata.common.loader.EnhancedServiceLoader;
+import org.apache.seata.rm.datasource.ConnectionContext;
+import org.apache.seata.rm.datasource.ConnectionProxy;
+import org.apache.seata.rm.datasource.DataSourceProxy;
+import org.apache.seata.rm.datasource.DataSourceProxyTest;
+import org.apache.seata.rm.datasource.mock.MockDriver;
+import org.apache.seata.rm.datasource.sql.struct.Row;
+import org.apache.seata.rm.datasource.sql.struct.TableRecords;
+import org.apache.seata.rm.datasource.undo.*;
+import org.apache.seata.rm.datasource.undo.parser.JacksonUndoLogParser;
+import org.apache.seata.sqlparser.SQLRecognizerFactory;
+import org.apache.seata.sqlparser.SQLType;
+import org.apache.seata.sqlparser.SqlParserType;
+import org.apache.seata.sqlparser.druid.DruidDelegatingSQLRecognizerFactory;
+import org.apache.seata.sqlparser.druid.SQLOperateRecognizerHolder;
+import org.apache.seata.sqlparser.druid.SQLOperateRecognizerHolderFactory;
+import org.apache.seata.sqlparser.struct.TableMeta;
+import org.apache.seata.sqlparser.util.JdbcConstants;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.sql.Types;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * The type DmUndoLogManager test.
+ */
+public class DmUndoLogManagerTest {
+
+    List<String> returnValueColumnLabels = Lists.newArrayList("log_status");
+    Object[][] returnValue = new Object[][] {
+        new Object[] {1}, new Object[] {2},
+    };
+    Object[][] columnMetas = new Object[][] {
+        new Object[] {
+            "",
+            "",
+            "table_plain_executor_test",
+            "id",
+            Types.INTEGER,
+            "INTEGER",
+            64,
+            0,
+            10,
+            1,
+            "",
+            "",
+            0,
+            0,
+            64,
+            1,
+            "NO",
+            "YES"
+        },
+        new Object[] {
+            "",
+            "",
+            "table_plain_executor_test",
+            "name",
+            Types.VARCHAR,
+            "VARCHAR",
+            64,
+            0,
+            10,
+            0,
+            "",
+            "",
+            0,
+            0,
+            64,
+            2,
+            "YES",
+            "NO"
+        },
+    };
+    Object[][] indexMetas = new Object[][] {
+        new Object[] {"PRIMARY", "id", false, "", 3, 1, "A", 34},
+    };
+
+    private DruidDataSource dataSource;
+    private DataSourceProxy dataSourceProxy;
+    private ConnectionProxy connectionProxy;
+    private DmUndoLogManager undoLogManager;
+    private TableMeta tableMeta;
+
+    @BeforeAll
+    public static void setup() {
+        EnhancedServiceLoader.load(
+                SQLOperateRecognizerHolder.class,
+                JdbcConstants.DM,
+                SQLOperateRecognizerHolderFactory.class.getClassLoader());
+        DruidDelegatingSQLRecognizerFactory recognizerFactory = 
(DruidDelegatingSQLRecognizerFactory)
+                EnhancedServiceLoader.load(SQLRecognizerFactory.class, 
SqlParserType.SQL_PARSER_TYPE_DRUID);
+    }
+
+    @BeforeEach
+    public void init() throws SQLException {
+        MockDriver mockDriver = new MockDriver(returnValueColumnLabels, 
returnValue, columnMetas, indexMetas);
+        dataSource = new DruidDataSource();
+        dataSource.setUrl("jdbc:mock:xxx");
+        dataSource.setDriver(mockDriver);
+
+        dataSourceProxy = DataSourceProxyTest.getDataSourceProxy(dataSource);
+
+        connectionProxy = new ConnectionProxy(dataSourceProxy, 
getPhysicsConnection(dataSource));
+        undoLogManager = new DmUndoLogManager();
+        tableMeta = new TableMeta();
+        tableMeta.setTableName("table_plain_executor_test");
+    }
+
+    private Connection getPhysicsConnection(DruidDataSource dataSource) throws 
SQLException {
+        Connection connection = dataSource.getConnection().getConnection();
+        if (connection instanceof DruidStatementConnection) {
+            return ((DruidStatementConnection) connection).getConnection();
+        }
+        return connection;
+    }
+
+    @Test
+    public void testDeleteUndoLogByLogCreated() throws SQLException {
+        Assertions.assertEquals(
+                0, undoLogManager.deleteUndoLogByLogCreated(new Date(), 3000, 
dataSource.getConnection()));
+        Assertions.assertDoesNotThrow(
+                () -> undoLogManager.deleteUndoLogByLogCreated(new Date(), 
3000, connectionProxy));
+    }
+
+    @Test
+    public void testInsertUndoLog() throws SQLException {
+        Assertions.assertDoesNotThrow(() -> 
undoLogManager.insertUndoLogWithGlobalFinished(
+                "xid", 1L, new JacksonUndoLogParser(), 
dataSource.getConnection()));
+
+        Assertions.assertDoesNotThrow(
+                () -> undoLogManager.insertUndoLogWithNormal("xid", 1L, "", 
new byte[] {}, dataSource.getConnection()));
+
+        Assertions.assertDoesNotThrow(
+                () -> undoLogManager.deleteUndoLogByLogCreated(new Date(), 
3000, connectionProxy));
+    }
+
+    @Test
+    public void testDeleteUndoLog() {
+        Assertions.assertDoesNotThrow(() -> 
undoLogManager.deleteUndoLog("xid", 1L, dataSource.getConnection()));
+        Assertions.assertDoesNotThrow(() -> 
undoLogManager.deleteUndoLog("xid", 1L, connectionProxy));
+    }
+
+    @Test
+    public void testBatchDeleteUndoLog() {
+        Assertions.assertDoesNotThrow(() -> undoLogManager.batchDeleteUndoLog(
+                Sets.newHashSet("xid"), Sets.newHashSet(1L), 
dataSource.getConnection()));
+
+        Assertions.assertDoesNotThrow(
+                () -> 
undoLogManager.batchDeleteUndoLog(Sets.newHashSet("xid"), Sets.newHashSet(1L), 
connectionProxy));
+    }
+
+    @Test
+    public void testBatchDeleteUndoLogWithEmptyParams() {
+        Assertions.assertDoesNotThrow(() ->
+                undoLogManager.batchDeleteUndoLog(Sets.newHashSet(), 
Sets.newHashSet(1L), dataSource.getConnection()));
+
+        Assertions.assertDoesNotThrow(() -> undoLogManager.batchDeleteUndoLog(
+                Sets.newHashSet("xid"), Sets.newHashSet(), 
dataSource.getConnection()));
+
+        Assertions.assertDoesNotThrow(
+                () -> undoLogManager.batchDeleteUndoLog(null, 
Sets.newHashSet(1L), dataSource.getConnection()));
+
+        Assertions.assertDoesNotThrow(
+                () -> 
undoLogManager.batchDeleteUndoLog(Sets.newHashSet("xid"), null, 
dataSource.getConnection()));
+    }
+
+    @Test
+    public void testFlushUndoLogs()
+            throws NoSuchMethodException, InvocationTargetException, 
IllegalAccessException, NoSuchFieldException {
+        connectionProxy.bind("xid");
+        ConnectionContext context = connectionProxy.getContext();
+        Method method = context.getClass().getDeclaredMethod("setBranchId", 
Long.class);
+        method.setAccessible(true);
+        method.invoke(context, 1L);
+
+        SQLUndoLog undoLogItem = getUndoLogItem(1);
+        undoLogItem.setTableName("test");
+        Method appendUndoItemMethod = 
context.getClass().getDeclaredMethod("appendUndoItem", SQLUndoLog.class);
+        appendUndoItemMethod.setAccessible(true);
+        appendUndoItemMethod.invoke(context, undoLogItem);
+
+        Assertions.assertDoesNotThrow(() -> 
undoLogManager.flushUndoLogs(connectionProxy));
+    }
+
+    @Test
+    public void testNeedCompress()
+            throws NoSuchFieldException, IllegalAccessException, 
NoSuchMethodException, InvocationTargetException {
+        SQLUndoLog smallUndoItem = getUndoLogItem(1);
+        BranchUndoLog smallBranchUndoLog = new BranchUndoLog();
+        smallBranchUndoLog.setBranchId(1L);
+        smallBranchUndoLog.setXid("test_xid");
+        
smallBranchUndoLog.setSqlUndoLogs(Collections.singletonList(smallUndoItem));
+        UndoLogParser parser = UndoLogParserFactory.getInstance();
+        byte[] smallUndoLogContent = parser.encode(smallBranchUndoLog);
+
+        Method method = 
AbstractUndoLogManager.class.getDeclaredMethod("needCompress", byte[].class);
+        method.setAccessible(true);
+        Assertions.assertFalse((Boolean) method.invoke(undoLogManager, 
smallUndoLogContent));
+
+        SQLUndoLog hugeUndoItem = getUndoLogItem(10000);
+        BranchUndoLog hugeBranchUndoLog = new BranchUndoLog();
+        hugeBranchUndoLog.setBranchId(2L);
+        hugeBranchUndoLog.setXid("test_xid1");
+        
hugeBranchUndoLog.setSqlUndoLogs(Collections.singletonList(hugeUndoItem));
+        byte[] hugeUndoLogContent = parser.encode(hugeBranchUndoLog);
+        Assertions.assertTrue((Boolean) method.invoke(undoLogManager, 
hugeUndoLogContent));
+    }
+
+    @Test
+    public void testUndo() throws SQLException {
+        Assertions.assertDoesNotThrow(() -> 
undoLogManager.undo(dataSourceProxy, "xid", 1L));
+    }
+
+    @Test
+    public void testToBatchDeleteSubUndoLogSql() {
+        String sql = DmUndoLogManager.toBatchDeleteSubUndoLogSql(2, 3);
+        Assertions.assertNotNull(sql);
+        Assertions.assertTrue(sql.contains("DELETE FROM"));
+        Assertions.assertTrue(sql.contains("\"CONTEXT\" IN"));
+        Assertions.assertTrue(sql.contains("xid IN"));
+        Assertions.assertTrue(sql.contains("(?,?,?)"));
+        Assertions.assertTrue(sql.contains("(?,?)"));
+    }
+
+    @Test
+    public void testBatchDeleteWithMultipleXidsAndBranchIds() {
+        Assertions.assertDoesNotThrow(() -> undoLogManager.batchDeleteUndoLog(
+                Sets.newHashSet("xid1", "xid2", "xid3"), Sets.newHashSet(1L, 
2L, 3L, 4L), dataSource.getConnection()));
+    }
+
+    private SQLUndoLog getUndoLogItem(int size) throws NoSuchFieldException, 
IllegalAccessException {
+        SQLUndoLog sqlUndoLog = new SQLUndoLog();
+        sqlUndoLog.setTableName("table_plain_executor_test");
+        sqlUndoLog.setSqlType(SQLType.INSERT);
+        sqlUndoLog.setTableMeta(tableMeta);
+
+        Field rowsField = TableRecords.class.getDeclaredField("rows");
+        rowsField.setAccessible(true);
+
+        List<Row> rows = new ArrayList<>(size);
+        for (int i = 0; i < size; i++) {
+            Row row = new Row();
+            row.add(new org.apache.seata.rm.datasource.sql.struct.Field("id", 
1, "value_id_" + i));
+            row.add(new 
org.apache.seata.rm.datasource.sql.struct.Field("name", 1, "value_name_" + i));
+            rows.add(row);
+        }
+
+        sqlUndoLog.setAfterImage(TableRecords.empty(tableMeta));
+        TableRecords afterImage = new TableRecords(tableMeta);
+        rowsField.set(afterImage, rows);
+        sqlUndoLog.setAfterImage(afterImage);
+
+        return sqlUndoLog;
+    }
+}
diff --git 
a/rm-datasource/src/test/java/org/apache/seata/rm/datasource/undo/dm/DmUndoUpdateExecutorTest.java
 
b/rm-datasource/src/test/java/org/apache/seata/rm/datasource/undo/dm/DmUndoUpdateExecutorTest.java
new file mode 100644
index 0000000000..8c37c9309e
--- /dev/null
+++ 
b/rm-datasource/src/test/java/org/apache/seata/rm/datasource/undo/dm/DmUndoUpdateExecutorTest.java
@@ -0,0 +1,173 @@
+/*
+ * 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.
+ */
+package org.apache.seata.rm.datasource.undo.dm;
+
+import org.apache.seata.rm.datasource.sql.struct.Row;
+import org.apache.seata.rm.datasource.sql.struct.TableRecords;
+import org.apache.seata.rm.datasource.undo.BaseExecutorTest;
+import org.apache.seata.rm.datasource.undo.SQLUndoLog;
+import org.apache.seata.sqlparser.SQLType;
+import org.apache.seata.sqlparser.struct.TableMeta;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * The type DmUndoUpdateExecutor test.
+ */
+public class DmUndoUpdateExecutorTest extends BaseExecutorTest {
+
+    @Test
+    public void buildUndoSQL() {
+        DmUndoUpdateExecutor executor = createExecutor();
+
+        String sql = executor.buildUndoSQL();
+        Assertions.assertNotNull(sql);
+        Assertions.assertTrue(sql.contains("UPDATE"));
+        Assertions.assertTrue(sql.contains("SET"));
+        Assertions.assertTrue(sql.contains("\"age\" = ?"));
+        Assertions.assertTrue(sql.contains("WHERE"));
+        Assertions.assertTrue(sql.contains("\"id\" = ?"));
+        Assertions.assertTrue(sql.contains("table_name"));
+    }
+
+    @Test
+    public void getUndoRows() {
+        DmUndoUpdateExecutor executor = createExecutor();
+        Assertions.assertEquals(executor.getUndoRows(), 
executor.getSqlUndoLog().getBeforeImage());
+    }
+
+    @Test
+    public void testInvalidUndoLog() {
+        TableMeta tableMeta = Mockito.mock(TableMeta.class);
+        
Mockito.when(tableMeta.getPrimaryKeyOnlyName()).thenReturn(Arrays.asList("id"));
+        Mockito.when(tableMeta.getTableName()).thenReturn("table_name");
+
+        TableRecords beforeImage = new TableRecords();
+        beforeImage.setTableName("table_name");
+        beforeImage.setTableMeta(tableMeta);
+        beforeImage.setRows(new ArrayList<>());
+
+        SQLUndoLog sqlUndoLog = new SQLUndoLog();
+        sqlUndoLog.setSqlType(SQLType.UPDATE);
+        sqlUndoLog.setTableMeta(tableMeta);
+        sqlUndoLog.setTableName("table_name");
+        sqlUndoLog.setBeforeImage(beforeImage);
+        sqlUndoLog.setAfterImage(new TableRecords());
+
+        DmUndoUpdateExecutor executor = new DmUndoUpdateExecutor(sqlUndoLog);
+
+        Assertions.assertThrows(Exception.class, executor::buildUndoSQL);
+    }
+
+    @Test
+    public void testMultipleColumns() {
+        DmUndoUpdateExecutor executor = createExecutorWithMultipleColumns();
+
+        String sql = executor.buildUndoSQL();
+        Assertions.assertNotNull(sql);
+        Assertions.assertTrue(sql.contains("UPDATE"));
+        Assertions.assertTrue(sql.contains("\"age\" = ?"));
+        Assertions.assertTrue(sql.contains("\"name\" = ?"));
+        Assertions.assertTrue(sql.contains("WHERE"));
+        Assertions.assertTrue(sql.contains("\"id\" = ?"));
+    }
+
+    private DmUndoUpdateExecutor createExecutor() {
+        TableMeta tableMeta = Mockito.mock(TableMeta.class);
+        
Mockito.when(tableMeta.getPrimaryKeyOnlyName()).thenReturn(Arrays.asList("id"));
+        Mockito.when(tableMeta.getTableName()).thenReturn("table_name");
+
+        TableRecords beforeImage = new TableRecords();
+        beforeImage.setTableName("table_name");
+        beforeImage.setTableMeta(tableMeta);
+        List<Row> beforeRows = new ArrayList<>();
+        Row row0 = new Row();
+        addField(row0, "id", 1, "12345");
+        addField(row0, "age", 1, "1");
+        beforeRows.add(row0);
+        Row row1 = new Row();
+        addField(row1, "id", 1, "12346");
+        addField(row1, "age", 1, "1");
+        beforeRows.add(row1);
+        beforeImage.setRows(beforeRows);
+
+        TableRecords afterImage = new TableRecords();
+        afterImage.setTableName("table_name");
+        afterImage.setTableMeta(tableMeta);
+        List<Row> afterRows = new ArrayList<>();
+        Row row2 = new Row();
+        addField(row2, "id", 1, "12345");
+        addField(row2, "age", 1, "2");
+        afterRows.add(row2);
+        Row row3 = new Row();
+        addField(row3, "id", 1, "12346");
+        addField(row3, "age", 1, "2");
+        afterRows.add(row3);
+        afterImage.setRows(afterRows);
+
+        SQLUndoLog sqlUndoLog = new SQLUndoLog();
+        sqlUndoLog.setSqlType(SQLType.UPDATE);
+        sqlUndoLog.setTableMeta(tableMeta);
+        sqlUndoLog.setTableName("table_name");
+        sqlUndoLog.setBeforeImage(beforeImage);
+        sqlUndoLog.setAfterImage(afterImage);
+
+        return new DmUndoUpdateExecutor(sqlUndoLog);
+    }
+
+    private DmUndoUpdateExecutor createExecutorWithMultipleColumns() {
+        TableMeta tableMeta = Mockito.mock(TableMeta.class);
+        
Mockito.when(tableMeta.getPrimaryKeyOnlyName()).thenReturn(Arrays.asList("id"));
+        Mockito.when(tableMeta.getTableName()).thenReturn("table_name");
+
+        TableRecords beforeImage = new TableRecords();
+        beforeImage.setTableName("table_name");
+        beforeImage.setTableMeta(tableMeta);
+        List<Row> beforeRows = new ArrayList<>();
+        Row row0 = new Row();
+        addField(row0, "id", 1, "12345");
+        addField(row0, "age", 1, "25");
+        addField(row0, "name", 12, "John");
+        beforeRows.add(row0);
+        beforeImage.setRows(beforeRows);
+
+        TableRecords afterImage = new TableRecords();
+        afterImage.setTableName("table_name");
+        afterImage.setTableMeta(tableMeta);
+        List<Row> afterRows = new ArrayList<>();
+        Row row1 = new Row();
+        addField(row1, "id", 1, "12345");
+        addField(row1, "age", 1, "26");
+        addField(row1, "name", 12, "Jane");
+        afterRows.add(row1);
+        afterImage.setRows(afterRows);
+
+        SQLUndoLog sqlUndoLog = new SQLUndoLog();
+        sqlUndoLog.setSqlType(SQLType.UPDATE);
+        sqlUndoLog.setTableMeta(tableMeta);
+        sqlUndoLog.setTableName("table_name");
+        sqlUndoLog.setBeforeImage(beforeImage);
+        sqlUndoLog.setAfterImage(afterImage);
+
+        return new DmUndoUpdateExecutor(sqlUndoLog);
+    }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]


Reply via email to