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]