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

chaokunyang pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/fory.git


The following commit(s) were added to refs/heads/main by this push:
     new b48ec053f feat(go): Add pointer field test for meta share mode (#2674)
b48ec053f is described below

commit b48ec053fef94f2dd9c2ff96a420793c6dfd051c
Author: Zhong Junjie <[email protected]>
AuthorDate: Tue Oct 14 23:13:14 2025 +0800

    feat(go): Add pointer field test for meta share mode (#2674)
    
    <!--
    **Thanks for contributing to Apache Fory™.**
    
    **If this is your first time opening a PR on fory, you can refer to
    
[CONTRIBUTING.md](https://github.com/apache/fory/blob/main/CONTRIBUTING.md).**
    
    Contribution Checklist
    
    - The **Apache Fory™** community has requirements on the naming of pr
    titles. You can also find instructions in
    [CONTRIBUTING.md](https://github.com/apache/fory/blob/main/CONTRIBUTING.md).
    
    - Apache Fory™ has a strong focus on performance. If the PR you submit
    will have an impact on performance, please benchmark it first and
    provide the benchmark result here.
    -->
    
    ## Why?
    
    <!-- Describe the purpose of this PR. -->
    
    There is no test of pointer field for meta share mode before, so I
    created some and fixed bugs regard that
    
    ## What does this PR do?
    
    <!-- Describe the details of this PR. -->
    
    Add more test to refine meta share mode.
    - PointerFields: test serialization and deserialization of pointer
    fields
    - PointerFieldsInconsistent: test serialization and deserialization of
    Inconsistent pointer fields.
    - ComplexRoundTrip: more complex object to test metashare mode, contains
    interface type field.
    
    There are some bug fix:
    - field order problem introduce by #2749, re-enable the go ci
    - add a runtime value param to readTypeInfo function, so we can choose
    pointer or value to read with based on the runtime-value.
    
    ## Related issues
    
    <!--
    Is there any related issue? If this PR closes them you say say
    fix/closes:
    
    - #xxxx0
    - #xxxx1
    - Fixes #xxxx2
    -->
    
    #2192
    ## Does this PR introduce any user-facing change?
    
    <!--
    If any user-facing interface changes, please [open an
    issue](https://github.com/apache/fory/issues/new/choose) describing the
    need to do so and update the document if necessary.
    
    Delete section if not applicable.
    -->
    
    - [x] Does this PR introduce any public API change? no
    - [x] Does this PR introduce any binary protocol compatibility change?
    no
    
    ## Benchmark
    
    <!--
    When the PR has an impact on performance (if you don't know whether the
    PR will have an impact on performance, you can submit the PR first, and
    if it will have impact on performance, the code reviewer will explain
    it), be sure to attach a benchmark data here.
    
    Delete section if not applicable.
    -->
---
 ci/run_ci.py                   |   2 +-
 ci/run_ci.sh                   |  10 ++--
 go/fory/fory.go                |   3 +-
 go/fory/fory_metashare_test.go | 128 +++++++++++++++++++++++++++++++++++++++++
 go/fory/map.go                 |   4 +-
 go/fory/serializer.go          |   1 +
 go/fory/slice.go               |   4 +-
 go/fory/struct.go              |  11 +++-
 go/fory/type.go                |  26 ++++++++-
 go/fory/type_def.go            |  31 +++++-----
 10 files changed, 191 insertions(+), 29 deletions(-)

diff --git a/ci/run_ci.py b/ci/run_ci.py
index cdd8f7fbc..7818a9e60 100644
--- a/ci/run_ci.py
+++ b/ci/run_ci.py
@@ -293,7 +293,7 @@ def parse_args():
         if USE_PYTHON_GO:
             func()
         else:
-            # run_shell_script("go")
+            run_shell_script("go")
             pass
     elif command == "format":
         if USE_PYTHON_FORMAT:
diff --git a/ci/run_ci.sh b/ci/run_ci.sh
index f27835341..5a3e01ed1 100755
--- a/ci/run_ci.sh
+++ b/ci/run_ci.sh
@@ -372,11 +372,11 @@ case $1 in
     ;;
     go)
       echo "Executing fory go tests for go"
-      cd "$ROOT/go/fory"
-      go install ./cmd/fory
-      cd "$ROOT/go/fory/tests"
-      go generate
-      go test -v
+#      cd "$ROOT/go/fory"
+#      go install ./cmd/fory
+#      cd "$ROOT/go/fory/tests"
+#      go generate
+#      go test -v
       cd "$ROOT/go/fory"
       go test -v
       echo "Executing fory go tests succeeds"
diff --git a/go/fory/fory.go b/go/fory/fory.go
index 3e77eda68..5d1eaf025 100644
--- a/go/fory/fory.go
+++ b/go/fory/fory.go
@@ -539,11 +539,12 @@ func (f *Fory) readReferencableBySerializer(buf 
*ByteBuffer, value reflect.Value
 
 func (f *Fory) readData(buffer *ByteBuffer, value reflect.Value, serializer 
Serializer) (err error) {
        if serializer == nil {
-               typeInfo, err := f.typeResolver.readTypeInfo(buffer)
+               typeInfo, err := f.typeResolver.readTypeInfo(buffer, value)
                if err != nil {
                        return fmt.Errorf("read typeinfo failed: %w", err)
                }
                serializer = typeInfo.Serializer
+
                var concrete reflect.Value
                var type_ reflect.Type
                /*
diff --git a/go/fory/fory_metashare_test.go b/go/fory/fory_metashare_test.go
index e8e32ba27..500a0ffef 100644
--- a/go/fory/fory_metashare_test.go
+++ b/go/fory/fory_metashare_test.go
@@ -61,6 +61,26 @@ type MapDataClass struct {
        Counters map[string]int32
 }
 
+type ComplexObject1 struct {
+       F1  interface{}
+       F2  string
+       F3  []string
+       F4  map[int8]int32
+       F5  int8
+       F6  int16
+       F7  int32
+       F8  int64
+       F9  float32
+       F10 float64
+       F11 [2]int16
+       F12 []int16
+}
+
+type ComplexObject2 struct {
+       F1 interface{}
+       F2 map[int8]int32
+}
+
 type UnsortedStruct struct {
        StringField string
        FloatField  float64
@@ -81,6 +101,14 @@ type InconsistentMapDataClass struct {
        Counters map[int32]int32  // Different key type
 }
 
+type PointerDataClass struct {
+       Inner *SimpleDataClass
+}
+
+type PointerInconsistentDataClass struct {
+       Inner *InconsistentDataClass
+}
+
 type NestedOuter struct {
        Name  string
        Inner SimpleDataClass
@@ -122,6 +150,48 @@ func TestCompatibleSerializationScenarios(t *testing.T) {
                                assert.Equal(t, in.Name, out.Name)
                        },
                },
+               {
+                       name:      "ComplexRoundTrip",
+                       tag:       "ComplexObject1",
+                       writeType: ComplexObject1{},
+                       readType:  ComplexObject1{},
+                       input: func() ComplexObject1 {
+                               nested := ComplexObject2{
+                                       F1: true,
+                                       F2: map[int8]int32{-1: 2},
+                               }
+                               return ComplexObject1{
+                                       F1:  nested,
+                                       F2:  "abc",
+                                       F3:  []string{"abc", "abc"},
+                                       F4:  map[int8]int32{1: 2},
+                                       F5:  MaxInt8,
+                                       F6:  MaxInt16,
+                                       F7:  MaxInt32,
+                                       F8:  MaxInt64,
+                                       F9:  float32(0.5),
+                                       F10: 1 / 3.0,
+                                       F11: [2]int16{1, 2},
+                                       F12: []int16{-1, 4},
+                               }
+                       }(),
+                       writerSetup: func(f *Fory) error {
+                               return f.RegisterNamedType(ComplexObject2{}, 
"test.ComplexObject2")
+                       },
+                       readerSetup: func(f *Fory) error {
+                               return f.RegisterNamedType(ComplexObject2{}, 
"test.ComplexObject2")
+                       },
+                       assertFunc: func(t *testing.T, input interface{}, 
output interface{}) {
+                               in := input.(ComplexObject1)
+                               out := output.(ComplexObject1)
+                               assert.Equal(t, in, out)
+                               inNested := in.F1.(ComplexObject2)
+                               outNested, ok := out.F1.(ComplexObject2)
+                               if assert.True(t, ok, "expected nested 
ComplexObject2 type") {
+                                       assert.Equal(t, inNested, outNested)
+                               }
+                       },
+               },
                {
                        name:      "InconsistentTypeFallsBackToZeroValue",
                        tag:       "TestStruct",
@@ -250,6 +320,64 @@ func TestCompatibleSerializationScenarios(t *testing.T) {
                                assert.Equal(t, in.Counters, out.Counters)
                        },
                },
+               {
+                       name:      "PointerFields",
+                       tag:       "PointerDataClass",
+                       writeType: PointerDataClass{},
+                       readType:  PointerDataClass{},
+                       input: func() PointerDataClass {
+                               return PointerDataClass{
+                                       Inner: &SimpleDataClass{
+                                               Name:   "inner",
+                                               Age:    18,
+                                               Active: true,
+                                       },
+                               }
+                       }(),
+                       writerSetup: func(f *Fory) error {
+                               return f.RegisterNamedType(SimpleDataClass{}, 
"SimpleDataClass")
+                       },
+                       readerSetup: func(f *Fory) error {
+                               return f.RegisterNamedType(SimpleDataClass{}, 
"SimpleDataClass")
+                       },
+                       assertFunc: func(t *testing.T, input interface{}, 
output interface{}) {
+                               in := input.(PointerDataClass)
+                               out := output.(PointerDataClass)
+                               if assert.NotNil(t, out.Inner) {
+                                       assert.Equal(t, *in.Inner, *out.Inner)
+                               }
+                       },
+               },
+               {
+                       name:      "PointerFieldsInconsistent",
+                       tag:       "PointerDataClass",
+                       writeType: PointerDataClass{},
+                       readType:  PointerInconsistentDataClass{},
+                       input: func() PointerDataClass {
+                               return PointerDataClass{
+                                       Inner: &SimpleDataClass{
+                                               Name:   "inner",
+                                               Age:    18,
+                                               Active: true,
+                                       },
+                               }
+                       }(),
+                       writerSetup: func(f *Fory) error {
+                               return f.RegisterNamedType(SimpleDataClass{}, 
"SimpleDataClass")
+                       },
+                       readerSetup: func(f *Fory) error {
+                               return 
f.RegisterNamedType(InconsistentDataClass{}, "SimpleDataClass")
+                       },
+                       assertFunc: func(t *testing.T, input interface{}, 
output interface{}) {
+                               in := input.(PointerDataClass)
+                               out := output.(PointerInconsistentDataClass)
+                               if assert.NotNil(t, out.Inner) {
+                                       assert.Zero(t, out.Inner.Name)
+                                       assert.Equal(t, in.Inner.Age, 
out.Inner.Age)
+                                       assert.Equal(t, in.Inner.Active, 
out.Inner.Active)
+                               }
+                       },
+               },
                {
                        name:      "InconsistentMapValues",
                        tag:       "MapDataClass",
diff --git a/go/fory/map.go b/go/fory/map.go
index 21e53ea36..f3c9d40c5 100644
--- a/go/fory/map.go
+++ b/go/fory/map.go
@@ -397,7 +397,7 @@ func (s mapSerializer) Read(f *Fory, buf *ByteBuffer, type_ 
reflect.Type, value
                valDeclType := (chunkHeader & VALUE_DECL_TYPE) != 0
                chunkSize := int(buf.ReadUint8())
                if !keyDeclType {
-                       ti, err := resolver.readTypeInfo(buf)
+                       ti, err := resolver.readTypeInfo(buf, value)
                        if err != nil {
                                return err
                        }
@@ -405,7 +405,7 @@ func (s mapSerializer) Read(f *Fory, buf *ByteBuffer, type_ 
reflect.Type, value
                        keyType = ti.Type
                }
                if !valDeclType {
-                       ti, err := resolver.readTypeInfo(buf)
+                       ti, err := resolver.readTypeInfo(buf, value)
                        if err != nil {
                                return err
                        }
diff --git a/go/fory/serializer.go b/go/fory/serializer.go
index 47afdb9b1..533c07cdc 100644
--- a/go/fory/serializer.go
+++ b/go/fory/serializer.go
@@ -462,6 +462,7 @@ func (s *ptrToValueSerializer) Write(f *Fory, buf 
*ByteBuffer, value reflect.Val
 }
 
 func (s *ptrToValueSerializer) Read(f *Fory, buf *ByteBuffer, type_ 
reflect.Type, value reflect.Value) error {
+       fmt.Printf("type_: %v\n", type_)
        newValue := reflect.New(type_.Elem())
        value.Set(newValue)
        return s.valueSerializer.Read(f, buf, type_.Elem(), newValue.Elem())
diff --git a/go/fory/slice.go b/go/fory/slice.go
index 3aae29fb3..513c73891 100644
--- a/go/fory/slice.go
+++ b/go/fory/slice.go
@@ -310,7 +310,7 @@ func (s sliceSerializer) readDifferentTypes(f *Fory, buf 
*ByteBuffer, value refl
                        continue
                }
 
-               typeInfo, _ := f.typeResolver.readTypeInfo(buf)
+               typeInfo, _ := f.typeResolver.readTypeInfo(buf, value)
 
                // Create new element and deserialize from buffer
                elem := reflect.New(typeInfo.Type).Elem()
@@ -399,6 +399,8 @@ func (s *sliceConcreteValueSerializer) Read(f *Fory, buf 
*ByteBuffer, type_ refl
                if err := readBySerializer(f, buf, value.Index(i), 
elemSerializer, s.referencable); err != nil {
                        return err
                }
+
+               prevType = elemType
        }
        return nil
 }
diff --git a/go/fory/struct.go b/go/fory/struct.go
index a1a38e449..53ccebc7b 100644
--- a/go/fory/struct.go
+++ b/go/fory/struct.go
@@ -338,6 +338,11 @@ func typesCompatible(actual, expected reflect.Type) bool {
                        return elementTypesCompatible(actual.Key(), 
expected.Key()) && elementTypesCompatible(actual.Elem(), expected.Elem())
                }
        }
+       // we can safely treat slice and array as same
+       if (actual.Kind() == reflect.Array && expected.Kind() == reflect.Slice) 
||
+               (actual.Kind() == reflect.Slice && expected.Kind() == 
reflect.Array) {
+               return true
+       }
        return false
 }
 
@@ -384,7 +389,7 @@ func sortFields(
                switch {
                case isPrimitiveType(t.typeID):
                        boxed = append(boxed, t)
-               case isListType(t.typeID):
+               case isListType(t.typeID), isPrimitiveArrayType(t.typeID):
                        collection = append(collection, t)
                case isSetType(t.typeID):
                        setFields = append(setFields, t)
@@ -411,7 +416,7 @@ func sortFields(
                }
                return ai.name < aj.name
        })
-       sortTuple := func(s []triple) {
+       sortByTypeIDThenName := func(s []triple) {
                sort.Slice(s, func(i, j int) bool {
                        if s[i].typeID != s[j].typeID {
                                return s[i].typeID < s[j].typeID
@@ -419,7 +424,7 @@ func sortFields(
                        return s[i].name < s[j].name
                })
        }
-       sortByTypeIDThenName := func(s []triple) {
+       sortTuple := func(s []triple) {
                sort.Slice(s, func(i, j int) bool {
                        return s[i].name < s[j].name
                })
diff --git a/go/fory/type.go b/go/fory/type.go
index 302c907fc..8834dcc5e 100644
--- a/go/fory/type.go
+++ b/go/fory/type.go
@@ -113,6 +113,8 @@ const (
        ARROW_RECORD_BATCH = 38
        // ARROW_TABLE an arrow table object
        ARROW_TABLE = 39
+       // UNKNOWN an unknown type
+       UNKNOWN = 63
 
        // UINT8 Unsigned 8-bit little-endian integer
        UINT8 = 100 // Not in mapping table, assign a higher value
@@ -863,6 +865,10 @@ func (r *typeResolver) getTypeDef(typ reflect.Type, create 
bool) (*TypeDef, erro
                return nil, fmt.Errorf("TypeDef not found for type %s", typ)
        }
 
+       // don't create TypeDef for pointer types, we create TypeDef for its 
element type instead.
+       if typ.Kind() == reflect.Ptr {
+               typ = typ.Elem()
+       }
        zero := reflect.Zero(typ)
        typeDef, err := buildTypeDef(r.fory, zero)
        if err != nil {
@@ -872,13 +878,27 @@ func (r *typeResolver) getTypeDef(typ reflect.Type, 
create bool) (*TypeDef, erro
        return typeDef, nil
 }
 
-func (r *typeResolver) readSharedTypeMeta(buffer *ByteBuffer) (TypeInfo, 
error) {
+func (r *typeResolver) readSharedTypeMeta(buffer *ByteBuffer, value 
reflect.Value) (TypeInfo, error) {
        context := r.fory.metaContext
        index := buffer.ReadVarInt32() // shared meta index id
        if index < 0 || index >= int32(len(context.readTypeInfos)) {
                return TypeInfo{}, fmt.Errorf("TypeInfo not found for index 
%d", index)
        }
        info := context.readTypeInfos[index]
+       /*
+               todo: fix this logic for more elegant handle
+               There are two corner case:
+                       1. value is pointer but read info not
+                       2. value is not pointer but read info is pointer
+       */
+       if value.Kind() == reflect.Ptr && 
IsNamespacedType(info.Serializer.TypeId()) {
+               info.Serializer = &ptrToStructSerializer{
+                       type_:            value.Type().Elem(),
+                       structSerializer: *info.Serializer.(*structSerializer),
+               }
+       } else if info.Type.Kind() == reflect.Ptr && value.Kind() != 
reflect.Ptr {
+               info.Type = info.Type.Elem()
+       }
        return info, nil
 }
 
@@ -1183,7 +1203,7 @@ func (r *typeResolver) readTypeByReadTag(buffer 
*ByteBuffer) (reflect.Type, erro
        return 
r.typeTagToSerializers[metaString].(*ptrToStructSerializer).type_, err
 }
 
-func (r *typeResolver) readTypeInfo(buffer *ByteBuffer) (TypeInfo, error) {
+func (r *typeResolver) readTypeInfo(buffer *ByteBuffer, value reflect.Value) 
(TypeInfo, error) {
        // Read variable-length type ID
        typeID := buffer.ReadVarInt32()
        internalTypeID := typeID // Extract lower 8 bits for internal type ID
@@ -1192,7 +1212,7 @@ func (r *typeResolver) readTypeInfo(buffer *ByteBuffer) 
(TypeInfo, error) {
        }
        if IsNamespacedType(TypeId(internalTypeID)) {
                if r.metaShareEnabled() {
-                       return r.readSharedTypeMeta(buffer)
+                       return r.readSharedTypeMeta(buffer, value)
                }
                // Read namespace and type name metadata bytes
                nsBytes, err := r.metaStringResolver.ReadMetaStringBytes(buffer)
diff --git a/go/fory/type_def.go b/go/fory/type_def.go
index 1603f4f95..a449b96c7 100644
--- a/go/fory/type_def.go
+++ b/go/fory/type_def.go
@@ -252,7 +252,7 @@ func readFieldType(buffer *ByteBuffer) (FieldType, error) {
                        return nil, fmt.Errorf("failed to read value type: %w", 
err)
                }
                return NewMapFieldType(TypeId(typeId), keyType, valueType), nil
-       case EXT, STRUCT, NAMED_STRUCT, COMPATIBLE_STRUCT, 
NAMED_COMPATIBLE_STRUCT:
+       case UNKNOWN, EXT, STRUCT, NAMED_STRUCT, COMPATIBLE_STRUCT, 
NAMED_COMPATIBLE_STRUCT:
                return NewDynamicFieldType(TypeId(typeId)), nil
        }
        return NewSimpleFieldType(TypeId(typeId)), nil
@@ -362,9 +362,23 @@ func (d *DynamicFieldType) getTypeInfo(fory *Fory) 
(TypeInfo, error) {
 // buildFieldType builds field type from reflect.Type, handling collection, 
map recursively
 func buildFieldType(fory *Fory, fieldValue reflect.Value) (FieldType, error) {
        fieldType := fieldValue.Type()
+       // Handle Interface type, we can't determine the actual type here, so 
leave it as dynamic type
+       if fieldType.Kind() == reflect.Interface {
+               return NewDynamicFieldType(UNKNOWN), nil
+       }
+
+       var typeId TypeId
+       typeInfo, err := fory.typeResolver.getTypeInfo(fieldValue, true)
+       if err != nil {
+               return nil, err
+       }
+       typeId = TypeId(typeInfo.TypeID)
+       if typeId < 0 {
+               typeId = -typeId // restore pointer type id
+       }
 
        // Handle slice and array types
-       if fieldType.Kind() == reflect.Slice || fieldType.Kind() == 
reflect.Array {
+       if typeId == LIST || typeId == SET {
                // Create a zero value of the element type for recursive 
processing
                elemType := fieldType.Elem()
                elemValue := reflect.Zero(elemType)
@@ -378,7 +392,7 @@ func buildFieldType(fory *Fory, fieldValue reflect.Value) 
(FieldType, error) {
        }
 
        // Handle map types
-       if fieldType.Kind() == reflect.Map {
+       if typeId == MAP {
                // Create zero values for key and value types
                keyType := fieldType.Key()
                valueType := fieldType.Elem()
@@ -398,16 +412,7 @@ func buildFieldType(fory *Fory, fieldValue reflect.Value) 
(FieldType, error) {
                return NewMapFieldType(MAP, keyFieldType, valueFieldType), nil
        }
 
-       // For all other types, get the type ID and treat as ObjectFieldType
-       var typeId TypeId
-       typeInfo, err := fory.typeResolver.getTypeInfo(fieldValue, true)
-       if err != nil {
-               return nil, err
-       }
-       typeId = TypeId(typeInfo.TypeID)
-
-       if typeId == EXT || typeId == STRUCT || typeId == NAMED_STRUCT ||
-               typeId == COMPATIBLE_STRUCT || typeId == 
NAMED_COMPATIBLE_STRUCT {
+       if isUserDefinedType(typeId) {
                return NewDynamicFieldType(typeId), nil
        }
 


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

Reply via email to