Skip to content

Commit

Permalink
Support row interface with map[string]any
Browse files Browse the repository at this point in the history
Signed-off-by: Congqi Xia <congqi.xia@zilliz.com>
  • Loading branch information
congqixia committed May 25, 2023
1 parent 6368d50 commit 88e9fe1
Show file tree
Hide file tree
Showing 3 changed files with 419 additions and 43 deletions.
126 changes: 83 additions & 43 deletions entity/rows.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,21 @@ type Row interface {
Description() string
}

// MapRow is the alias type for map[string]interface{} implementing `Row` inteface with empty methods.
type MapRow map[string]interface{}

func (mr MapRow) Collection() string {
return ""
}

func (mr MapRow) Partition() string {
return ""
}

func (mr MapRow) Description() string {
return ""
}

// RowBase row base, returns default collection, partition name which is empty string
type RowBase struct{}

Expand Down Expand Up @@ -321,49 +336,9 @@ func RowsToColumns(rows []Row, schemas ...*Schema) ([]Column, error) {
for _, row := range rows {
// collection schema name need not to be same, since receiver could has other names
v := reflect.ValueOf(row)
if v.Kind() == reflect.Ptr {
v = v.Elem()
}

type fieldCandi struct {
name string
v reflect.Value
options map[string]string
}
set := make(map[string]fieldCandi)
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 = set[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()-1)
}

set[name] = fieldCandi{
name: name,
v: v,
options: settings,
}
set, err := reflectValueCandi(v)
if err != nil {
return nil, err
}

for idx, field := range sch.Fields {
Expand Down Expand Up @@ -407,3 +382,68 @@ func RowsToColumns(rows []Row, schemas ...*Schema) ([]Column, error) {
}
return columns, nil
}

type fieldCandi struct {
name string
v reflect.Value
options map[string]string
}

func reflectValueCandi(v reflect.Value) (map[string]fieldCandi, error) {
if v.Kind() == reflect.Ptr {
v = v.Elem()
}

result := make(map[string]fieldCandi)
switch v.Kind() {
case reflect.Map: // map[string]interface{}
iter := v.MapRange()
for iter.Next() {
key := iter.Key().String()
result[key] = fieldCandi{
name: key,
v: iter.Value(),
}
}
return result, 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()-1)
}

result[name] = fieldCandi{
name: name,
v: v,
options: settings,
}
}

return result, nil
default:
return nil, fmt.Errorf("unsupport row type: %s", v.Kind().String())
}
}
51 changes: 51 additions & 0 deletions entity/rows_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
package entity

import (
"reflect"
"testing"

"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -66,6 +67,12 @@ func TestParseSchema(t *testing.T) {
assert.Nil(t, sch)
assert.NotNil(t, err)

// MapRow
m := make(MapRow)
sch, err = ParseSchema(m)
assert.Nil(t, sch)
assert.NotNil(t, err)

// non struct
arrayRow := ArrayRow([16]float32{})
sch, err = ParseSchema(&arrayRow)
Expand Down Expand Up @@ -263,6 +270,50 @@ func (s *RowsSuite) TestDynamicSchema() {
})
}

func (s *RowsSuite) TestReflectValueCandi() {
cases := []struct {
tag string
v reflect.Value
expect map[string]fieldCandi
expectErr bool
}{
{
tag: "MapRow",
v: reflect.ValueOf(MapRow(map[string]interface{}{
"A": "abd", "B": int64(8),
})),
expect: map[string]fieldCandi{
"A": {
name: "A",
v: reflect.ValueOf("abd"),
},
"B": {
name: "B",
v: reflect.ValueOf(int64(8)),
},
},
expectErr: false,
},
}

for _, c := range cases {
s.Run(c.tag, func() {
r, err := reflectValueCandi(c.v)
if c.expectErr {
s.Error(err)
return
}
s.NoError(err)
s.Equal(len(c.expect), len(r))
for k, v := range c.expect {
rv, has := r[k]
s.Require().True(has)
s.Equal(v.name, rv.name)
}
})
}
}

func TestRows(t *testing.T) {
suite.Run(t, new(RowsSuite))
}
Loading

0 comments on commit 88e9fe1

Please sign in to comment.