From b823a0e742177860fa5c019fd6ce9bab85913a8d Mon Sep 17 00:00:00 2001 From: congqixia Date: Fri, 20 Sep 2024 17:07:05 +0800 Subject: [PATCH] enhance: Support embedded struct field as Row input (#823) Related to #818 Signed-off-by: Congqi Xia --- entity/rows.go | 20 ++++++++++++++++++-- entity/rows_test.go | 44 ++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 58 insertions(+), 6 deletions(-) diff --git a/entity/rows.go b/entity/rows.go index dc30af7a1..75775237b 100644 --- a/entity/rows.go +++ b/entity/rows.go @@ -32,7 +32,7 @@ const ( // MilvusTagSep struct tag const for attribute separator MilvusTagSep = `;` - //MilvusTagName struct tag const for field name + // MilvusTagName struct tag const for field name MilvusTagName = `NAME` // VectorDimTag struct tag const for vector dimension @@ -171,7 +171,7 @@ func ParseSchemaAny(r interface{}) (*Schema, error) { switch elemType.Kind() { case reflect.Uint8: field.DataType = FieldTypeBinaryVector - //TODO maybe override by tag settings, when dim is not multiplier of 8 + // TODO maybe override by tag settings, when dim is not multiplier of 8 field.TypeParams = map[string]string{ TypeParamDim: strconv.FormatInt(int64(arrayLen*8), 10), } @@ -526,6 +526,22 @@ func reflectValueCandi(v reflect.Value) (map[string]fieldCandi, error) { case reflect.Struct: for i := 0; i < v.NumField(); i++ { ft := v.Type().Field(i) + // embedded struct + 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 { + _, 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 + } + name := ft.Name tag, ok := ft.Tag.Lookup(MilvusTag) diff --git a/entity/rows_test.go b/entity/rows_test.go index 19d182d6b..81c633d71 100644 --- a/entity/rows_test.go +++ b/entity/rows_test.go @@ -57,7 +57,6 @@ type SliceBadDimStruct2 struct { } func TestParseSchema(t *testing.T) { - t.Run("invalid cases", func(t *testing.T) { // anonymous struct with default collection name ("") will cause error anonymusStruct := struct { @@ -108,11 +107,9 @@ func TestParseSchema(t *testing.T) { sch, err = ParseSchema(&SliceBadDimStruct2{}) assert.Nil(t, sch) assert.NotNil(t, err) - }) t.Run("valid cases", func(t *testing.T) { - sch, err := ParseSchema(RowBase{}) assert.Nil(t, err) assert.Equal(t, "RowBase", sch.CollectionName) @@ -268,7 +265,6 @@ type RowsSuite struct { func (s *RowsSuite) TestRowsToColumns() { s.Run("valid_cases", func() { - columns, err := RowsToColumns([]Row{&ValidStruct{}}) s.Nil(err) s.Equal(10, len(columns)) @@ -358,6 +354,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 @@ -381,6 +381,42 @@ 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_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, + }, } for _, c := range cases {