enhance: [GoSDK] support embedded struct in row data (#36443)

Related to milvus-io/milvus-sdk-go#818

This PR make Row-based insert data parsing embedded struct as flatten
fields instead.

Signed-off-by: Congqi Xia <congqi.xia@zilliz.com>
This commit is contained in:
congqixia 2024-09-24 11:49:13 +08:00 committed by GitHub
parent df7ae08851
commit b3f2d3db6f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 143 additions and 48 deletions

View File

@ -276,60 +276,91 @@ type fieldCandi struct {
}
func reflectValueCandi(v reflect.Value) (map[string]fieldCandi, error) {
if v.Kind() == reflect.Ptr {
// unref **/***/... struct{}
for v.Kind() == reflect.Ptr {
v = v.Elem()
}
result := make(map[string]fieldCandi)
switch v.Kind() {
case reflect.Map: // map[string]any
iter := v.MapRange()
for iter.Next() {
key := iter.Key().String()
result[key] = fieldCandi{
name: key,
v: iter.Value(),
}
}
return result, nil
return getMapReflectCandidates(v), nil
case reflect.Struct:
for i := 0; i < v.NumField(); i++ {
ft := v.Type().Field(i)
name := ft.Name
tag, ok := ft.Tag.Lookup(MilvusTag)
settings := make(map[string]string)
if ok {
if tag == MilvusSkipTagValue {
continue
}
settings = ParseTagSetting(tag, MilvusTagSep)
fn, has := settings[MilvusTagName]
if has {
// overwrite column to tag name
name = fn
}
}
_, ok = result[name]
// duplicated
if ok {
return nil, fmt.Errorf("column has duplicated name: %s when parsing field: %s", name, ft.Name)
}
v := v.Field(i)
if v.Kind() == reflect.Array {
v = v.Slice(0, v.Len())
}
result[name] = fieldCandi{
name: name,
v: v,
options: settings,
}
}
return result, nil
return getStructReflectCandidates(v)
default:
return nil, fmt.Errorf("unsupport row type: %s", v.Kind().String())
}
}
// getMapReflectCandidates converts input map into fieldCandidate struct.
// if value is struct/map etc, it will be treated as json data type directly(if schema say so).
func getMapReflectCandidates(v reflect.Value) map[string]fieldCandi {
result := make(map[string]fieldCandi)
iter := v.MapRange()
for iter.Next() {
key := iter.Key().String()
result[key] = fieldCandi{
name: key,
v: iter.Value(),
}
}
return result
}
// getStructReflectCandidates parses struct fields into fieldCandidates.
// embedded struct will be flatten as field as well.
func getStructReflectCandidates(v reflect.Value) (map[string]fieldCandi, error) {
result := make(map[string]fieldCandi)
for i := 0; i < v.NumField(); i++ {
ft := v.Type().Field(i)
name := ft.Name
// embedded struct, flatten all fields
if ft.Anonymous && ft.Type.Kind() == reflect.Struct {
embedCandidate, err := reflectValueCandi(v.Field(i))
if err != nil {
return nil, err
}
for key, candi := range embedCandidate {
// check duplicated field name in different structs
_, ok := result[key]
if ok {
return nil, fmt.Errorf("column has duplicated name: %s when parsing field: %s", key, ft.Name)
}
result[key] = candi
}
continue
}
tag, ok := ft.Tag.Lookup(MilvusTag)
settings := make(map[string]string)
if ok {
if tag == MilvusSkipTagValue {
continue
}
settings = ParseTagSetting(tag, MilvusTagSep)
fn, has := settings[MilvusTagName]
if has {
// overwrite column to tag name
name = fn
}
}
_, ok = result[name]
// duplicated
if ok {
return nil, fmt.Errorf("column has duplicated name: %s when parsing field: %s", name, ft.Name)
}
v := v.Field(i)
if v.Kind() == reflect.Array {
v = v.Slice(0, v.Len())
}
result[name] = fieldCandi{
name: name,
v: v,
options: settings,
}
}
return result, nil
}

View File

@ -1,6 +1,7 @@
package row
import (
"fmt"
"reflect"
"testing"
@ -126,6 +127,10 @@ func (s *RowsSuite) TestDynamicSchema() {
}
func (s *RowsSuite) TestReflectValueCandi() {
type DynamicRows struct {
Float float32 `json:"float" milvus:"name:float"`
}
cases := []struct {
tag string
v reflect.Value
@ -149,6 +154,65 @@ func (s *RowsSuite) TestReflectValueCandi() {
},
expectErr: false,
},
{
tag: "StructRow",
v: reflect.ValueOf(struct {
A string
B int64
}{A: "abc", B: 16}),
expect: map[string]fieldCandi{
"A": {
name: "A",
v: reflect.ValueOf("abc"),
},
"B": {
name: "B",
v: reflect.ValueOf(int64(16)),
},
},
expectErr: false,
},
{
tag: "StructRow_DuplicateName",
v: reflect.ValueOf(struct {
A string `milvus:"name:a"`
B int64 `milvus:"name:a"`
}{A: "abc", B: 16}),
expectErr: true,
},
{
tag: "StructRow_EmbedStruct",
v: reflect.ValueOf(struct {
A string `milvus:"name:a"`
DynamicRows
}{A: "emb", DynamicRows: DynamicRows{Float: 0.1}}),
expect: map[string]fieldCandi{
"a": {
name: "a",
v: reflect.ValueOf("emb"),
},
"float": {
name: "float",
v: reflect.ValueOf(float32(0.1)),
},
},
expectErr: false,
},
{
tag: "StructRow_EmbedDuplicateName",
v: reflect.ValueOf(struct {
Int64 int64 `json:"int64" milvus:"name:int64"`
Float float32 `json:"float" milvus:"name:float"`
FloatVec []float32 `json:"floatVec" milvus:"name:floatVec"`
DynamicRows
}{}),
expectErr: true,
},
{
tag: "Unsupported_primitive",
v: reflect.ValueOf(int64(1)),
expectErr: true,
},
}
for _, c := range cases {
@ -162,7 +226,7 @@ func (s *RowsSuite) TestReflectValueCandi() {
s.Equal(len(c.expect), len(r))
for k, v := range c.expect {
rv, has := r[k]
s.Require().True(has)
s.Require().True(has, fmt.Sprintf("candidate with key(%s) must provided", k))
s.Equal(v.name, rv.name)
}
})