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

jimin 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 55bd893dbd optimize: add support for Jackson serialization and 
deserialization of PostgreSQL array types (#7669)
55bd893dbd is described below

commit 55bd893dbdf145bc215c516c5a22bc415c7cd05f
Author: maple <[email protected]>
AuthorDate: Mon Sep 29 10:15:12 2025 +0800

    optimize: add support for Jackson serialization and deserialization of 
PostgreSQL array types (#7669)
---
 changes/en-us/2.x.md                               |   1 +
 changes/zh-cn/2.x.md                               |   1 +
 .../undo/parser/JacksonUndoLogParser.java          | 116 ++++++++++++-
 .../undo/parser/JacksonUndoLogParserTest.java      | 181 +++++++++++++++++++++
 4 files changed, 298 insertions(+), 1 deletion(-)

diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md
index d30c2bd7d6..32658d7018 100644
--- a/changes/en-us/2.x.md
+++ b/changes/en-us/2.x.md
@@ -25,6 +25,7 @@ Add changes here for all PR submitted to the 2.x branch.
 - [[#7492](https://github.com/apache/incubator-seata/pull/7492)] upgrade HTTP 
client in common module to support HTTP/2
 - [[#7551](https://github.com/apache/incubator-seata/pull/7551)] XAUtils add 
support for DM Database
 - [[#7559](https://github.com/apache/incubator-seata/pull/7559)] Introduce 
Cleanup API for TableMetaRefreshHolder Instance
+- [[#7669](https://github.com/apache/incubator-seata/pull/7669)] add support 
for Jackson serialization and deserialization of PostgreSQL array types
 
 ### bugfix:
 
diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md
index 1fcb5dbb73..7bdf2afd13 100644
--- a/changes/zh-cn/2.x.md
+++ b/changes/zh-cn/2.x.md
@@ -25,6 +25,7 @@
 - [[#7492](https://github.com/apache/incubator-seata/pull/7492)] 升级 common 
模块中的 HTTP 客户端以支持 HTTP/2
 - [[#7551](https://github.com/apache/incubator-seata/pull/7551)] XAUtils支持达梦数据库
 - [[#7559](https://github.com/apache/incubator-seata/pull/7559)] 为 
TableMetaRefreshHolder 实例引入清理 API
+- [[#7669](https://github.com/apache/incubator-seata/pull/7669)] 添加对 Jackson 
序列化和反序列化 PostgreSQL 数组类型的支持
 
 
 ### bugfix:
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 ae399e884c..a9944f1d9b 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
@@ -25,6 +25,7 @@ import com.fasterxml.jackson.core.type.WritableTypeId;
 import com.fasterxml.jackson.databind.DeserializationContext;
 import com.fasterxml.jackson.databind.DeserializationFeature;
 import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.JsonSerializer;
 import com.fasterxml.jackson.databind.MapperFeature;
 import com.fasterxml.jackson.databind.ObjectMapper;
@@ -40,6 +41,7 @@ import org.apache.seata.common.loader.EnhancedServiceLoader;
 import org.apache.seata.common.loader.EnhancedServiceNotFoundException;
 import org.apache.seata.common.loader.LoadLevel;
 import org.apache.seata.common.util.CollectionUtils;
+import org.apache.seata.rm.datasource.sql.serial.SerialArray;
 import org.apache.seata.rm.datasource.undo.BranchUndoLog;
 import org.apache.seata.rm.datasource.undo.UndoLogParser;
 import org.apache.seata.rm.datasource.undo.parser.spi.JacksonSerializer;
@@ -136,6 +138,16 @@ public class JacksonUndoLogParser implements 
UndoLogParser, Initialize {
      */
     private final JsonDeserializer dmdbTimestampDeserializer = new 
DmdbTimestampDeserializer();
 
+    /**
+     * customize serializer for 
org.apache.seata.rm.datasource.sql.serial.SerialArray
+     */
+    private final JsonSerializer serialArraySerializer = new 
SerialArraySerializer();
+
+    /**
+     * customize deserializer for 
org.apache.seata.rm.datasource.sql.serial.SerialArray
+     */
+    private final JsonDeserializer serialArrayDeserializer = new 
SerialArrayDeserializer();
+
     @Override
     public void init() {
         try {
@@ -170,6 +182,8 @@ public class JacksonUndoLogParser implements UndoLogParser, 
Initialize {
         module.addDeserializer(SerialClob.class, clobDeserializer);
         module.addSerializer(LocalDateTime.class, localDateTimeSerializer);
         module.addDeserializer(LocalDateTime.class, localDateTimeDeserializer);
+        module.addSerializer(SerialArray.class, serialArraySerializer);
+        module.addDeserializer(SerialArray.class, serialArrayDeserializer);
         registerDmdbTimestampModuleIfPresent();
         mapper.registerModule(module);
         mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, 
false);
@@ -483,7 +497,7 @@ public class JacksonUndoLogParser implements UndoLogParser, 
Initialize {
         }
     }
 
-    public class DmdbTimestampDeserializer extends JsonDeserializer<Object> {
+    private class DmdbTimestampDeserializer extends JsonDeserializer<Object> {
 
         @Override
         public Object deserialize(JsonParser p, DeserializationContext ctxt) {
@@ -532,4 +546,104 @@ public class JacksonUndoLogParser implements 
UndoLogParser, Initialize {
         Objects.requireNonNull(zoneId, "zoneId must be not null");
         JacksonUndoLogParser.zoneId = zoneId;
     }
+
+    /**
+     * the class of serialize SerialArray type
+     */
+    private static class SerialArraySerializer extends 
JsonSerializer<SerialArray> {
+
+        @Override
+        public void serializeWithType(
+                SerialArray serialArray,
+                JsonGenerator gen,
+                SerializerProvider serializers,
+                TypeSerializer typeSerializer)
+                throws IOException {
+            WritableTypeId typeIdDef =
+                    typeSerializer.writeTypePrefix(gen, 
typeSerializer.typeId(serialArray, JsonToken.START_OBJECT));
+            serializeValue(serialArray, gen, serializers);
+            typeSerializer.writeTypeSuffix(gen, typeIdDef);
+        }
+
+        @Override
+        public void serialize(SerialArray serialArray, JsonGenerator gen, 
SerializerProvider serializers)
+                throws IOException {
+            gen.writeStartObject();
+            serializeValue(serialArray, gen, serializers);
+            gen.writeEndObject();
+        }
+
+        private void serializeValue(SerialArray serialArray, JsonGenerator 
gen, SerializerProvider serializers)
+                throws IOException {
+            gen.writeFieldName("baseType");
+            try {
+                gen.writeNumber(serialArray.getBaseType());
+            } catch (SQLException e) {
+                gen.writeNull();
+            }
+            gen.writeFieldName("baseTypeName");
+            try {
+                gen.writeString(serialArray.getBaseTypeName());
+            } catch (SQLException e) {
+                gen.writeNull();
+            }
+            gen.writeFieldName("elements");
+            try {
+                Object[] elements = serialArray.getElements();
+                gen.writeStartArray();
+                if (elements != null) {
+                    for (Object element : elements) {
+                        gen.writeObject(element);
+                    }
+                }
+                gen.writeEndArray();
+            } catch (Exception e) {
+                gen.writeNull();
+            }
+        }
+    }
+
+    /**
+     * the class of deserialize SerialArray type
+     */
+    private static class SerialArrayDeserializer extends 
JsonDeserializer<SerialArray> {
+        @Override
+        public SerialArray deserialize(JsonParser p, DeserializationContext 
ctxt) throws IOException {
+            try {
+                JsonNode node = p.getCodec().readTree(p);
+                SerialArray serialArray = new SerialArray();
+
+                if (node.has("baseType") && !node.get("baseType").isNull()) {
+                    serialArray.setBaseType(node.get("baseType").asInt());
+                }
+
+                if (node.has("baseTypeName") && 
!node.get("baseTypeName").isNull()) {
+                    
serialArray.setBaseTypeName(node.get("baseTypeName").asText());
+                }
+
+                if (node.has("elements") && node.get("elements").isArray()) {
+                    JsonNode elementsNode = node.get("elements");
+                    Object[] elements = new Object[elementsNode.size()];
+                    for (int i = 0; i < elementsNode.size(); i++) {
+                        JsonNode elementNode = elementsNode.get(i);
+                        if (elementNode.isNull()) {
+                            elements[i] = null;
+                        } else if (elementNode.isNumber()) {
+                            elements[i] = elementNode.asLong();
+                        } else if (elementNode.isTextual()) {
+                            elements[i] = elementNode.asText();
+                        } else {
+                            elements[i] = elementNode;
+                        }
+                    }
+                    serialArray.setElements(elements);
+                }
+
+                return serialArray;
+            } catch (Exception e) {
+                LOGGER.error("deserialize SerialArray error: {}", 
e.getMessage(), e);
+                return null;
+            }
+        }
+    }
 }
diff --git 
a/rm-datasource/src/test/java/org/apache/seata/rm/datasource/undo/parser/JacksonUndoLogParserTest.java
 
b/rm-datasource/src/test/java/org/apache/seata/rm/datasource/undo/parser/JacksonUndoLogParserTest.java
index 37e804fc72..921f7024b0 100644
--- 
a/rm-datasource/src/test/java/org/apache/seata/rm/datasource/undo/parser/JacksonUndoLogParserTest.java
+++ 
b/rm-datasource/src/test/java/org/apache/seata/rm/datasource/undo/parser/JacksonUndoLogParserTest.java
@@ -19,6 +19,7 @@ package org.apache.seata.rm.datasource.undo.parser;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import org.apache.seata.common.loader.EnhancedServiceLoader;
 import org.apache.seata.rm.datasource.DataCompareUtils;
+import org.apache.seata.rm.datasource.sql.serial.SerialArray;
 import org.apache.seata.rm.datasource.sql.struct.Field;
 import org.apache.seata.rm.datasource.undo.AbstractUndoLogManager;
 import org.apache.seata.rm.datasource.undo.BaseUndoLogParserTest;
@@ -33,14 +34,18 @@ import java.io.IOException;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.math.BigDecimal;
+import java.sql.Array;
 import java.sql.JDBCType;
+import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.sql.Timestamp;
+import java.sql.Types;
 import java.time.Instant;
 import java.time.LocalDateTime;
 import java.time.ZoneId;
 import java.time.ZonedDateTime;
 import java.util.Date;
+import java.util.Map;
 
 import static org.mockito.Mockito.mockStatic;
 
@@ -181,6 +186,182 @@ public class JacksonUndoLogParserTest extends 
BaseUndoLogParserTest {
         }
     }
 
+    @Test
+    public void testSerializeAndDeserializeSerialArray()
+            throws NoSuchFieldException, IllegalAccessException, IOException, 
SQLException {
+        // get the jackson mapper
+        java.lang.reflect.Field reflectField = 
parser.getClass().getDeclaredField("mapper");
+        reflectField.setAccessible(true);
+        ObjectMapper mapper = (ObjectMapper) reflectField.get(parser);
+
+        // create a mock Array object for testing SerialArray
+        Array mockArray = new MockArray();
+        SerialArray serialArray = new SerialArray(mockArray);
+
+        // test SerialArray with BIGINT array (PostgreSQL _int8 type)
+        Field field = new Field("dept_ids", 
JDBCType.ARRAY.getVendorTypeNumber(), serialArray);
+        byte[] bytes = mapper.writeValueAsBytes(field);
+        Field sameField = mapper.readValue(bytes, Field.class);
+        Assertions.assertTrue(DataCompareUtils.isFieldEquals(field, 
sameField).getResult());
+
+        // verify the SerialArray properties are correctly 
serialized/deserialized
+        SerialArray deserializedArray = (SerialArray) sameField.getValue();
+        Assertions.assertEquals(serialArray.getBaseType(), 
deserializedArray.getBaseType());
+        Assertions.assertEquals(serialArray.getBaseTypeName(), 
deserializedArray.getBaseTypeName());
+        Assertions.assertArrayEquals(serialArray.getElements(), 
deserializedArray.getElements());
+    }
+
+    @Test
+    public void testSerializeAndDeserializeSerialArrayWithNulls()
+            throws NoSuchFieldException, IllegalAccessException, IOException, 
SQLException {
+        // get the jackson mapper
+        java.lang.reflect.Field reflectField = 
parser.getClass().getDeclaredField("mapper");
+        reflectField.setAccessible(true);
+        ObjectMapper mapper = (ObjectMapper) reflectField.get(parser);
+
+        // create SerialArray with null elements
+        Array mockArrayWithNulls = new MockArrayWithNulls();
+        SerialArray serialArray = new SerialArray(mockArrayWithNulls);
+
+        Field field = new Field("nullable_array", 
JDBCType.ARRAY.getVendorTypeNumber(), serialArray);
+        byte[] bytes = mapper.writeValueAsBytes(field);
+        Field sameField = mapper.readValue(bytes, Field.class);
+
+        Assertions.assertTrue(DataCompareUtils.isFieldEquals(field, 
sameField).getResult());
+
+        // verify null elements are handled correctly
+        SerialArray deserializedArray = (SerialArray) sameField.getValue();
+        Object[] elements = deserializedArray.getElements();
+        Assertions.assertEquals(3, elements.length);
+        Assertions.assertEquals(1L, elements[0]);
+        Assertions.assertNull(elements[1]);
+        Assertions.assertEquals(3L, elements[2]);
+    }
+
+    /**
+     * Mock Array class for testing SerialArray serialization
+     */
+    private static class MockArray implements Array {
+        private final Object[] elements = {1L, 2L, 3L, 4L, 5L};
+
+        @Override
+        public String getBaseTypeName() throws SQLException {
+            return "int8";
+        }
+
+        @Override
+        public int getBaseType() throws SQLException {
+            return Types.BIGINT;
+        }
+
+        @Override
+        public Object getArray() throws SQLException {
+            return elements;
+        }
+
+        @Override
+        public Object getArray(Map<String, Class<?>> map) throws SQLException {
+            return elements;
+        }
+
+        @Override
+        public Object getArray(long index, int count) throws SQLException {
+            return elements;
+        }
+
+        @Override
+        public Object getArray(long index, int count, Map<String, Class<?>> 
map) throws SQLException {
+            return elements;
+        }
+
+        @Override
+        public ResultSet getResultSet() throws SQLException {
+            return null;
+        }
+
+        @Override
+        public ResultSet getResultSet(Map<String, Class<?>> map) throws 
SQLException {
+            return null;
+        }
+
+        @Override
+        public ResultSet getResultSet(long index, int count) throws 
SQLException {
+            return null;
+        }
+
+        @Override
+        public ResultSet getResultSet(long index, int count, Map<String, 
Class<?>> map) throws SQLException {
+            return null;
+        }
+
+        @Override
+        public void free() throws SQLException {
+            // do nothing
+        }
+    }
+
+    /**
+     * Mock Array class with null elements for testing edge cases
+     */
+    private static class MockArrayWithNulls implements Array {
+        private final Object[] elements = {1L, null, 3L};
+
+        @Override
+        public String getBaseTypeName() throws SQLException {
+            return "int8";
+        }
+
+        @Override
+        public int getBaseType() throws SQLException {
+            return Types.BIGINT;
+        }
+
+        @Override
+        public Object getArray() throws SQLException {
+            return elements;
+        }
+
+        @Override
+        public Object getArray(Map<String, Class<?>> map) throws SQLException {
+            return elements;
+        }
+
+        @Override
+        public Object getArray(long index, int count) throws SQLException {
+            return elements;
+        }
+
+        @Override
+        public Object getArray(long index, int count, Map<String, Class<?>> 
map) throws SQLException {
+            return elements;
+        }
+
+        @Override
+        public ResultSet getResultSet() throws SQLException {
+            return null;
+        }
+
+        @Override
+        public ResultSet getResultSet(Map<String, Class<?>> map) throws 
SQLException {
+            return null;
+        }
+
+        @Override
+        public ResultSet getResultSet(long index, int count) throws 
SQLException {
+            return null;
+        }
+
+        @Override
+        public ResultSet getResultSet(long index, int count, Map<String, 
Class<?>> map) throws SQLException {
+            return null;
+        }
+
+        @Override
+        public void free() throws SQLException {
+            // do nothing
+        }
+    }
+
     @Override
     public UndoLogParser getParser() {
         return parser;


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

Reply via email to