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

jianbin 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 5923c62a91 bugfix: handle timestamp with time zone in postgresql 
primary key (#7908)
5923c62a91 is described below

commit 5923c62a91ca28757a5af220d34640d65382ac05
Author: lokidundun <[email protected]>
AuthorDate: Sun Jan 4 10:21:29 2026 +0800

    bugfix: handle timestamp with time zone in postgresql primary key (#7908)
---
 changes/en-us/2.x.md                               |   1 +
 changes/zh-cn/2.x.md                               |   1 +
 rm-datasource/pom.xml                              |   4 +
 .../rm/datasource/sql/struct/TableRecords.java     |   3 +
 .../undo/parser/JacksonUndoLogParser.java          |   3 +
 .../rm/datasource/sql/struct/TableRecordsTest.java | 115 +++++++++++++++++++++
 6 files changed, 127 insertions(+)

diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md
index 446c43a15f..6b1ae8dbdc 100644
--- a/changes/en-us/2.x.md
+++ b/changes/en-us/2.x.md
@@ -69,6 +69,7 @@ Add changes here for all PR submitted to the 2.x branch.
 - [[#7879](https://github.com/apache/incubator-seata/pull/7879)] fix:correct 
server port and naming server port
 - [[#7881](https://github.com/apache/incubator-seata/pull/7881)] the 
vgroup_table in the SQL files of all databases should use a three-column unique 
constraint
 - [[#7891](https://github.com/apache/incubator-seata/pull/7891)] raft 
split-brain causes incorrect cluster information
+- [[#7908](https://github.com/apache/incubator-seata/pull/7908)] handle 
timestamp with time zone in postgresql primary key
 
 
 ### optimize:
diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md
index 350295f191..d75178f830 100644
--- a/changes/zh-cn/2.x.md
+++ b/changes/zh-cn/2.x.md
@@ -69,6 +69,7 @@
 - [[#7879](https://github.com/apache/incubator-seata/pull/7879)] 
修正服务器端口与命名服务器端口
 - [[#7881](https://github.com/apache/incubator-seata/pull/7881)] 
修复了除mysql之外其他数据库的sql文件关于vgroup_table的表,使用三列唯一索引以保证容灾迁移高可用
 - [[#7891](https://github.com/apache/incubator-seata/pull/7891)] 
修复raft重选举与心跳并发时可能导致namingserver侧的元数据存在多个leader
+- [[#7908](https://github.com/apache/incubator-seata/pull/7908)] 在 PostgreSQL 
主键中处理带时区的时间戳
 
 
 ### optimize:
diff --git a/rm-datasource/pom.xml b/rm-datasource/pom.xml
index 9ae40fcdf0..90411c220d 100644
--- a/rm-datasource/pom.xml
+++ b/rm-datasource/pom.xml
@@ -70,6 +70,10 @@
             <groupId>com.fasterxml.jackson.core</groupId>
             <artifactId>jackson-databind</artifactId>
         </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.datatype</groupId>
+            <artifactId>jackson-datatype-jsr310</artifactId>
+        </dependency>
 
         <dependency>
             <groupId>org.apache.commons</groupId>
diff --git 
a/rm-datasource/src/main/java/org/apache/seata/rm/datasource/sql/struct/TableRecords.java
 
b/rm-datasource/src/main/java/org/apache/seata/rm/datasource/sql/struct/TableRecords.java
index 72dc7afeac..a14e7ca4ee 100755
--- 
a/rm-datasource/src/main/java/org/apache/seata/rm/datasource/sql/struct/TableRecords.java
+++ 
b/rm-datasource/src/main/java/org/apache/seata/rm/datasource/sql/struct/TableRecords.java
@@ -36,6 +36,7 @@ import java.sql.ResultSet;
 import java.sql.ResultSetMetaData;
 import java.sql.SQLException;
 import java.sql.Types;
+import java.time.OffsetDateTime;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -247,6 +248,8 @@ public class TableRecords implements java.io.Serializable {
                     }
                 } else if (dataType == TIMESTAMP_WITH_TIME_ZONE || dataType == 
TIMESTAMP_WITH_LOCAL_TIME_ZONE) {
                     
field.setValue(convertOffSetTime(timeToOffsetDateTime(resultSet.getBytes(i))));
+                } else if (dataType == Types.TIMESTAMP_WITH_TIMEZONE) {
+                    field.setValue(resultSet.getObject(i, 
OffsetDateTime.class));
                 } else {
                     // JDBCType.DISTINCT, JDBCType.STRUCT etc...
                     field.setValue(holdSerialDataType(resultSet.getObject(i)));
diff --git 
a/rm-datasource/src/main/java/org/apache/seata/rm/datasource/undo/parser/JacksonUndoLogParser.java
 
b/rm-datasource/src/main/java/org/apache/seata/rm/datasource/undo/parser/JacksonUndoLogParser.java
index a9944f1d9b..f83f441824 100644
--- 
a/rm-datasource/src/main/java/org/apache/seata/rm/datasource/undo/parser/JacksonUndoLogParser.java
+++ 
b/rm-datasource/src/main/java/org/apache/seata/rm/datasource/undo/parser/JacksonUndoLogParser.java
@@ -35,6 +35,7 @@ import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
 import com.fasterxml.jackson.databind.module.SimpleModule;
 import com.fasterxml.jackson.databind.node.ArrayNode;
 import com.fasterxml.jackson.databind.ser.std.ArraySerializerBase;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
 import org.apache.seata.common.Constants;
 import org.apache.seata.common.executor.Initialize;
 import org.apache.seata.common.loader.EnhancedServiceLoader;
@@ -185,6 +186,8 @@ public class JacksonUndoLogParser implements UndoLogParser, 
Initialize {
         module.addSerializer(SerialArray.class, serialArraySerializer);
         module.addDeserializer(SerialArray.class, serialArrayDeserializer);
         registerDmdbTimestampModuleIfPresent();
+        JavaTimeModule javaTimeModule = new JavaTimeModule();
+        mapper.registerModule(javaTimeModule);
         mapper.registerModule(module);
         mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, 
false);
         mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, 
JsonTypeInfo.As.PROPERTY);
diff --git 
a/rm-datasource/src/test/java/org/apache/seata/rm/datasource/sql/struct/TableRecordsTest.java
 
b/rm-datasource/src/test/java/org/apache/seata/rm/datasource/sql/struct/TableRecordsTest.java
index db1d21d6b3..4cda675eea 100644
--- 
a/rm-datasource/src/test/java/org/apache/seata/rm/datasource/sql/struct/TableRecordsTest.java
+++ 
b/rm-datasource/src/test/java/org/apache/seata/rm/datasource/sql/struct/TableRecordsTest.java
@@ -20,22 +20,31 @@ import com.alibaba.druid.mock.MockStatement;
 import com.alibaba.druid.mock.MockStatementBase;
 import com.alibaba.druid.pool.DruidDataSource;
 import com.alibaba.druid.pool.DruidStatementConnection;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
 import com.google.common.collect.Lists;
 import org.apache.seata.common.exception.ShouldNeverHappenException;
 import org.apache.seata.rm.datasource.DataSourceProxy;
 import org.apache.seata.rm.datasource.DataSourceProxyTest;
 import org.apache.seata.rm.datasource.exception.TableMetaException;
 import org.apache.seata.rm.datasource.mock.MockDriver;
+import org.apache.seata.sqlparser.struct.ColumnMeta;
+import org.apache.seata.sqlparser.struct.IndexMeta;
+import org.apache.seata.sqlparser.struct.IndexType;
 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.BeforeEach;
 import org.junit.jupiter.api.Test;
 
+import java.io.IOException;
 import java.sql.Connection;
 import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.sql.Types;
+import java.time.OffsetDateTime;
+import java.time.ZoneOffset;
 import java.util.List;
 
 /**
@@ -292,4 +301,110 @@ public class TableRecordsTest {
         Assertions.assertThrows(UnsupportedOperationException.class, () -> 
empty.add(new Row()));
         Assertions.assertThrows(UnsupportedOperationException.class, 
empty::getTableMeta);
     }
+
+    private static Object[][] columnMetasOffsetDateTime = new Object[][] {
+        new Object[] {
+            "", "", "table_records_test", "id", Types.INTEGER, "INTEGER", 64, 
0, 10, 1, "", "", 0, 0, 64, 1, "NO", "YES"
+        },
+        new Object[] {
+            "",
+            "",
+            "table_records_test",
+            "time_col",
+            Types.TIMESTAMP_WITH_TIMEZONE,
+            "TIMESTAMP_WITH_TIMEZONE",
+            64,
+            0,
+            10,
+            0,
+            "",
+            "",
+            0,
+            0,
+            64,
+            2,
+            "YES",
+            "NO"
+        },
+    };
+
+    private static List<String> returnValueColumnLabelsOffsetDateTime = 
Lists.newArrayList("id", "time_col");
+
+    private static Object[][] returnValueOffsetDateTime = new Object[][] {
+        new Object[] {1, OffsetDateTime.of(2025, 1, 15, 10, 30, 45, 0, 
ZoneOffset.UTC)},
+    };
+
+    @Test
+    public void testBuildRecordsWithOffsetDateTime() throws SQLException, 
IOException {
+        MockDriver mockDriver = new MockDriver(
+                returnValueColumnLabelsOffsetDateTime,
+                returnValueOffsetDateTime,
+                columnMetasOffsetDateTime,
+                indexMetas);
+
+        DruidDataSource dataSource = new DruidDataSource();
+        dataSource.setUrl("jdbc:mock:offset");
+        dataSource.setDriver(mockDriver);
+
+        try (MockStatementBase mockStatement = new 
MockStatement(getPhysicsConnection(dataSource))) {
+            DataSourceProxy proxy = 
DataSourceProxyTest.getDataSourceProxy(dataSource);
+            TableMetaCacheFactory.getTableMetaCache(JdbcConstants.POSTGRESQL)
+                    .refresh(proxy.getPlainConnection(), 
proxy.getResourceId());
+            TableMeta tableMeta = 
TableMetaCacheFactory.getTableMetaCache(JdbcConstants.POSTGRESQL)
+                    .getTableMeta(proxy.getPlainConnection(), 
"table_records_test", proxy.getResourceId());
+            ColumnMeta idColumnMeta = new ColumnMeta();
+            idColumnMeta.setColumnName("id");
+            idColumnMeta.setDataType(java.sql.Types.INTEGER);
+            idColumnMeta.setTableName("table_records_test");
+            idColumnMeta.setIsAutoincrement("NO");
+            idColumnMeta.setIsNullAble("NO");
+
+            IndexMeta primaryIndex = new IndexMeta();
+            primaryIndex.setIndexName("PRIMARY");
+            primaryIndex.setIndextype(IndexType.PRIMARY);
+            primaryIndex.getValues().add(idColumnMeta);
+
+            tableMeta.getAllIndexes().put("PRIMARY", primaryIndex);
+            if (tableMeta.getColumnMeta("id") == null) {
+                tableMeta.getAllColumns().put("id", idColumnMeta);
+            }
+            ResultSet originalResultSet = 
mockDriver.executeQuery(mockStatement, "select * from table_records_test");
+            ResultSet proxyResultSet = (ResultSet) 
java.lang.reflect.Proxy.newProxyInstance(
+                    TableRecordsTest.class.getClassLoader(), new Class[] 
{ResultSet.class}, (p, method, args) -> {
+                        if ("getObject".equals(method.getName())
+                                && args.length == 2
+                                && args[1] == OffsetDateTime.class) {
+                            return originalResultSet.getObject((Integer) 
args[0]);
+                        }
+                        try {
+                            return method.invoke(originalResultSet, args);
+                        } catch (java.lang.reflect.InvocationTargetException 
e) {
+                            throw e.getTargetException();
+                        }
+                    });
+            TableRecords tableRecords = TableRecords.buildRecords(tableMeta, 
proxyResultSet);
+            Assertions.assertNotNull(tableRecords);
+            Assertions.assertEquals(1, tableRecords.size());
+            Row row = tableRecords.getRows().get(0);
+            Field timeField = row.getFields().stream()
+                    .filter(f -> "time_col".equalsIgnoreCase(f.getName()))
+                    .findFirst()
+                    .orElseThrow(() -> new RuntimeException("time_col not 
found"));
+            Assertions.assertEquals(Types.TIMESTAMP_WITH_TIMEZONE, 
timeField.getType());
+            Assertions.assertTrue(timeField.getValue() instanceof 
OffsetDateTime);
+
+            OffsetDateTime originalTime = (OffsetDateTime) 
timeField.getValue();
+
+            ObjectMapper mapper = new ObjectMapper()
+                    .registerModule(new JavaTimeModule())
+                    
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
+                    .disableDefaultTyping();
+
+            byte[] encodedBytes = mapper.writeValueAsBytes(originalTime);
+            OffsetDateTime deserializedTime = mapper.readValue(encodedBytes, 
OffsetDateTime.class);
+            Assertions.assertEquals(originalTime, deserializedTime);
+        } finally {
+            dataSource.close();
+        }
+    }
 }


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

Reply via email to