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]