Repository: milvus-io/milvus-sdk-go
Pull request #171 (2021-08-11 08:34:52)

Add CreateCollectionByRow
Add CreateCollectionByRow
Add Unit test for row based feature functions

Related issue: #161 

Signed-off-by: Congqi Xia <congqi.xia@zilliz.com>

sre-ci-robot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/milvus-io/milvus-sdk-go/pull/171#" title="Author self-approved">congqixia</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=milvus-io%2Fmilvus-sdk-go).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/milvus-io/milvus-sdk-go/blob/master/OWNERS)~~ [congqixia]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
sunby: /lgtm
/approve
sre-ci-robot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/milvus-io/milvus-sdk-go/pull/171#" title="Author self-approved">congqixia</a>*, *<a href="https://github.com/milvus-io/milvus-sdk-go/pull/171#issuecomment-896623603" title="Approved">sunby</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=milvus-io%2Fmilvus-sdk-go).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/milvus-io/milvus-sdk-go/blob/master/OWNERS)~~ [congqixia,sunby]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->

Patch:
diff --git a/client/client.go b/client/client.go
index acfab87..8f5fe31 100644
--- a/client/client.go
+++ b/client/client.go
@@ -93,6 +93,9 @@ type Client interface {
 	// CalcDistance calculate the distance between vectors specified by ids or provided
 	CalcDistance(ctx context.Context, collName string, partitions []string,
 		metricType entity.MetricType, opLeft, opRight entity.Column) (entity.Column, error)
+
+	// -- row basd apis --
+	CreateCollectionByRow(ctx context.Context, row entity.Row, shardNum int32) error
 }
 
 // SearchResult contains the result from Search api of client
diff --git a/client/client_grpc_row.go b/client/client_grpc_row.go
index a3a641a..15ce463 100644
--- a/client/client_grpc_row.go
+++ b/client/client_grpc_row.go
@@ -1,14 +1,63 @@
 package client
 
 import (
+	"context"
 	"errors"
 	"fmt"
 	"reflect"
 
+	"github.com/golang/protobuf/proto"
 	"github.com/milvus-io/milvus-sdk-go/v2/entity"
 	"github.com/milvus-io/milvus-sdk-go/v2/internal/proto/schema"
+	"github.com/milvus-io/milvus-sdk-go/v2/internal/proto/server"
 )
 
