mirror of https://github.com/milvus-io/milvus.git
				
				
				
			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>pull/36463/head
							parent
							
								
									df7ae08851
								
							
						
					
					
						commit
						b3f2d3db6f
					
				| 
						 | 
				
			
			@ -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
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue