Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@lokidundun
Copy link
Contributor

@lokidundun lokidundun commented Dec 29, 2025

Ⅰ. Describe what this PR did

handle timestamp with time zone in postgresql primary key

Ⅱ. Does this pull request fix one issue?

fixes #5624

Ⅲ. Why don't you add test cases (unit test/integration test)?

Ⅳ. Describe how to verify it

Ⅴ. Special notes for reviews

@codecov
Copy link

codecov bot commented Dec 29, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 71.26%. Comparing base (2897be9) to head (e8a26c5).
⚠️ Report is 1 commits behind head on 2.x.

Additional details and impacted files
@@            Coverage Diff            @@
##                2.x    #7908   +/-   ##
=========================================
  Coverage     71.26%   71.26%           
  Complexity      835      835           
=========================================
  Files          1294     1294           
  Lines         49550    49554    +4     
  Branches       5883     5884    +1     
=========================================
+ Hits          35311    35314    +3     
- Misses        11331    11333    +2     
+ Partials       2908     2907    -1     
Files with missing lines Coverage Δ
...e/seata/rm/datasource/sql/struct/TableRecords.java 68.46% <100.00%> (+0.57%) ⬆️
...m/datasource/undo/parser/JacksonUndoLogParser.java 69.01% <100.00%> (+0.24%) ⬆️

... and 7 files with indirect coverage changes

Impacted file tree graph

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request adds support for handling PostgreSQL's TIMESTAMP WITH TIMEZONE data type in primary keys and other columns. The fix addresses issue #5624 by adding proper handling for the standard JDBC Types.TIMESTAMP_WITH_TIMEZONE constant (used by PostgreSQL) alongside the existing Oracle-specific timestamp types.

Key Changes:

  • Added handling for Types.TIMESTAMP_WITH_TIMEZONE in TableRecords.buildRecords() to properly read and convert PostgreSQL timestamp values using OffsetDateTime
  • Integrated Jackson's JavaTimeModule to support serialization/deserialization of OffsetDateTime and other Java 8 time types in undo logs
  • Added comprehensive test coverage for the new timestamp type handling

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
rm-datasource/src/main/java/org/apache/seata/rm/datasource/sql/struct/TableRecords.java Added new condition to handle Types.TIMESTAMP_WITH_TIMEZONE by retrieving values as OffsetDateTime objects
rm-datasource/src/main/java/org/apache/seata/rm/datasource/undo/parser/JacksonUndoLogParser.java Registered JavaTimeModule to enable proper JSON serialization/deserialization of OffsetDateTime for undo logs
rm-datasource/pom.xml Added Jackson datatype dependencies (jackson-datatype-jdk8 and jackson-datatype-jsr310) to support Java 8 time types
rm-datasource/src/test/java/org/apache/seata/rm/datasource/sql/struct/TableRecordsTest.java Added test case testBuildRecordsWithOffsetDateTime to verify correct handling of timestamp with timezone fields
changes/en-us/2.x.md Added changelog entry documenting the PostgreSQL timestamp fix
changes/zh-cn/2.x.md Added Chinese changelog entry documenting the PostgreSQL timestamp fix

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 189 to 190
JavaTimeModule javaTimeModule = new JavaTimeModule();
mapper.registerModules(module, javaTimeModule);
Copy link

Copilot AI Dec 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The JavaTimeModule already provides serializers and deserializers for LocalDateTime and other Java 8 date/time types. Adding it alongside custom LocalDateTime serializers (localDateTimeSerializer and localDateTimeDeserializer) may cause conflicts or unexpected behavior. Consider removing the custom LocalDateTime serializer/deserializer registration on lines 184-185, or verify that the custom implementation is intentionally overriding the default JavaTimeModule behavior.

Copilot uses AI. Check for mistakes.
Comment on lines 75 to 78
<artifactId>jackson-datatype-jdk8</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
Copy link

Copilot AI Dec 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The jackson-datatype-jdk8 dependency appears to be unused in the codebase. This module provides support for JDK 8 features like Optional, OptionalInt, etc., but there's no code that explicitly uses these Jackson JDK 8 serializers. If this dependency is not needed, consider removing it to keep dependencies minimal. Only jackson-datatype-jsr310 is needed for OffsetDateTime and other java.time types.

Suggested change
<artifactId>jackson-datatype-jdk8</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>

Copilot uses AI. Check for mistakes.
Comment on lines 189 to 190
JavaTimeModule javaTimeModule = new JavaTimeModule();
mapper.registerModules(module, javaTimeModule);
Copy link

Copilot AI Dec 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding a test case in JacksonUndoLogParserTest to verify that OffsetDateTime values can be properly serialized and deserialized. While there's a test for LocalDateTime (lines 114-125), there's no test for OffsetDateTime which is now supported through the JavaTimeModule. This would ensure that undo logs containing PostgreSQL TIMESTAMP WITH TIMEZONE fields can be correctly persisted and restored.

Copilot uses AI. Check for mistakes.
Comment on lines 326 to 327
new Object[] {1, OffsetDateTime.now()},
};
Copy link

Copilot AI Dec 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using OffsetDateTime.now() creates a non-deterministic test value which can make test results harder to reproduce and debug. Consider using a fixed OffsetDateTime value instead, such as OffsetDateTime.of(2024, 1, 15, 10, 30, 45, 0, ZoneOffset.UTC) to ensure consistent and reproducible test behavior.

Copilot uses AI. Check for mistakes.
Comment on lines 341 to 376
MockStatementBase mockStatement = new MockStatement(getPhysicsConnection(dataSource));
DataSourceProxy proxy = DataSourceProxyTest.getDataSourceProxy(dataSource);

TableMetaCacheFactory.getTableMetaCache(JdbcConstants.MYSQL)
.refresh(proxy.getPlainConnection(), proxy.getResourceId());

TableMeta tableMeta = TableMetaCacheFactory.getTableMetaCache(JdbcConstants.MYSQL)
.getTableMeta(proxy.getPlainConnection(), "table_records_test", proxy.getResourceId());

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);
Copy link

Copilot AI Dec 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This MockStatement is not always closed on method exit.

Suggested change
MockStatementBase mockStatement = new MockStatement(getPhysicsConnection(dataSource));
DataSourceProxy proxy = DataSourceProxyTest.getDataSourceProxy(dataSource);
TableMetaCacheFactory.getTableMetaCache(JdbcConstants.MYSQL)
.refresh(proxy.getPlainConnection(), proxy.getResourceId());
TableMeta tableMeta = TableMetaCacheFactory.getTableMetaCache(JdbcConstants.MYSQL)
.getTableMeta(proxy.getPlainConnection(), "table_records_test", proxy.getResourceId());
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);
try (MockStatementBase mockStatement = new MockStatement(getPhysicsConnection(dataSource))) {
DataSourceProxy proxy = DataSourceProxyTest.getDataSourceProxy(dataSource);
TableMetaCacheFactory.getTableMetaCache(JdbcConstants.MYSQL)
.refresh(proxy.getPlainConnection(), proxy.getResourceId());
TableMeta tableMeta = TableMetaCacheFactory.getTableMetaCache(JdbcConstants.MYSQL)
.getTableMeta(proxy.getPlainConnection(), "table_records_test", proxy.getResourceId());
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);
}

Copilot uses AI. Check for mistakes.
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 344 to 346
TableMetaCacheFactory.getTableMetaCache(JdbcConstants.MYSQL)
.refresh(proxy.getPlainConnection(), proxy.getResourceId());
TableMeta tableMeta = TableMetaCacheFactory.getTableMetaCache(JdbcConstants.MYSQL)
Copy link

Copilot AI Dec 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test is using JdbcConstants.MYSQL for a test that is specifically designed to test PostgreSQL's TIMESTAMP_WITH_TIMEZONE handling. This is misleading and could lead to incorrect test behavior. The test should use JdbcConstants.POSTGRESQL instead to properly reflect the database being tested.

Suggested change
TableMetaCacheFactory.getTableMetaCache(JdbcConstants.MYSQL)
.refresh(proxy.getPlainConnection(), proxy.getResourceId());
TableMeta tableMeta = TableMetaCacheFactory.getTableMetaCache(JdbcConstants.MYSQL)
TableMetaCacheFactory.getTableMetaCache(JdbcConstants.POSTGRESQL)
.refresh(proxy.getPlainConnection(), proxy.getResourceId());
TableMeta tableMeta = TableMetaCacheFactory.getTableMetaCache(JdbcConstants.POSTGRESQL)

Copilot uses AI. Check for mistakes.
Comment on lines 330 to 373
@Test
public void testBuildRecordsWithOffsetDateTime() throws SQLException {
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.MYSQL)
.refresh(proxy.getPlainConnection(), proxy.getResourceId());
TableMeta tableMeta = TableMetaCacheFactory.getTableMetaCache(JdbcConstants.MYSQL)
.getTableMeta(proxy.getPlainConnection(), "table_records_test", proxy.getResourceId());
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);
}
}
Copy link

Copilot AI Dec 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test lacks coverage for the serialization and deserialization of OffsetDateTime through the JacksonUndoLogParser. While the test verifies that OffsetDateTime can be retrieved from the ResultSet and stored in a Field, it doesn't test the critical path of serializing/deserializing this data through the undo log, which is the primary use case affected by the JavaTimeModule registration in JacksonUndoLogParser.

Copilot uses AI. Check for mistakes.
@funky-eyes funky-eyes added this to the 2.6.0 milestone Dec 31, 2025
@funky-eyes funky-eyes added type: bug Category issues or prs related to bug. module/rm-datasource rm-datasource module mode: AT AT transaction mode multilingual labels Dec 31, 2025
Copy link
Contributor

@funky-eyes funky-eyes left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@funky-eyes funky-eyes merged commit 5923c62 into apache:2.x Jan 4, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

mode: AT AT transaction mode module/rm-datasource rm-datasource module multilingual type: bug Category issues or prs related to bug.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

postgresql中如果主键中包含timestampe with time zone列时,seata at事务出错

3 participants