+// CreateCollectionByRow create collection by row
+func (c *grpcClient) CreateCollectionByRow(ctx context.Context, row entity.Row, shardNum int32) error {
+	if c.service == nil {
+		return ErrClientNotReady
+	}
+	// parse schema from row definition
+	sch, err := entity.ParseSchema(row)
+	if err != nil {
+		return err
+	}
+
+	// check collection already exists
+	has, err := c.HasCollection(ctx, sch.CollectionName)
+	if err != nil {
+		return err
+	}
+	// already exists collection with same name, return error
+	if has {
+		return fmt.Errorf("collection %s already exist", sch.CollectionName)
+	}
+	// marshal schema to bytes for message transfer
+	p := sch.ProtoMessage()
+	bs, err := proto.Marshal(p)
+	if err != nil {
+		return err
+	}
+	// compose request and invoke service
+	req := &server.CreateCollectionRequest{
+		DbName:         "", // reserved fields, not used for now
+		CollectionName: sch.CollectionName,
+		Schema:         bs,
+		ShardsNum:      shardNum,
+	}
+	resp, err := c.service.CreateCollection(ctx, req)
+	// handles response
+	if err != nil {
+		return err
+	}
+	err = handleRespStatus(resp)
+	if err != nil {
+		return nil
+	}
+	return nil
+}
+
+// SearchResultByRows search result for row-based Search
 type SearchResultByRows struct {
 	ResultCount int
 	Scores      []float32
@@ -16,8 +65,14 @@ type SearchResultByRows struct {
 	Err         error
 }
 
-func SearchResultToRows(sch *entity.Schema, results *schema.SearchResultData, t reflect.Type) {
+// SearchResultToRows converts search result proto to rows
+func SearchResultToRows(sch *entity.Schema, results *schema.SearchResultData, t reflect.Type, output map[string]struct{}) ([]SearchResultByRows, error) {
+	var err error
 	offset := 0
+	// new will have a pointer, so de-reference first if type is pointer to struct
+	if t.Kind() == reflect.Ptr {
+		t = t.Elem()
+	}
 	sr := make([]SearchResultByRows, 0, results.GetNumQueries())
 	fieldDataList := results.GetFieldsData()
 	nameFieldData := make(map[string]*schema.FieldData)
@@ -38,11 +93,14 @@ func SearchResultToRows(sch *entity.Schema, results *schema.SearchResultData, t
 
 			//extract primary field logic
 			for _, field := range sch.Fields {
-				f := v.FieldByName(field.Name)
-				if f.IsNil() {
-					//TODO check field in output list
+				f := v.FieldByName(field.Name) // TODO silverxia field may be annotated by tags, which means the field name will not be the same
+				if !f.IsValid() {
+					if _, has := output[field.Name]; has {
+						// TODO silverxia in output field list but not defined in rows
+					}
 					continue
 				}
+				// Primary key has different field from search result
 				if field.PrimaryKey {
 					switch f.Kind() {
 					case reflect.Int64:
@@ -69,25 +127,45 @@ func SearchResultToRows(sch *entity.Schema, results *schema.SearchResultData, t
 				//fieldDataList
 				fieldData, has := nameFieldData[field.Name]
 				if !has || fieldData == nil {
-					//TODO if check field in output list
+					if _, has := output[field.Name]; has {
+						// TODO silverxia in output field list but not defined in rows
+					}
+
 					continue
 				}
 
-				offset += rc
-				sr = append(sr, entry)
-			}
+				// Set field value with offset+j-th item
+				err = SetFieldValue(field, f, fieldData, offset+j)
+				if err != nil {
+					entry.Err = err
+					break
+				}
 
+			}
+			r := p.Interface()
+			row, ok := r.(entity.Row)
+			if ok {
+				entry.Rows = append(entry.Rows, row)
+			}
 		}
+		sr = append(sr, entry)
+		// set offset after processed one result
+		offset += rc
 	}
+	return sr, nil
 }
 
 var (
+	// ErrFieldTypeNotMatch error for field type not match
 	ErrFieldTypeNotMatch = errors.New("field type not matched")
 )
 
+// SetFieldValue set row field value with reflection
 func SetFieldValue(field *entity.Field, f reflect.Value, fieldData *schema.FieldData, idx int) error {
 	scalars := fieldData.GetScalars()
 	vectors := fieldData.GetVectors()
+	// This switch part is messy
+	// Maybe this can be refactored later
 	switch field.DataType {
 	case entity.FieldTypeBool:
 		if f.Kind() != reflect.Bool {
@@ -121,6 +199,9 @@ func SetFieldValue(field *entity.Field, f reflect.Value, fieldData *schema.Field
 			return ErrFieldTypeNotMatch
 		}
 		data := scalars.GetIntData()
+		if data == nil {
+			return ErrFieldTypeNotMatch
+		}
 		f.SetInt(int64(data.Data[idx]))
 	case entity.FieldTypeInt32:
 		if f.Kind() != reflect.Int32 {
@@ -130,6 +211,9 @@ func SetFieldValue(field *entity.Field, f reflect.Value, fieldData *schema.Field
 			return ErrFieldTypeNotMatch
 		}
 		data := scalars.GetIntData()
+		if data == nil {
+			return ErrFieldTypeNotMatch
+		}
 		f.SetInt(int64(data.Data[idx]))
 	case entity.FieldTypeInt64:
 		if f.Kind() != reflect.Int64 {
@@ -138,9 +222,11 @@ func SetFieldValue(field *entity.Field, f reflect.Value, fieldData *schema.Field
 		if scalars == nil {
 			return ErrFieldTypeNotMatch
 		}
-		data := scalars.GetIntData()
-		f.SetInt(int64(data.Data[idx]))
-
+		data := scalars.GetLongData()
+		if data == nil {
+			return ErrFieldTypeNotMatch
+		}
+		f.SetInt(data.Data[idx])
 	case entity.FieldTypeFloat:
 		if f.Kind() != reflect.Float32 {
 			return ErrFieldTypeNotMatch
@@ -149,6 +235,10 @@ func SetFieldValue(field *entity.Field, f reflect.Value, fieldData *schema.Field
 			return ErrFieldTypeNotMatch
 		}
 		data := scalars.GetFloatData()
+		if data == nil {
+			return ErrFieldTypeNotMatch
+		}
+
 		f.SetFloat(float64(data.Data[idx]))
 	case entity.FieldTypeDouble:
 		if f.Kind() != reflect.Float64 {
@@ -158,6 +248,10 @@ func SetFieldValue(field *entity.Field, f reflect.Value, fieldData *schema.Field
 			return ErrFieldTypeNotMatch
 		}
 		data := scalars.GetDoubleData()
+		if data == nil {
+			return ErrFieldTypeNotMatch
+		}
+
 		f.SetFloat(data.Data[idx])
 	case entity.FieldTypeString:
 		if f.Kind() != reflect.String {
@@ -167,6 +261,10 @@ func SetFieldValue(field *entity.Field, f reflect.Value, fieldData *schema.Field
 			return ErrFieldTypeNotMatch
 		}
 		data := scalars.GetStringData()
+		if data == nil {
+			return ErrFieldTypeNotMatch
+		}
+
 		f.SetString(data.Data[idx])
 
 	case entity.FieldTypeFloatVector:
@@ -205,7 +303,7 @@ func SetFieldValue(field *entity.Field, f reflect.Value, fieldData *schema.Field
 		case reflect.Slice:
 			f.Set(reflect.ValueOf(vector))
 		case reflect.Array:
-			arrType := reflect.ArrayOf(int(vectors.Dim), reflect.TypeOf(byte(0)))
+			arrType := reflect.ArrayOf(int(vectors.Dim/8), reflect.TypeOf(byte(0)))
 			arr := reflect.New(arrType).Elem()
 			for i := 0; i < int(vectors.Dim/8); i++ {
 				arr.Index(i).Set(reflect.ValueOf(vector[i]))
@@ -214,6 +312,8 @@ func SetFieldValue(field *entity.Field, f reflect.Value, fieldData *schema.Field
 		default:
 			return ErrFieldTypeNotMatch
 		}
+	default:
+		return ErrFieldTypeNotMatch
 	}
 	return nil
 }


Test patch:
diff --git a/client/client_grpc_collection_test.go b/client/client_grpc_collection_test.go
index 286575b..6d238ad 100644
--- a/client/client_grpc_collection_test.go
+++ b/client/client_grpc_collection_test.go
@@ -5,7 +5,6 @@ import (
 	"errors"
 	"fmt"
 	"math/rand"
-	"reflect"
 	"testing"
 	"time"
 
@@ -15,110 +14,8 @@ import (
 	"github.com/milvus-io/milvus-sdk-go/v2/internal/proto/schema"
 	"github.com/milvus-io/milvus-sdk-go/v2/internal/proto/server"
 	"github.com/stretchr/testify/assert"
-	"google.golang.org/grpc"
 )
 
-func TestHandleRespStatus(t *testing.T) {
-	assert.NotNil(t, handleRespStatus(nil))
-	assert.Nil(t, handleRespStatus(&common.Status{
-		ErrorCode: common.ErrorCode_Success,
-	}))
-	assert.NotNil(t, handleRespStatus(&common.Status{
-		ErrorCode: common.ErrorCode_UnexpectedError,
-	}))
-}
-
-func TestGrpcClientNil(t *testing.T) {
-	c := &grpcClient{}
-	tp := reflect.TypeOf(c)
-	v := reflect.ValueOf(c)
-	ctx := context.Background()
-	c2 := testClient(ctx, t)
-	v2 := reflect.ValueOf(c2)
-
-	ctxDone, cancel := context.WithCancel(context.Background())
-	cancel() // cancel here, so the ctx is done already
-
-	for i := 0; i < tp.NumMethod(); i++ {
-		m := tp.Method(i)
-		mt := m.Type                                   // type of function
-		if m.Name == "Close" || m.Name == "Connect" || // skip connect & close
-			m.Name == "Search" || // type alias MetricType treated as string
-			m.Name == "CalcDistance" ||
-			m.Name == "Insert" { // complex methods with ...
-			continue
-		}
-		ins := make([]reflect.Value, 0, mt.NumIn())
-		for j := 1; j < mt.NumIn(); j++ { // idx == 0, is the receiver v
-			if j == 1 {
-				//non-general solution, hard code context!
-				ins = append(ins, reflect.ValueOf(ctx))
-				continue
-			}
-			inT := mt.In(j)
-
-			switch inT.Kind() {
-			case reflect.String: // pass empty
-				ins = append(ins, reflect.ValueOf(""))
-			case reflect.Int, reflect.Int64:
-				ins = append(ins, reflect.ValueOf(0))
-			case reflect.Bool:
-				ins = append(ins, reflect.ValueOf(false))
-			case reflect.Interface:
-				idxType := reflect.TypeOf((*entity.Index)(nil)).Elem()
-				if inT.Implements(idxType) {
-					ins = append(ins, reflect.ValueOf(entity.NewFlatIndex("flat_index", entity.L2)))
-				}
-			default:
-				ins = append(ins, reflect.Zero(inT))
-			}
-		}
-		outs := v.MethodByName(m.Name).Call(ins)
-		assert.True(t, len(outs) > 0)
-		assert.EqualValues(t, ErrClientNotReady, outs[len(outs)-1].Interface())
-
-		// ctx done
-
-		if len(ins) > 0 { // with context param
-			ins[0] = reflect.ValueOf(ctxDone)
-			outs := v2.MethodByName(m.Name).Call(ins)
-			assert.True(t, len(outs) > 0)
-			assert.False(t, outs[len(outs)-1].IsNil())
-		}
-	}
-}
-
-func TestGrpcClientConnect(t *testing.T) {
-	ctx := context.Background()
-
-	t.Run("Use bufconn dailer, testing case", func(t *testing.T) {
-		c, err := NewGrpcClient(ctx, "bufnet", grpc.WithBlock(), grpc.WithInsecure(), grpc.WithContextDialer(bufDialer)) // uses in memory buf connection
-		assert.Nil(t, err)
-		assert.NotNil(t, c)
-	})
-
-	t.Run("Test empty addr, using default timeout", func(t *testing.T) {
-		c, err := NewGrpcClient(ctx, "")
-		assert.NotNil(t, err)
-		assert.Nil(t, c)
-	})
-}
-
-func TestGrpcClientClose(t *testing.T) {
-	ctx := context.Background()
-
-	t.Run("normal close", func(t *testing.T) {
-		c := testClient(ctx, t)
-		assert.Nil(t, c.Close())
-	})
-
-	t.Run("double close", func(t *testing.T) {
-		c := testClient(ctx, t)
-		assert.Nil(t, c.Close())
-		assert.Nil(t, c.Close())
-	})
-}
-
 func TestGrpcClientListCollections(t *testing.T) {
 	ctx := context.Background()
 	c := testClient(ctx, t)
@@ -563,11 +460,3 @@ func TestGrpcClientGetCollectionStatistics(t *testing.T) {
 		}
 	}
 }
-
-func successStatus() (*common.Status, error) {
-	return &common.Status{ErrorCode: common.ErrorCode_Success}, nil
-}
-
-func badRequestStatus() (*common.Status, error) {
-	return &common.Status{ErrorCode: common.ErrorCode_IllegalArgument}, errors.New("illegal request type")
-}
diff --git a/client/client_grpc_row_test.go b/client/client_grpc_row_test.go
index 9f8f4c8..e4f9cda 100644
--- a/client/client_grpc_row_test.go
+++ b/client/client_grpc_row_test.go
@@ -1,91 +1,501 @@
 package client
 
 import (
+	"context"
+	"errors"
+	"fmt"
 	"reflect"
 	"testing"
 
+	"github.com/golang/protobuf/proto"
 	"github.com/milvus-io/milvus-sdk-go/v2/entity"
+	"github.com/milvus-io/milvus-sdk-go/v2/internal/proto/common"
 	"github.com/milvus-io/milvus-sdk-go/v2/internal/proto/schema"
+	"github.com/milvus-io/milvus-sdk-go/v2/internal/proto/server"
 	"github.com/stretchr/testify/assert"
 )
 
+func TestCreateCollectionByRow(t *testing.T) {
+	ctx := context.Background()
+	c := testClient(ctx, t)
+	type ValidStruct struct {
+		entity.RowBase
+		ID     int64 `milvus:"primary_key"`
+		Attr1  int8
+		Attr2  int16
+		Attr3  int32
+		Attr4  float32
+		Attr5  float64
+		Attr6  string
+		Vector []float32 `milvus:"dim:4"`
+	}
+	t.Run("Test normal creation", func(t *testing.T) {
+		mock.delInjection(mHasCollection)
+		shardsNum := int32(1)
+		mock.setInjection(mCreateCollection, func(ctx context.Context, raw proto.Message) (proto.Message, error) {
+			req, ok := raw.(*server.CreateCollectionRequest)
+			if !ok {
+				return &common.Status{ErrorCode: common.ErrorCode_IllegalArgument}, errors.New("illegal request type")
+			}
+			assert.Equal(t, "ValidStruct", req.GetCollectionName())
+			sschema := &schema.CollectionSchema{}
+			if !assert.Nil(t, proto.Unmarshal(req.GetSchema(), sschema)) {
+				assert.Equal(t, 8, len(sschema.Fields))
+				assert.Equal(t, shardsNum, req.GetShardsNum())
+			}
+
+			return &common.Status{ErrorCode: common.ErrorCode_Success}, nil
+		})
+		assert.Nil(t, c.CreateCollectionByRow(ctx, &ValidStruct{}, shardsNum))
+	})
+
+	t.Run("Invalid cases", func(t *testing.T) {
+		//Duplicated
+		m := make(map[string]struct{})
+		mock.setInjection(mCreateCollection, func(_ context.Context, raw proto.Message) (proto.Message, error) {
+			req, ok := raw.(*server.CreateCollectionRequest)
+			if !ok {
+				return badRequestStatus()
+			}
+			m[req.GetCollectionName()] = struct{}{}
+
+			return successStatus()
+		})
+		mock.setInjection(mHasCollection, func(_ context.Context, raw proto.Message) (proto.Message, error) {
+			req, ok := raw.(*server.HasCollectionRequest)
+			resp := &server.BoolResponse{}
+			if !ok {
+				return badRequestStatus()
+			}
+
+			_, has := m[req.GetCollectionName()]
+			resp.Value = has
+			s, err := successStatus()
+			resp.Status = s
+			return resp, err
+		})
+		assert.Nil(t, c.CreateCollectionByRow(ctx, &ValidStruct{}, 1))
+		assert.NotNil(t, c.CreateCollectionByRow(ctx, &ValidStruct{}, 1))
+		// Invalid struct
+		anonymusStruct := struct {
+			entity.RowBase
+		}{}
+
+		assert.NotNil(t, c.CreateCollectionByRow(ctx, &anonymusStruct, 1))
+	})
+}
+
+func TestSearchResultToRows(t *testing.T) {
+	t.Run("successful test cases", func(t *testing.T) {
+		sr := &schema.SearchResultData{
+			NumQueries: 1,
+			TopK:       3,
+			FieldsData: []*schema.FieldData{
+				longFieldData("ID", []int64{1, 2, 3}),
+				intFieldData("Attr1", []int32{1, 2, 3}),
+				intFieldData("Attr2", []int32{1, 2, 3}),
+				intFieldData("Attr3", []int32{1, 2, 3}),
+				floatFieldData("Attr4", []float32{0.1, 0.2, 0.3}),
+				doubleFieldData("Attr5", []float64{0.1, 0.2, 0.3}),
+				stringFieldData("Attr6", []string{"1", "2", "3"}),
+				floatVectorFieldData("Vector", 4, []float32{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}),
+			},
+			Scores: []float32{0.1, 0.2, 0.3},
+			Ids: &schema.IDs{
+				IdField: &schema.IDs_IntId{
+					IntId: &schema.LongArray{
+						Data: []int64{1, 2, 3},
+					},
+				},
+			},
+			Topks: []int64{3},
+		}
+
+		type ValidStruct struct {
+			entity.RowBase
+			ID     int64 `milvus:"primary_key"`
+			Attr1  int8
+			Attr2  int16
+			Attr3  int32
+			Attr4  float32
+			Attr5  float64
+			Attr6  string
+			Vector []float32 `milvus:"dim:4"`
+		}
+		sch, err := entity.ParseSchema(&ValidStruct{})
+		assert.Nil(t, err)
+		results, err := SearchResultToRows(sch, sr, reflect.TypeOf(&ValidStruct{}), arrMap("ID", "Vector", "Attr1", "Attr2", "Attr3", "Attr4", "Attr5", "Attr6"))
+		assert.Nil(t, err)
+		assert.NotNil(t, results)
+		if assert.Equal(t, 1, len(results)) {
+			result := results[0]
+			assert.EqualValues(t, []float32{0.1, 0.2, 0.3}, result.Scores)
+			if assert.Nil(t, result.Err) {
+				if !assert.Equal(t, 3, len(result.Rows)) {
+					t.FailNow()
+				}
+				for i, row := range result.Rows {
+					s, ok := row.(*ValidStruct)
+					if assert.True(t, ok) {
+						assert.EqualValues(t, i+1, s.ID)
+						assert.EqualValues(t, i+1, s.Attr1)
+						assert.EqualValues(t, i+1, s.Attr2)
+						assert.EqualValues(t, i+1, s.Attr3)
+						assert.EqualValues(t, float32(i+1)/10.0, s.Attr4)
+						assert.EqualValues(t, float64(i+1)/10.0, s.Attr5)
+						assert.Equal(t, fmt.Sprintf("%d", i+1), s.Attr6)
+					}
+				}
+			}
+		}
+	})
+}
+
+func arrMap(items ...string) map[string]struct{} {
+	r := make(map[string]struct{})
+	for _, item := range items {
+		r[item] = struct{}{}
+	}
+	return r
+}
+
 func TestSetFieldValue(t *testing.T) {
 	type TestStruct struct {
-		Int8  int8
-		Int16 int16
-		Int32 int32
-		Int64 int64
-		Arr   [8]float32
+		Bool   bool
+		Int8   int8
+		Int16  int16
+		Int32  int32
+		Int64  int64
+		Float  float32
+		Double float64
+		String string
+		Arr    [8]float32
+		ArrBin [8]byte
 	}
 
-	var err error
-	item := &TestStruct{}
-	i8 := reflect.ValueOf(item).Elem().FieldByName("Int8")
-	err = SetFieldValue(&entity.Field{
-		DataType: entity.FieldTypeInt8,
-	}, i8, &schema.FieldData{
+	t.Run("successful cases", func(t *testing.T) {
+		var err error
+		item := &TestStruct{}
+		// test bool field
+		b := reflect.ValueOf(item).Elem().FieldByName("Bool")
+		err = SetFieldValue(&entity.Field{
+			DataType: entity.FieldTypeBool,
+		}, b, boolFieldData("", []bool{true}), 0)
+		assert.Nil(t, err)
+		assert.EqualValues(t, true, item.Bool)
+
+		// test int 8 field
+		i8 := reflect.ValueOf(item).Elem().FieldByName("Int8")
+		err = SetFieldValue(&entity.Field{
+			DataType: entity.FieldTypeInt8,
+		}, i8, intFieldData("", []int32{10}), 0)
+		assert.Nil(t, err)
+		assert.EqualValues(t, 10, item.Int8)
+
+		i16 := reflect.ValueOf(item).Elem().FieldByName("Int16")
+		err = SetFieldValue(&entity.Field{
+			DataType: entity.FieldTypeInt16,
+		}, i16, intFieldData("", []int32{10}), 0)
+		assert.Nil(t, err)
+		assert.EqualValues(t, 10, item.Int16)
+
+		i32 := reflect.ValueOf(item).Elem().FieldByName("Int32")
+		err = SetFieldValue(&entity.Field{
+			DataType: entity.FieldTypeInt32,
+		}, i32, intFieldData("", []int32{10}), 0)
+		assert.Nil(t, err)
+		assert.EqualValues(t, 10, item.Int32)
+
+		i64 := reflect.ValueOf(item).Elem().FieldByName("Int64")
+		err = SetFieldValue(&entity.Field{
+			DataType: entity.FieldTypeInt64,
+		}, i64, longFieldData("", []int64{10}), 0)
+		assert.Nil(t, err)
+		assert.EqualValues(t, 10, item.Int64)
+
+		f32 := reflect.ValueOf(item).Elem().FieldByName("Float")
+		err = SetFieldValue(&entity.Field{
+			DataType: entity.FieldTypeFloat,
+		}, f32, floatFieldData("", []float32{0.618}), 0)
+		assert.Nil(t, err)
+		assert.InDelta(t, 0.618, item.Float, 1e-6)
+
+		f64 := reflect.ValueOf(item).Elem().FieldByName("Double")
+		err = SetFieldValue(&entity.Field{
+			DataType: entity.FieldTypeDouble,
+		}, f64, doubleFieldData("", []float64{0.618}), 0)
+		assert.Nil(t, err)
+		assert.InDelta(t, 0.618, item.Double, 1e-6)
+
+		str := reflect.ValueOf(item).Elem().FieldByName("String")
+		err = SetFieldValue(&entity.Field{
+			DataType: entity.FieldTypeString,
+		}, str, stringFieldData("", []string{"test"}), 0)
+		assert.Nil(t, err)
+		assert.EqualValues(t, "test", item.String)
+
+		// float32 array field
+		arr := reflect.ValueOf(item).Elem().FieldByName("Arr")
+		err = SetFieldValue(&entity.Field{
+			DataType: entity.FieldTypeFloatVector,
+		}, arr, floatVectorFieldData("", 8, []float32{0, 1, 2, 3, 4, 5, 6, 7}), 0)
+		assert.Nil(t, err)
+		assert.EqualValues(t, [8]float32{0, 1, 2, 3, 4, 5, 6, 7}, item.Arr)
+
+		binArr := reflect.ValueOf(item).Elem().FieldByName("ArrBin")
+		err = SetFieldValue(&entity.Field{
+			DataType: entity.FieldTypeBinaryVector,
+		}, binArr, binaryVectorFieldData("", []byte{'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a'}), 0)
+		assert.Nil(t, err)
+		assert.EqualValues(t, [8]byte{'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a'}, item.ArrBin)
+	})
+
+	t.Run("fail cases", func(t *testing.T) {
+		var err error
+		item := &TestStruct{}
+		// invalid data type
+		b := reflect.ValueOf(item).Elem().FieldByName("Bool")
+		i8 := reflect.ValueOf(item).Elem().FieldByName("Int8")
+		i16 := reflect.ValueOf(item).Elem().FieldByName("Int16")
+		i32 := reflect.ValueOf(item).Elem().FieldByName("Int32")
+		i64 := reflect.ValueOf(item).Elem().FieldByName("Int64")
+		f32 := reflect.ValueOf(item).Elem().FieldByName("Float")
+		f64 := reflect.ValueOf(item).Elem().FieldByName("Double")
+		str := reflect.ValueOf(item).Elem().FieldByName("String")
+		vf := reflect.ValueOf(item).Elem().FieldByName("Arr")
+		//vb := reflect.ValueOf(item).Elem().FieldByName("ArrBin")
+
+		err = SetFieldValue(&entity.Field{
+			DataType: entity.FieldTypeNone,
+		}, b, boolFieldData("", []bool{true}), 0)
+		assert.Equal(t, ErrFieldTypeNotMatch, err)
+		// field type not matched cases
+
+		// bool
+		err = SetFieldValue(&entity.Field{DataType: entity.FieldTypeBool}, i8, boolFieldData("", []bool{true}), 0)
+		assert.Equal(t, err, ErrFieldTypeNotMatch)
+		err = SetFieldValue(&entity.Field{DataType: entity.FieldTypeBool}, b, emptyFieldData(), 0)
+		assert.Equal(t, err, ErrFieldTypeNotMatch)
+		err = SetFieldValue(&entity.Field{DataType: entity.FieldTypeBool}, b, emptyScalarFieldData(), 0)
+		assert.Equal(t, err, ErrFieldTypeNotMatch)
+
+		// int8
+		err = SetFieldValue(&entity.Field{DataType: entity.FieldTypeInt8}, b, intFieldData("", []int32{10}), 0)
+		assert.Equal(t, err, ErrFieldTypeNotMatch)
+		err = SetFieldValue(&entity.Field{DataType: entity.FieldTypeInt8}, i8, emptyFieldData(), 0)
+		assert.Equal(t, err, ErrFieldTypeNotMatch)
+		err = SetFieldValue(&entity.Field{DataType: entity.FieldTypeInt8}, i8, emptyScalarFieldData(), 0)
+		assert.Equal(t, err, ErrFieldTypeNotMatch)
+
+		// int16
+		err = SetFieldValue(&entity.Field{DataType: entity.FieldTypeInt16}, b, intFieldData("", []int32{10}), 0)
+		assert.Equal(t, err, ErrFieldTypeNotMatch)
+		err = SetFieldValue(&entity.Field{DataType: entity.FieldTypeInt16}, i16, emptyFieldData(), 0)
+		assert.Equal(t, err, ErrFieldTypeNotMatch)
+		err = SetFieldValue(&entity.Field{DataType: entity.FieldTypeInt16}, i16, emptyScalarFieldData(), 0)
+		assert.Equal(t, err, ErrFieldTypeNotMatch)
+
+		// int32
+		err = SetFieldValue(&entity.Field{DataType: entity.FieldTypeInt32}, b, intFieldData("", []int32{10}), 0)
+		assert.Equal(t, err, ErrFieldTypeNotMatch)
+		err = SetFieldValue(&entity.Field{DataType: entity.FieldTypeInt32}, i32, emptyFieldData(), 0)
+		assert.Equal(t, err, ErrFieldTypeNotMatch)
+		err = SetFieldValue(&entity.Field{DataType: entity.FieldTypeInt32}, i32, emptyScalarFieldData(), 0)
+		assert.Equal(t, err, ErrFieldTypeNotMatch)
+
+		// int64
+		err = SetFieldValue(&entity.Field{DataType: entity.FieldTypeInt64}, b, longFieldData("", []int64{10}), 0)
+		assert.Equal(t, err, ErrFieldTypeNotMatch)
+		err = SetFieldValue(&entity.Field{DataType: entity.FieldTypeInt64}, i64, emptyFieldData(), 0)
+		assert.Equal(t, err, ErrFieldTypeNotMatch)
+		err = SetFieldValue(&entity.Field{DataType: entity.FieldTypeInt64}, i64, emptyScalarFieldData(), 0)
+		assert.Equal(t, err, ErrFieldTypeNotMatch)
+
+		// float
+		err = SetFieldValue(&entity.Field{DataType: entity.FieldTypeFloat}, b, floatFieldData("", []float32{0.6}), 0)
+		assert.Equal(t, err, ErrFieldTypeNotMatch)
+		err = SetFieldValue(&entity.Field{DataType: entity.FieldTypeFloat}, f32, emptyFieldData(), 0)
+		assert.Equal(t, err, ErrFieldTypeNotMatch)
+		err = SetFieldValue(&entity.Field{DataType: entity.FieldTypeFloat}, f32, emptyScalarFieldData(), 0)
+		assert.Equal(t, err, ErrFieldTypeNotMatch)
+
+		// double
+		err = SetFieldValue(&entity.Field{DataType: entity.FieldTypeDouble}, b, doubleFieldData("", []float64{0.6}), 0)
+		assert.Equal(t, err, ErrFieldTypeNotMatch)
+		err = SetFieldValue(&entity.Field{DataType: entity.FieldTypeDouble}, f64, emptyFieldData(), 0)
+		assert.Equal(t, err, ErrFieldTypeNotMatch)
+		err = SetFieldValue(&entity.Field{DataType: entity.FieldTypeDouble}, f64, emptyScalarFieldData(), 0)
+		assert.Equal(t, err, ErrFieldTypeNotMatch)
+
+		// string
+		err = SetFieldValue(&entity.Field{DataType: entity.FieldTypeString}, b, stringFieldData("", []string{"test"}), 0)
+		assert.Equal(t, err, ErrFieldTypeNotMatch)
+		err = SetFieldValue(&entity.Field{DataType: entity.FieldTypeString}, str, emptyFieldData(), 0)
+		assert.Equal(t, err, ErrFieldTypeNotMatch)
+		err = SetFieldValue(&entity.Field{DataType: entity.FieldTypeString}, str, emptyScalarFieldData(), 0)
+		assert.Equal(t, err, ErrFieldTypeNotMatch)
+
+		// float vector
+		err = SetFieldValue(&entity.Field{DataType: entity.FieldTypeFloatVector}, b, floatVectorFieldData("", 4, []float32{1, 2, 3, 4}), 0)
+		assert.Equal(t, err, ErrFieldTypeNotMatch)
+		err = SetFieldValue(&entity.Field{DataType: entity.FieldTypeFloatVector}, vf, emptyFieldData(), 0)
+		assert.Equal(t, err, ErrFieldTypeNotMatch)
+		err = SetFieldValue(&entity.Field{DataType: entity.FieldTypeFloatVector}, vf, emptyVectorFieldData(), 0)
+		assert.Equal(t, err, ErrFieldTypeNotMatch)
+
+		// binary vector
+		err = SetFieldValue(&entity.Field{DataType: entity.FieldTypeBinaryVector}, b, binaryVectorFieldData("", []byte{1, 2, 3, 4}), 0)
+		assert.Equal(t, err, ErrFieldTypeNotMatch)
+		err = SetFieldValue(&entity.Field{DataType: entity.FieldTypeBinaryVector}, vf, emptyFieldData(), 0)
+		assert.Equal(t, err, ErrFieldTypeNotMatch)
+		err = SetFieldValue(&entity.Field{DataType: entity.FieldTypeBinaryVector}, vf, emptyVectorFieldData(), 0)
+		assert.Equal(t, err, ErrFieldTypeNotMatch)
+	})
+}
+
+func emptyFieldData() *schema.FieldData {
+	return &schema.FieldData{}
+}
+func emptyScalarFieldData() *schema.FieldData {
+	return &schema.FieldData{
+		Field: &schema.FieldData_Scalars{
+			Scalars: &schema.ScalarField{},
+		},
+	}
+}
+
+func emptyVectorFieldData() *schema.FieldData {
+	return &schema.FieldData{
+		Field: &schema.FieldData_Vectors{
+			Vectors: &schema.VectorField{},
+		},
+	}
+}
+
+func boolFieldData(name string, data []bool) *schema.FieldData {
+	return &schema.FieldData{
+		FieldName: name,
+		Type:      schema.DataType_Bool,
 		Field: &schema.FieldData_Scalars{
 			Scalars: &schema.ScalarField{
-				Data: &schema.ScalarField_IntData{
-					IntData: &schema.IntArray{
-						Data: []int32{10},
+				Data: &schema.ScalarField_BoolData{
+					BoolData: &schema.BoolArray{
+						Data: data,
 					},
 				},
 			},
 		},
-	}, 0)
-	assert.Nil(t, err)
-	assert.EqualValues(t, 10, item.Int8)
-
-	i16 := reflect.ValueOf(item).Elem().FieldByName("Int16")
-	err = SetFieldValue(&entity.Field{
-		DataType: entity.FieldTypeInt16,
-	}, i16, &schema.FieldData{
+	}
+}
+
+func intFieldData(name string, data []int32) *schema.FieldData {
+	return &schema.FieldData{
+		FieldName: name,
+		// Type not determined
 		Field: &schema.FieldData_Scalars{
 			Scalars: &schema.ScalarField{
 				Data: &schema.ScalarField_IntData{
 					IntData: &schema.IntArray{
-						Data: []int32{10},
+						Data: data,
 					},
 				},
 			},
 		},
-	}, 0)
-	assert.Nil(t, err)
-	assert.EqualValues(t, 10, item.Int16)
-
-	i32 := reflect.ValueOf(item).Elem().FieldByName("Int32")
-	err = SetFieldValue(&entity.Field{
-		DataType: entity.FieldTypeInt32,
-	}, i32, &schema.FieldData{
+	}
+}
+
+func longFieldData(name string, data []int64) *schema.FieldData {
+	return &schema.FieldData{
+		FieldName: name,
+		Type:      schema.DataType_Int64,
 		Field: &schema.FieldData_Scalars{
 			Scalars: &schema.ScalarField{
-				Data: &schema.ScalarField_IntData{
-					IntData: &schema.IntArray{
-						Data: []int32{10},
+				Data: &schema.ScalarField_LongData{
+					LongData: &schema.LongArray{
+						Data: data,
+					},
+				},
+			},
+		},
+	}
+}
+
+func floatFieldData(name string, data []float32) *schema.FieldData {
+	return &schema.FieldData{
+		FieldName: name,
+		Type:      schema.DataType_Float,
+		Field: &schema.FieldData_Scalars{
+			Scalars: &schema.ScalarField{
+				Data: &schema.ScalarField_FloatData{
+					FloatData: &schema.FloatArray{
+						Data: data,
+					},
+				},
+			},
+		},
+	}
+}
+
+func doubleFieldData(name string, data []float64) *schema.FieldData {
+	return &schema.FieldData{
+		FieldName: name,
+		Type:      schema.DataType_Double,
+		Field: &schema.FieldData_Scalars{
+			Scalars: &schema.ScalarField{
+				Data: &schema.ScalarField_DoubleData{
+					DoubleData: &schema.DoubleArray{
+						Data: data,
+					},
+				},
+			},
+		},
+	}
+}
+
+func stringFieldData(name string, data []string) *schema.FieldData {
+	return &schema.FieldData{
+		FieldName: name,
+		Type:      schema.DataType_String,
+		Field: &schema.FieldData_Scalars{
+			Scalars: &schema.ScalarField{
+				Data: &schema.ScalarField_StringData{
+					StringData: &schema.StringArray{
+						Data: data,
 					},
 				},
 			},
 		},
-	}, 0)
-	assert.Nil(t, err)
-	assert.EqualValues(t, 10, item.Int32)
-
-	arr := reflect.ValueOf(item).Elem().FieldByName("Arr")
-	err = SetFieldValue(&entity.Field{
-		DataType: entity.FieldTypeFloatVector,
-	}, arr, &schema.FieldData{
+	}
+}
+
+func floatVectorFieldData(name string, dim int, data []float32) *schema.FieldData {
+	return &schema.FieldData{
+		FieldName: name,
+		Type:      schema.DataType_FloatVector,
 		Field: &schema.FieldData_Vectors{
 			Vectors: &schema.VectorField{
-				Dim: 8,
+				Dim: int64(dim),
 				Data: &schema.VectorField_FloatVector{
 					FloatVector: &schema.FloatArray{
-						Data: []float32{0, 1, 2, 3, 4, 5, 6, 7},
+						Data: data,
 					},
 				},
 			},
 		},
-	}, 0)
-	assert.Nil(t, err)
-	assert.EqualValues(t, [8]float32{0, 1, 2, 3, 4, 5, 6, 7}, item.Arr)
+	}
+}
+
+func binaryVectorFieldData(name string, data []byte) *schema.FieldData {
+	return &schema.FieldData{
+		FieldName: name,
+		Type:      schema.DataType_BinaryVector,
+		Field: &schema.FieldData_Vectors{
+			Vectors: &schema.VectorField{
+				Dim: int64(8 * len(data)),
+				Data: &schema.VectorField_BinaryVector{
+					BinaryVector: data,
+				},
+			},
+		},
+	}
 }
diff --git a/client/client_test.go b/client/client_test.go
new file mode 100644
index 0000000..4760ae5
--- /dev/null
+++ b/client/client_test.go
@@ -0,0 +1,138 @@
+package client
+
+import (
+	"context"
+	"errors"
+	"reflect"
+	"testing"
+
+	"github.com/milvus-io/milvus-sdk-go/v2/entity"
+	"github.com/milvus-io/milvus-sdk-go/v2/internal/proto/common"
+	"github.com/stretchr/testify/assert"
+	"google.golang.org/grpc"
+)
+
+func TestHandleRespStatus(t *testing.T) {
+	assert.NotNil(t, handleRespStatus(nil))
+	assert.Nil(t, handleRespStatus(&common.Status{
+		ErrorCode: common.ErrorCode_Success,
+	}))
+	assert.NotNil(t, handleRespStatus(&common.Status{
+		ErrorCode: common.ErrorCode_UnexpectedError,
+	}))
+}
+
+type ValidStruct struct {
+	entity.RowBase
+	ID     int64 `milvus:"primary_key"`
+	Attr1  int8
+	Attr2  int16
+	Attr3  int32
+	Attr4  float32
+	Attr5  float64
+	Attr6  string
+	Vector []float32 `milvus:"dim:128"`
+}
+
+func TestGrpcClientNil(t *testing.T) {
+	c := &grpcClient{}
+	tp := reflect.TypeOf(c)
+	v := reflect.ValueOf(c)
+	ctx := context.Background()
+	c2 := testClient(ctx, t)
+	v2 := reflect.ValueOf(c2)
+
+	ctxDone, cancel := context.WithCancel(context.Background())
+	cancel() // cancel here, so the ctx is done already
+
+	for i := 0; i < tp.NumMethod(); i++ {
+		m := tp.Method(i)
+		mt := m.Type                                   // type of function
+		if m.Name == "Close" || m.Name == "Connect" || // skip connect & close
+			m.Name == "Search" || // type alias MetricType treated as string
+			m.Name == "CalcDistance" ||
+			m.Name == "Insert" { // complex methods with ...
+			continue
+		}
+		ins := make([]reflect.Value, 0, mt.NumIn())
+		for j := 1; j < mt.NumIn(); j++ { // idx == 0, is the receiver v
+			if j == 1 {
+				//non-general solution, hard code context!
+				ins = append(ins, reflect.ValueOf(ctx))
+				continue
+			}
+			inT := mt.In(j)
+
+			switch inT.Kind() {
+			case reflect.String: // pass empty
+				ins = append(ins, reflect.ValueOf(""))
+			case reflect.Int, reflect.Int64:
+				ins = append(ins, reflect.ValueOf(0))
+			case reflect.Bool:
+				ins = append(ins, reflect.ValueOf(false))
+			case reflect.Interface:
+				idxType := reflect.TypeOf((*entity.Index)(nil)).Elem()
+				rowType := reflect.TypeOf((*entity.Row)(nil)).Elem()
+				switch {
+				case inT.Implements(idxType):
+					ins = append(ins, reflect.ValueOf(entity.NewFlatIndex("flat_index", entity.L2)))
+				case inT.Implements(rowType):
+					ins = append(ins, reflect.ValueOf(&ValidStruct{}))
+				}
+			default:
+				ins = append(ins, reflect.Zero(inT))
+			}
+		}
+		outs := v.MethodByName(m.Name).Call(ins)
+		assert.True(t, len(outs) > 0)
+		assert.EqualValues(t, ErrClientNotReady, outs[len(outs)-1].Interface())
+
+		// ctx done
+
+		if len(ins) > 0 { // with context param
+			ins[0] = reflect.ValueOf(ctxDone)
+			outs := v2.MethodByName(m.Name).Call(ins)
+			assert.True(t, len(outs) > 0)
+			assert.False(t, outs[len(outs)-1].IsNil())
+		}
+	}
+}
+
+func TestGrpcClientConnect(t *testing.T) {
+	ctx := context.Background()
+
+	t.Run("Use bufconn dailer, testing case", func(t *testing.T) {
+		c, err := NewGrpcClient(ctx, "bufnet", grpc.WithBlock(), grpc.WithInsecure(), grpc.WithContextDialer(bufDialer)) // uses in memory buf connection
+		assert.Nil(t, err)
+		assert.NotNil(t, c)
+	})
+
+	t.Run("Test empty addr, using default timeout", func(t *testing.T) {
+		c, err := NewGrpcClient(ctx, "")
+		assert.NotNil(t, err)
+		assert.Nil(t, c)
+	})
+}
+
+func TestGrpcClientClose(t *testing.T) {
+	ctx := context.Background()
+
+	t.Run("normal close", func(t *testing.T) {
+		c := testClient(ctx, t)
+		assert.Nil(t, c.Close())
+	})
+
+	t.Run("double close", func(t *testing.T) {
+		c := testClient(ctx, t)
+		assert.Nil(t, c.Close())
+		assert.Nil(t, c.Close())
+	})
+}
+
+func successStatus() (*common.Status, error) {
+	return &common.Status{ErrorCode: common.ErrorCode_Success}, nil
+}
+
+func badRequestStatus() (*common.Status, error) {
+	return &common.Status{ErrorCode: common.ErrorCode_IllegalArgument}, errors.New("illegal request type")
+}