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,13 +276,25 @@ type fieldCandi struct {
} }
func reflectValueCandi(v reflect.Value) (map[string]fieldCandi, error) { func reflectValueCandi(v reflect.Value) (map[string]fieldCandi, error) {
if v.Kind() == reflect.Ptr { // unref **/***/... struct{}
for v.Kind() == reflect.Ptr {
v = v.Elem() v = v.Elem()
} }
result := make(map[string]fieldCandi)
switch v.Kind() { switch v.Kind() {
case reflect.Map: // map[string]any case reflect.Map: // map[string]any
return getMapReflectCandidates(v), nil
case reflect.Struct:
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() iter := v.MapRange()
for iter.Next() { for iter.Next() {
key := iter.Key().String() key := iter.Key().String()
@ -291,13 +303,35 @@ func reflectValueCandi(v reflect.Value) (map[string]fieldCandi, error) {
v: iter.Value(), v: iter.Value(),
} }
} }
return result, nil return result
case reflect.Struct: }
// 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++ { for i := 0; i < v.NumField(); i++ {
ft := v.Type().Field(i) ft := v.Type().Field(i)
name := ft.Name name := ft.Name
tag, ok := ft.Tag.Lookup(MilvusTag)
// 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) settings := make(map[string]string)
if ok { if ok {
if tag == MilvusSkipTagValue { if tag == MilvusSkipTagValue {
@ -329,7 +363,4 @@ func reflectValueCandi(v reflect.Value) (map[string]fieldCandi, error) {
} }
return result, nil return result, nil
default:
return nil, fmt.Errorf("unsupport row type: %s", v.Kind().String())
}
} }

View File

@ -1,6 +1,7 @@
package row package row
import ( import (
"fmt"
"reflect" "reflect"
"testing" "testing"
@ -126,6 +127,10 @@ func (s *RowsSuite) TestDynamicSchema() {
} }
func (s *RowsSuite) TestReflectValueCandi() { func (s *RowsSuite) TestReflectValueCandi() {
type DynamicRows struct {
Float float32 `json:"float" milvus:"name:float"`
}
cases := []struct { cases := []struct {
tag string tag string
v reflect.Value v reflect.Value
@ -149,6 +154,65 @@ func (s *RowsSuite) TestReflectValueCandi() {
}, },
expectErr: false, 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 { for _, c := range cases {
@ -162,7 +226,7 @@ func (s *RowsSuite) TestReflectValueCandi() {
s.Equal(len(c.expect), len(r)) s.Equal(len(c.expect), len(r))
for k, v := range c.expect { for k, v := range c.expect {
rv, has := r[k] 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) s.Equal(v.name, rv.name)
} }
}) })