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 f3aa087b7e optimize: add fastjson support for serialization and 
deserialization of PostgreSQL array types (#7711)
f3aa087b7e is described below

commit f3aa087b7eae6e77f1da35d524d3fa13b48dfbe7
Author: maple <[email protected]>
AuthorDate: Wed Oct 22 09:43:06 2025 +0800

    optimize: add fastjson support for serialization and deserialization of 
PostgreSQL array types (#7711)
---
 changes/en-us/2.x.md                               |   3 +
 changes/zh-cn/2.x.md                               |   2 +
 .../undo/parser/FastjsonUndoLogParser.java         | 191 ++++++++++++++-
 .../undo/parser/FastjsonUndoLogParserTest.java     | 260 +++++++++++++++++++++
 4 files changed, 454 insertions(+), 2 deletions(-)

diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md
index 22c90f66bc..7705bc0bfa 100644
--- a/changes/en-us/2.x.md
+++ b/changes/en-us/2.x.md
@@ -65,6 +65,9 @@ Add changes here for all PR submitted to the 2.x branch.
 - [[#7645](https://github.com/seata/seata/pull/7645)] simplifying the relevant 
transport.* configuration types
 - [[#7673](https://github.com/apache/incubator-seata/pull/7673)] bump 
@babel/runtime from ^7.26.10 to ^7.27.0
 - [[#7689](https://github.com/apache/incubator-seata/pull/7689)] optimize 
source release
+- [[#7711](https://github.com/apache/incubator-seata/pull/7711)] add fastjson 
support for serialization and deserialization of PostgreSQL array types
+
+
 
 
 ### security:
diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md
index 8885012973..a34a0e3b19 100644
--- a/changes/zh-cn/2.x.md
+++ b/changes/zh-cn/2.x.md
@@ -64,6 +64,8 @@
 - [[#7645](https://github.com/seata/seata/pull/7645)] 简化相关的 transport.* 配置项类型
 - [[#7673](https://github.com/apache/incubator-seata/pull/7673)] 升级 
@babel/runtime ^7.26.10 到 ^7.27.0
 - [[#7689](https://github.com/apache/incubator-seata/pull/7689)] 优化 source 
release
+- [[#7711](https://github.com/apache/incubator-seata/pull/7711)] 添加 fastjson 对 
PostgreSQL 数组类型的序列化和反序列化的支持
+
 
 
 ### security:
diff --git 
a/rm-datasource/src/main/java/org/apache/seata/rm/datasource/undo/parser/FastjsonUndoLogParser.java
 
b/rm-datasource/src/main/java/org/apache/seata/rm/datasource/undo/parser/FastjsonUndoLogParser.java
index 4c9f8aa3d8..624338be98 100644
--- 
a/rm-datasource/src/main/java/org/apache/seata/rm/datasource/undo/parser/FastjsonUndoLogParser.java
+++ 
b/rm-datasource/src/main/java/org/apache/seata/rm/datasource/undo/parser/FastjsonUndoLogParser.java
@@ -17,13 +17,41 @@
 package org.apache.seata.rm.datasource.undo.parser;
 
 import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.alibaba.fastjson.parser.DefaultJSONParser;
+import com.alibaba.fastjson.parser.JSONToken;
+import com.alibaba.fastjson.parser.ParserConfig;
+import com.alibaba.fastjson.parser.deserializer.ObjectDeserializer;
+import com.alibaba.fastjson.serializer.JSONSerializer;
+import com.alibaba.fastjson.serializer.ObjectSerializer;
+import com.alibaba.fastjson.serializer.SerializeConfig;
+import com.alibaba.fastjson.serializer.SerializeWriter;
 import com.alibaba.fastjson.serializer.SerializerFeature;
 import com.alibaba.fastjson.serializer.SimplePropertyPreFilter;
 import org.apache.seata.common.Constants;
 import org.apache.seata.common.executor.Initialize;
 import org.apache.seata.common.loader.LoadLevel;
+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.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.lang.reflect.Type;
+import java.math.BigDecimal;
+import java.sql.SQLException;
+
+import static java.sql.Types.BIGINT;
+import static java.sql.Types.DECIMAL;
+import static java.sql.Types.DOUBLE;
+import static java.sql.Types.FLOAT;
+import static java.sql.Types.INTEGER;
+import static java.sql.Types.NUMERIC;
+import static java.sql.Types.REAL;
+import static java.sql.Types.SMALLINT;
+import static java.sql.Types.TINYINT;
 
 /**
  * The type Json based undo log parser.
@@ -34,11 +62,19 @@ public class FastjsonUndoLogParser implements 
UndoLogParser, Initialize {
 
     public static final String NAME = "fastjson";
 
+    private static final Logger LOGGER = 
LoggerFactory.getLogger(FastjsonUndoLogParser.class);
+
     private final SimplePropertyPreFilter filter = new 
SimplePropertyPreFilter();
+    final SerializeConfig serializeConfig = new SerializeConfig();
+    final ParserConfig parserConfig = new ParserConfig();
 
     @Override
     public void init() {
         filter.getExcludes().add("tableMeta");
+
+        // Register SerialArray serializer and deserializer
+        serializeConfig.put(SerialArray.class, new SerialArraySerializer());
+        parserConfig.putDeserializer(SerialArray.class, new 
SerialArrayDeserializer());
     }
 
     @Override
@@ -54,13 +90,164 @@ public class FastjsonUndoLogParser implements 
UndoLogParser, Initialize {
     @Override
     public byte[] encode(BranchUndoLog branchUndoLog) {
         String json = JSON.toJSONString(
-                branchUndoLog, filter, SerializerFeature.WriteClassName, 
SerializerFeature.WriteDateUseDateFormat);
+                branchUndoLog,
+                serializeConfig,
+                filter,
+                SerializerFeature.WriteClassName,
+                SerializerFeature.WriteDateUseDateFormat);
         return json.getBytes(Constants.DEFAULT_CHARSET);
     }
 
     @Override
     public BranchUndoLog decode(byte[] bytes) {
         String text = new String(bytes, Constants.DEFAULT_CHARSET);
-        return JSON.parseObject(text, BranchUndoLog.class);
+        return JSON.parseObject(text, BranchUndoLog.class, parserConfig);
+    }
+
+    /**
+     * Custom Fastjson serializer for SerialArray
+     * Manually construct JSON structure while letting serializer handle 
elements properly
+     */
+    private static class SerialArraySerializer implements ObjectSerializer {
+        @Override
+        public void write(JSONSerializer serializer, Object object, Object 
fieldName, Type fieldType, int features)
+                throws IOException {
+            if (object == null) {
+                serializer.writeNull();
+                return;
+            }
+
+            SerialArray serialArray = (SerialArray) object;
+            SerializeWriter out = serializer.getWriter();
+
+            out.write('{');
+
+            // Write the correct @type information to ensure the deserializer 
is called
+            out.writeFieldName("@type");
+            out.writeString(serialArray.getClass().getName());
+            out.write(',');
+
+            // Write baseType
+            out.writeFieldName("baseType");
+            try {
+                out.writeInt(serialArray.getBaseType());
+            } catch (SQLException e) {
+                out.writeNull();
+            }
+            out.write(',');
+
+            // Write baseTypeName
+            out.writeFieldName("baseTypeName");
+            try {
+                String baseTypeName = serialArray.getBaseTypeName();
+                if (baseTypeName != null) {
+                    out.writeString(baseTypeName);
+                } else {
+                    out.writeNull();
+                }
+            } catch (SQLException e) {
+                out.writeNull();
+            }
+            out.write(',');
+
+            // Writing elements - using a serializer to ensure correct JSON 
formatting and type handling
+            out.writeFieldName("elements");
+            serializer.write(serialArray.getElements());
+
+            out.write('}');
+        }
+    }
+
+    /**
+     * Custom Fastjson deserializer for SerialArray
+     * Enhanced with comprehensive type mapping based on SQL baseType
+     */
+    private static class SerialArrayDeserializer implements ObjectDeserializer 
{
+        @Override
+        public SerialArray deserialze(DefaultJSONParser parser, Type type, 
Object fieldName) {
+            try {
+                JSONObject json = parser.parseObject();
+                if (json == null) {
+                    return null;
+                }
+
+                SerialArray serialArray = new SerialArray();
+
+                // Remove the @type field if it exists (Fastjson automatically 
adds this)
+                json.remove("@type");
+
+                // Extract baseType for type conversion
+                int baseType = 0;
+                Object baseTypeObj = json.get("baseType");
+                if (baseTypeObj instanceof Number) {
+                    baseType = ((Number) baseTypeObj).intValue();
+                    serialArray.setBaseType(baseType);
+                }
+
+                Object baseTypeName = json.get("baseTypeName");
+                if (baseTypeName instanceof String) {
+                    serialArray.setBaseTypeName((String) baseTypeName);
+                }
+
+                Object elementsObj = json.get("elements");
+                if (elementsObj instanceof JSONArray) {
+                    JSONArray elementsArray = (JSONArray) elementsObj;
+                    Object[] elements = new Object[elementsArray.size()];
+                    for (int i = 0; i < elementsArray.size(); i++) {
+                        Object element = elementsArray.get(i);
+                        elements[i] = convertElementByBaseType(element, 
baseType);
+                    }
+                    serialArray.setElements(elements);
+                }
+
+                return serialArray;
+            } catch (Exception e) {
+                LOGGER.error("deserialize SerialArray error: {}", 
e.getMessage(), e);
+                return null;
+            }
+        }
+
+        /**
+         * Convert element to appropriate Java type based on SQL baseType
+         */
+        private Object convertElementByBaseType(Object element, int baseType) {
+            if (element == null) {
+                return null;
+            }
+
+            // If not a number, return as-is (String, Boolean, etc.)
+            if (!(element instanceof Number)) {
+                return element;
+            }
+
+            Number numElement = (Number) element;
+
+            // Convert based on SQL type constants
+            switch (baseType) {
+                case TINYINT:
+                    return numElement.byteValue();
+                case SMALLINT:
+                    return numElement.shortValue();
+                case INTEGER:
+                    return numElement.intValue();
+                case BIGINT:
+                    return numElement.longValue();
+                case REAL:
+                case FLOAT:
+                    return numElement.floatValue();
+                case DOUBLE:
+                    return numElement.doubleValue();
+                case DECIMAL:
+                case NUMERIC:
+                    return new BigDecimal(numElement.toString());
+                default:
+                    return element;
+            }
+        }
+
+        @Override
+        public int getFastMatchToken() {
+            return JSONToken.LBRACE;
+        }
     }
 }
diff --git 
a/rm-datasource/src/test/java/org/apache/seata/rm/datasource/undo/parser/FastjsonUndoLogParserTest.java
 
b/rm-datasource/src/test/java/org/apache/seata/rm/datasource/undo/parser/FastjsonUndoLogParserTest.java
index d2a83b2d38..a02219132b 100644
--- 
a/rm-datasource/src/test/java/org/apache/seata/rm/datasource/undo/parser/FastjsonUndoLogParserTest.java
+++ 
b/rm-datasource/src/test/java/org/apache/seata/rm/datasource/undo/parser/FastjsonUndoLogParserTest.java
@@ -20,6 +20,8 @@ import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.serializer.SerializeConfig;
 import com.alibaba.fastjson.serializer.ValueFilter;
 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.sql.struct.KeyType;
 import org.apache.seata.rm.datasource.sql.struct.Row;
@@ -30,19 +32,32 @@ import org.apache.seata.rm.datasource.undo.SQLUndoLog;
 import org.apache.seata.rm.datasource.undo.UndoLogParser;
 import org.apache.seata.sqlparser.SQLType;
 import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
+import java.io.IOException;
 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.util.ArrayList;
 import java.util.List;
+import java.util.Map;
 
 public class FastjsonUndoLogParserTest extends BaseUndoLogParserTest {
 
     FastjsonUndoLogParser parser =
             (FastjsonUndoLogParser) 
EnhancedServiceLoader.load(UndoLogParser.class, FastjsonUndoLogParser.NAME);
 
+    @BeforeEach
+    public void setUp() {
+        // Ensure init() is called to register SerialArray serializers
+        parser.init();
+    }
+
     @Override
     public UndoLogParser getParser() {
         return parser;
@@ -121,6 +136,251 @@ public class FastjsonUndoLogParserTest extends 
BaseUndoLogParserTest {
         Assertions.assertTrue(value2 instanceof BigDecimal);
     }
 
+    @Test
+    public void testDirectSerialArraySerialization() throws SQLException {
+        // Test direct SerialArray serialization without UndoLog wrapper
+        Array mockArray = new MockArray();
+        SerialArray serialArray = new SerialArray(mockArray);
+
+        // Test with our custom serializers
+        String json = JSON.toJSONString(serialArray, parser.serializeConfig);
+        System.out.println("Direct SerialArray JSON: " + json);
+
+        // Do not specify a specific type, let Fastjson automatically 
determine it based on the @type information
+        SerialArray deserialized = (SerialArray) JSON.parse(json, 
parser.parserConfig);
+
+        System.out.println("Original - baseType: " + serialArray.getBaseType() 
+ ", baseTypeName: "
+                + serialArray.getBaseTypeName() + ", elements: "
+                + java.util.Arrays.toString(serialArray.getElements()));
+        System.out.println("Deserialized - baseType: " + 
deserialized.getBaseType() + ", baseTypeName: "
+                + deserialized.getBaseTypeName() + ", elements: "
+                + java.util.Arrays.toString(deserialized.getElements()));
+
+        Assertions.assertEquals(serialArray.getBaseType(), 
deserialized.getBaseType());
+        Assertions.assertEquals(serialArray.getBaseTypeName(), 
deserialized.getBaseTypeName());
+        Assertions.assertArrayEquals(serialArray.getElements(), 
deserialized.getElements());
+    }
+
+    @Test
+    public void testSerializeAndDeserializeSerialArray() throws IOException, 
SQLException {
+        // 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);
+
+        // Debug: Print JSON to see if our serializer is being used
+        byte[] bytes = getParser().encode(createTestUndoLog(field));
+        String jsonString = new String(bytes);
+        System.out.println("Serialized JSON: " + jsonString);
+
+        BranchUndoLog decodedLog = getParser().decode(bytes);
+        Field decodedField = getFieldFromLog(decodedLog);
+
+        // Debug information: print original and deserialized SerialArray 
properties
+        SerialArray original = (SerialArray) field.getValue();
+        SerialArray deserialized = (SerialArray) decodedField.getValue();
+
+        System.out.println("Original - baseType: " + original.getBaseType() + 
", baseTypeName: "
+                + original.getBaseTypeName() + ", elements: "
+                + java.util.Arrays.toString(original.getElements()));
+        System.out.println("Deserialized - baseType: " + 
deserialized.getBaseType() + ", baseTypeName: "
+                + deserialized.getBaseTypeName() + ", elements: "
+                + java.util.Arrays.toString(deserialized.getElements()));
+
+        Assertions.assertTrue(
+                DataCompareUtils.isFieldEquals(field, 
decodedField).getResult());
+
+        // verify the SerialArray properties are correctly 
serialized/deserialized
+        SerialArray deserializedArray = (SerialArray) decodedField.getValue();
+        Assertions.assertEquals(serialArray.getBaseType(), 
deserializedArray.getBaseType());
+        Assertions.assertEquals(serialArray.getBaseTypeName(), 
deserializedArray.getBaseTypeName());
+        Assertions.assertArrayEquals(serialArray.getElements(), 
deserializedArray.getElements());
+    }
+
+    @Test
+    public void testSerializeAndDeserializeSerialArrayWithNulls() throws 
IOException, SQLException {
+        // 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 = getParser().encode(createTestUndoLog(field));
+        BranchUndoLog decodedLog = getParser().decode(bytes);
+        Field decodedField = getFieldFromLog(decodedLog);
+
+        Assertions.assertTrue(
+                DataCompareUtils.isFieldEquals(field, 
decodedField).getResult());
+
+        // verify null elements are handled correctly
+        SerialArray deserializedArray = (SerialArray) decodedField.getValue();
+        Object[] elements = deserializedArray.getElements();
+        Assertions.assertEquals(3, elements.length);
+        Assertions.assertEquals(1L, elements[0]);
+        Assertions.assertNull(elements[1]);
+        Assertions.assertEquals(3L, elements[2]);
+    }
+
+    private BranchUndoLog createTestUndoLog(Field field) {
+        TableRecords afterImage = new TableRecords();
+        afterImage.setTableName("test_table");
+        List<Row> rows = new ArrayList<>();
+        Row row = new Row();
+        row.add(field);
+        rows.add(row);
+        afterImage.setRows(rows);
+
+        SQLUndoLog sqlUndoLog = new SQLUndoLog();
+        sqlUndoLog.setSqlType(SQLType.INSERT);
+        sqlUndoLog.setTableName("test_table");
+        sqlUndoLog.setBeforeImage(new TableRecords());
+        sqlUndoLog.setAfterImage(afterImage);
+
+        BranchUndoLog branchUndoLog = new BranchUndoLog();
+        branchUndoLog.setBranchId(123456L);
+        branchUndoLog.setXid("test_xid");
+        List<SQLUndoLog> logList = new ArrayList<>();
+        logList.add(sqlUndoLog);
+        branchUndoLog.setSqlUndoLogs(logList);
+
+        return branchUndoLog;
+    }
+
+    private Field getFieldFromLog(BranchUndoLog log) {
+        return log.getSqlUndoLogs()
+                .get(0)
+                .getAfterImage()
+                .getRows()
+                .get(0)
+                .getFields()
+                .get(0);
+    }
+
+    /**
+     * 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
+        }
+    }
+
     private class TimestampSerializer implements ValueFilter {
 
         @Override


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

Reply via email to