Repository: milvus-io/milvus-sdk-go
Pull request #435 (2023-04-21 03:19:18)

Add AlterCollection API
See also #433
/kind improvement

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/435#" 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":[]} -->
codecov[bot]: ## [Codecov](https://codecov.io/gh/milvus-io/milvus-sdk-go/pull/435?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=milvus-io) Report
> Merging [#435](https://codecov.io/gh/milvus-io/milvus-sdk-go/pull/435?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=milvus-io) (fef5d95) into [master](https://codecov.io/gh/milvus-io/milvus-sdk-go/commit/3779153352938a584ec139c6c4c9b3dae90a6bde?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=milvus-io) (3779153) will **increase** coverage by `0.07%`.
> The diff coverage is `94.11%`.

[![Impacted file tree graph](https://codecov.io/gh/milvus-io/milvus-sdk-go/pull/435/graphs/tree.svg?width=650&height=150&src=pr&token=WJK43PV390&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=milvus-io)](https://codecov.io/gh/milvus-io/milvus-sdk-go/pull/435?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=milvus-io)

```diff
@@            Coverage Diff             @@
##           master     #435      +/-   ##
==========================================
+ Coverage   87.38%   87.46%   +0.07%     
==========================================
  Files          30       31       +1     
  Lines        2869     2903      +34     
==========================================
+ Hits         2507     2539      +32     
- Misses        273      274       +1     
- Partials       89       90       +1     
```


| [Impacted Files](https://codecov.io/gh/milvus-io/milvus-sdk-go/pull/435?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=milvus-io) | Coverage Δ | |
|---|---|---|
| [client/client.go](https://codecov.io/gh/milvus-io/milvus-sdk-go/pull/435?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=milvus-io#diff-Y2xpZW50L2NsaWVudC5nbw==) | `75.80% <ø> (ø)` | |
| [client/collection.go](https://codecov.io/gh/milvus-io/milvus-sdk-go/pull/435?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=milvus-io#diff-Y2xpZW50L2NvbGxlY3Rpb24uZ28=) | `88.31% <91.30%> (ø)` | |
| [entity/collection\_attr.go](https://codecov.io/gh/milvus-io/milvus-sdk-go/pull/435?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=milvus-io#diff-ZW50aXR5L2NvbGxlY3Rpb25fYXR0ci5nbw==) | `100.00% <100.00%> (ø)` | |

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/435#" title="Author self-approved">congqixia</a>*, *<a href="https://github.com/milvus-io/milvus-sdk-go/pull/435#pullrequestreview-1395501387" title="LGTM">yah01</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":[]} -->

Patch:
diff --git a/client/client.go b/client/client.go
index 54bdec7..72ebfda 100644
--- a/client/client.go
+++ b/client/client.go
@@ -56,6 +56,8 @@ type Client interface {
 	HasCollection(ctx context.Context, collName string) (bool, error)
 	// RenameCollection performs renaming for provided collection.
 	RenameCollection(ctx context.Context, collName, newName string) error
+	// AlterCollection changes collection attributes.
+	AlterCollection(ctx context.Context, collName string, attrs ...entity.CollectionAttribute) error
 
 	// CreateAlias creates an alias for collection
 	CreateAlias(ctx context.Context, collName string, alias string) error
diff --git a/client/client_grpc_collection.go b/client/collection.go
similarity index 93%
rename from client/client_grpc_collection.go
rename to client/collection.go
index 1e6e484..4d2b5a4 100644
--- a/client/client_grpc_collection.go
+++ b/client/collection.go
@@ -512,3 +512,43 @@ func (c *GrpcClient) GetLoadState(ctx context.Context, collName string, partitio
 
 	return entity.LoadState(resp.GetState()), nil
 }
+
+// AlterCollection changes the collection attribute.
+func (c *GrpcClient) AlterCollection(ctx context.Context, collName string, attrs ...entity.CollectionAttribute) error {
+	if c.Service == nil {
+		return ErrClientNotReady
+	}
+	if err := c.checkCollectionExists(ctx, collName); err != nil {
+		return err
+	}
+
+	if len(attrs) == 0 {
+		return errors.New("no collection attribute provided")
+	}
+
+	keys := make(map[string]struct{})
+
+	props := make([]*common.KeyValuePair, 0, len(attrs))
+	for _, attr := range attrs {
+		k, v := attr.KeyValue()
+		if _, exists := keys[k]; exists {
+			return errors.New("duplicated attributed received")
+		}
+		keys[k] = struct{}{}
+		props = append(props, &common.KeyValuePair{
+			Key:   k,
+			Value: v,
+		})
+	}
+
+	req := &server.AlterCollectionRequest{
+		CollectionName: collName,
+		Properties:     props,
+	}
+
+	resp, err := c.Service.AlterCollection(ctx, req)
+	if err != nil {
+		return err
+	}
+	return handleRespStatus(resp)
+}
diff --git a/entity/collection_attr.go b/entity/collection_attr.go
new file mode 100644
index 0000000..4c97b79
--- /dev/null
+++ b/entity/collection_attr.go
@@ -0,0 +1,66 @@
+// Copyright (C) 2019-2021 Zilliz. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software distributed under the License
+// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+// or implied. See the License for the specific language governing permissions and limitations under the License.
+
+package entity
+
+import (
+	"strconv"
+
+	"github.com/cockroachdb/errors"
+)
+
+const (
+	// cakTTL const for collection attribute key TTL in seconds.
+	cakTTL = `collection.ttl.seconds`
+)
+
+// CollectionAttribute is the interface for altering collection attributes.
+type CollectionAttribute interface {
+	KeyValue() (string, string)
+	Valid() error
+}
+
+type collAttrBase struct {
+	key   string
+	value string
+}
+
+// KeyValue implements CollectionAttribute.
+func (ca collAttrBase) KeyValue() (string, string) {
+	return ca.key, ca.value
+}
+
+type ttlCollAttr struct {
+	collAttrBase
+}
+
+// Valid implements CollectionAttribute.
+// checks ttl seconds is valid positive integer.
+func (ca collAttrBase) Valid() error {
+	val, err := strconv.ParseInt(ca.value, 10, 64)
+	if err != nil {
+		return errors.Wrap(err, "ttl is not a valid positive integer")
+	}
+
+	if val < 0 {
+		return errors.New("ttl needs to be a positive integer")
+	}
+
+	return nil
+}
+
+// CollectionTTL returns collection attribute to set collection ttl in seconds.
+func CollectionTTL(ttl int64) ttlCollAttr {
+	ca := ttlCollAttr{}
+	ca.key = cakTTL
+	ca.value = strconv.FormatInt(ttl, 10)
+	return ca
+}
diff --git a/go.mod b/go.mod
index d7a2769..c3b8d32 100644
--- a/go.mod
+++ b/go.mod
@@ -5,7 +5,7 @@ go 1.17
 require (
 	github.com/golang/protobuf v1.5.2
 	github.com/grpc-ecosystem/go-grpc-middleware v1.3.0
-	github.com/milvus-io/milvus-proto/go-api v0.0.0-20230303054144-16f081962572
+	github.com/milvus-io/milvus-proto/go-api v0.0.0-20230418025426-3735aafecc6c
 	github.com/stretchr/testify v1.8.1
 	google.golang.org/grpc v1.48.0
 	google.golang.org/grpc/examples v0.0.0-20220617181431-3e7b97febc7f
diff --git a/go.sum b/go.sum
index a69270c..0d4a902 100644
--- a/go.sum
+++ b/go.sum
@@ -158,6 +158,8 @@ github.com/milvus-io/milvus-proto/go-api v0.0.0-20230105121931-9f9303dcc729 h1:h
 github.com/milvus-io/milvus-proto/go-api v0.0.0-20230105121931-9f9303dcc729/go.mod h1:148qnlmZ0Fdm1Fq+Mj/OW2uDoEP25g3mjh0vMGtkgmk=
 github.com/milvus-io/milvus-proto/go-api v0.0.0-20230303054144-16f081962572 h1:QOimLfT1VwjH9jUkq2SN3uT/WGfgcD4pYBoFOGUMwjM=
 github.com/milvus-io/milvus-proto/go-api v0.0.0-20230303054144-16f081962572/go.mod h1:148qnlmZ0Fdm1Fq+Mj/OW2uDoEP25g3mjh0vMGtkgmk=
+github.com/milvus-io/milvus-proto/go-api v0.0.0-20230418025426-3735aafecc6c h1:9BaqfI2UoA2OJY89ogizV+b6vDlcwTguZT3rwUJl7yY=
+github.com/milvus-io/milvus-proto/go-api v0.0.0-20230418025426-3735aafecc6c/go.mod h1:148qnlmZ0Fdm1Fq+Mj/OW2uDoEP25g3mjh0vMGtkgmk=
 github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
 github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
 github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
diff --git a/mocks/MilvusServiceServer.go b/mocks/MilvusServiceServer.go
index c2f180b..b7c338a 100644
--- a/mocks/MilvusServiceServer.go
+++ b/mocks/MilvusServiceServer.go
@@ -7,6 +7,8 @@ import (
 
 	commonpb "github.com/milvus-io/milvus-proto/go-api/commonpb"
 
+	federpb "github.com/milvus-io/milvus-proto/go-api/federpb"
+
 	milvuspb "github.com/milvus-io/milvus-proto/go-api/milvuspb"
 
 	mock "github.com/stretchr/testify/mock"
@@ -636,6 +638,53 @@ func (_c *MilvusServiceServer_DeleteCredential_Call) Return(_a0 *commonpb.Status
 	return _c
 }
 
+// DescribeAlias provides a mock function with given fields: _a0, _a1
+func (_m *MilvusServiceServer) DescribeAlias(_a0 context.Context, _a1 *milvuspb.DescribeAliasRequest) (*milvuspb.DescribeAliasResponse, error) {
+	ret := _m.Called(_a0, _a1)
+
+	var r0 *milvuspb.DescribeAliasResponse
+	if rf, ok := ret.Get(0).(func(context.Context, *milvuspb.DescribeAliasRequest) *milvuspb.DescribeAliasResponse); ok {
+		r0 = rf(_a0, _a1)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*milvuspb.DescribeAliasResponse)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(context.Context, *milvuspb.DescribeAliasRequest) error); ok {
+		r1 = rf(_a0, _a1)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// MilvusServiceServer_DescribeAlias_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DescribeAlias'
+type MilvusServiceServer_DescribeAlias_Call struct {
+	*mock.Call
+}
+
+// DescribeAlias is a helper method to define mock.On call
+//  - _a0 context.Context
+//  - _a1 *milvuspb.DescribeAliasRequest
+func (_e *MilvusServiceServer_Expecter) DescribeAlias(_a0 interface{}, _a1 interface{}) *MilvusServiceServer_DescribeAlias_Call {
+	return &MilvusServiceServer_DescribeAlias_Call{Call: _e.mock.On("DescribeAlias", _a0, _a1)}
+}
+
+func (_c *MilvusServiceServer_DescribeAlias_Call) Run(run func(_a0 context.Context, _a1 *milvuspb.DescribeAliasRequest)) *MilvusServiceServer_DescribeAlias_Call {
+	_c.Call.Run(func(args mock.Arguments) {
+		run(args[0].(context.Context), args[1].(*milvuspb.DescribeAliasRequest))
+	})
+	return _c
+}
+
+func (_c *MilvusServiceServer_DescribeAlias_Call) Return(_a0 *milvuspb.DescribeAliasResponse, _a1 error) *MilvusServiceServer_DescribeAlias_Call {
+	_c.Call.Return(_a0, _a1)
+	return _c
+}
+
 // DescribeCollection provides a mock function with given fields: _a0, _a1
 func (_m *MilvusServiceServer) DescribeCollection(_a0 context.Context, _a1 *milvuspb.DescribeCollectionRequest) (*milvuspb.DescribeCollectionResponse, error) {
 	ret := _m.Called(_a0, _a1)
@@ -777,6 +826,53 @@ func (_c *MilvusServiceServer_DescribeResourceGroup_Call) Return(_a0 *milvuspb.D
 	return _c
 }
 
+// DescribeSegmentIndexData provides a mock function with given fields: _a0, _a1
+func (_m *MilvusServiceServer) DescribeSegmentIndexData(_a0 context.Context, _a1 *federpb.DescribeSegmentIndexDataRequest) (*federpb.DescribeSegmentIndexDataResponse, error) {
+	ret := _m.Called(_a0, _a1)
+
+	var r0 *federpb.DescribeSegmentIndexDataResponse
+	if rf, ok := ret.Get(0).(func(context.Context, *federpb.DescribeSegmentIndexDataRequest) *federpb.DescribeSegmentIndexDataResponse); ok {
+		r0 = rf(_a0, _a1)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*federpb.DescribeSegmentIndexDataResponse)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(context.Context, *federpb.DescribeSegmentIndexDataRequest) error); ok {
+		r1 = rf(_a0, _a1)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// MilvusServiceServer_DescribeSegmentIndexData_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DescribeSegmentIndexData'
+type MilvusServiceServer_DescribeSegmentIndexData_Call struct {
+	*mock.Call
+}
+
+// DescribeSegmentIndexData is a helper method to define mock.On call
+//  - _a0 context.Context
+//  - _a1 *federpb.DescribeSegmentIndexDataRequest
+func (_e *MilvusServiceServer_Expecter) DescribeSegmentIndexData(_a0 interface{}, _a1 interface{}) *MilvusServiceServer_DescribeSegmentIndexData_Call {
+	return &MilvusServiceServer_DescribeSegmentIndexData_Call{Call: _e.mock.On("DescribeSegmentIndexData", _a0, _a1)}
+}
+
+func (_c *MilvusServiceServer_DescribeSegmentIndexData_Call) Run(run func(_a0 context.Context, _a1 *federpb.DescribeSegmentIndexDataRequest)) *MilvusServiceServer_DescribeSegmentIndexData_Call {
+	_c.Call.Run(func(args mock.Arguments) {
+		run(args[0].(context.Context), args[1].(*federpb.DescribeSegmentIndexDataRequest))
+	})
+	return _c
+}
+
+func (_c *MilvusServiceServer_DescribeSegmentIndexData_Call) Return(_a0 *federpb.DescribeSegmentIndexDataResponse, _a1 error) *MilvusServiceServer_DescribeSegmentIndexData_Call {
+	_c.Call.Return(_a0, _a1)
+	return _c
+}
+
 // DropAlias provides a mock function with given fields: _a0, _a1
 func (_m *MilvusServiceServer) DropAlias(_a0 context.Context, _a1 *milvuspb.DropAliasRequest) (*commonpb.Status, error) {
 	ret := _m.Called(_a0, _a1)
@@ -1153,6 +1249,53 @@ func (_c *MilvusServiceServer_Flush_Call) Return(_a0 *milvuspb.FlushResponse, _a
 	return _c
 }
 
+// FlushAll provides a mock function with given fields: _a0, _a1
+func (_m *MilvusServiceServer) FlushAll(_a0 context.Context, _a1 *milvuspb.FlushAllRequest) (*milvuspb.FlushAllResponse, error) {
+	ret := _m.Called(_a0, _a1)
+
+	var r0 *milvuspb.FlushAllResponse
+	if rf, ok := ret.Get(0).(func(context.Context, *milvuspb.FlushAllRequest) *milvuspb.FlushAllResponse); ok {
+		r0 = rf(_a0, _a1)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*milvuspb.FlushAllResponse)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(context.Context, *milvuspb.FlushAllRequest) error); ok {
+		r1 = rf(_a0, _a1)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// MilvusServiceServer_FlushAll_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FlushAll'
+type MilvusServiceServer_FlushAll_Call struct {
+	*mock.Call
+}
+
+// FlushAll is a helper method to define mock.On call
+//  - _a0 context.Context
+//  - _a1 *milvuspb.FlushAllRequest
+func (_e *MilvusServiceServer_Expecter) FlushAll(_a0 interface{}, _a1 interface{}) *MilvusServiceServer_FlushAll_Call {
+	return &MilvusServiceServer_FlushAll_Call{Call: _e.mock.On("FlushAll", _a0, _a1)}
+}
+
+func (_c *MilvusServiceServer_FlushAll_Call) Run(run func(_a0 context.Context, _a1 *milvuspb.FlushAllRequest)) *MilvusServiceServer_FlushAll_Call {
+	_c.Call.Run(func(args mock.Arguments) {
+		run(args[0].(context.Context), args[1].(*milvuspb.FlushAllRequest))
+	})
+	return _c
+}
+
+func (_c *MilvusServiceServer_FlushAll_Call) Return(_a0 *milvuspb.FlushAllResponse, _a1 error) *MilvusServiceServer_FlushAll_Call {
+	_c.Call.Return(_a0, _a1)
+	return _c
+}
+
 // GetCollectionStatistics provides a mock function with given fields: _a0, _a1
 func (_m *MilvusServiceServer) GetCollectionStatistics(_a0 context.Context, _a1 *milvuspb.GetCollectionStatisticsRequest) (*milvuspb.GetCollectionStatisticsResponse, error) {
 	ret := _m.Called(_a0, _a1)
@@ -1341,6 +1484,53 @@ func (_c *MilvusServiceServer_GetComponentStates_Call) Return(_a0 *milvuspb.Comp
 	return _c
 }
 
+// GetFlushAllState provides a mock function with given fields: _a0, _a1
+func (_m *MilvusServiceServer) GetFlushAllState(_a0 context.Context, _a1 *milvuspb.GetFlushAllStateRequest) (*milvuspb.GetFlushAllStateResponse, error) {
+	ret := _m.Called(_a0, _a1)
+
+	var r0 *milvuspb.GetFlushAllStateResponse
+	if rf, ok := ret.Get(0).(func(context.Context, *milvuspb.GetFlushAllStateRequest) *milvuspb.GetFlushAllStateResponse); ok {
+		r0 = rf(_a0, _a1)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*milvuspb.GetFlushAllStateResponse)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(context.Context, *milvuspb.GetFlushAllStateRequest) error); ok {
+		r1 = rf(_a0, _a1)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// MilvusServiceServer_GetFlushAllState_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetFlushAllState'
+type MilvusServiceServer_GetFlushAllState_Call struct {
+	*mock.Call
+}
+
+// GetFlushAllState is a helper method to define mock.On call
+//  - _a0 context.Context
+//  - _a1 *milvuspb.GetFlushAllStateRequest
+func (_e *MilvusServiceServer_Expecter) GetFlushAllState(_a0 interface{}, _a1 interface{}) *MilvusServiceServer_GetFlushAllState_Call {
+	return &MilvusServiceServer_GetFlushAllState_Call{Call: _e.mock.On("GetFlushAllState", _a0, _a1)}
+}
+
+func (_c *MilvusServiceServer_GetFlushAllState_Call) Run(run func(_a0 context.Context, _a1 *milvuspb.GetFlushAllStateRequest)) *MilvusServiceServer_GetFlushAllState_Call {
+	_c.Call.Run(func(args mock.Arguments) {
+		run(args[0].(context.Context), args[1].(*milvuspb.GetFlushAllStateRequest))
+	})
+	return _c
+}
+
+func (_c *MilvusServiceServer_GetFlushAllState_Call) Return(_a0 *milvuspb.GetFlushAllStateResponse, _a1 error) *MilvusServiceServer_GetFlushAllState_Call {
+	_c.Call.Return(_a0, _a1)
+	return _c
+}
+
 // GetFlushState provides a mock function with given fields: _a0, _a1
 func (_m *MilvusServiceServer) GetFlushState(_a0 context.Context, _a1 *milvuspb.GetFlushStateRequest) (*milvuspb.GetFlushStateResponse, error) {
 	ret := _m.Called(_a0, _a1)
@@ -2093,6 +2283,53 @@ func (_c *MilvusServiceServer_Insert_Call) Return(_a0 *milvuspb.MutationResult,
 	return _c
 }
 
+// ListAliases provides a mock function with given fields: _a0, _a1
+func (_m *MilvusServiceServer) ListAliases(_a0 context.Context, _a1 *milvuspb.ListAliasesRequest) (*milvuspb.ListAliasesResponse, error) {
+	ret := _m.Called(_a0, _a1)
+
+	var r0 *milvuspb.ListAliasesResponse
+	if rf, ok := ret.Get(0).(func(context.Context, *milvuspb.ListAliasesRequest) *milvuspb.ListAliasesResponse); ok {
+		r0 = rf(_a0, _a1)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*milvuspb.ListAliasesResponse)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(context.Context, *milvuspb.ListAliasesRequest) error); ok {
+		r1 = rf(_a0, _a1)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// MilvusServiceServer_ListAliases_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListAliases'
+type MilvusServiceServer_ListAliases_Call struct {
+	*mock.Call
+}
+
+// ListAliases is a helper method to define mock.On call
+//  - _a0 context.Context
+//  - _a1 *milvuspb.ListAliasesRequest
+func (_e *MilvusServiceServer_Expecter) ListAliases(_a0 interface{}, _a1 interface{}) *MilvusServiceServer_ListAliases_Call {
+	return &MilvusServiceServer_ListAliases_Call{Call: _e.mock.On("ListAliases", _a0, _a1)}
+}
+
+func (_c *MilvusServiceServer_ListAliases_Call) Run(run func(_a0 context.Context, _a1 *milvuspb.ListAliasesRequest)) *MilvusServiceServer_ListAliases_Call {
+	_c.Call.Run(func(args mock.Arguments) {
+		run(args[0].(context.Context), args[1].(*milvuspb.ListAliasesRequest))
+	})
+	return _c
+}
+
+func (_c *MilvusServiceServer_ListAliases_Call) Return(_a0 *milvuspb.ListAliasesResponse, _a1 error) *MilvusServiceServer_ListAliases_Call {
+	_c.Call.Return(_a0, _a1)
+	return _c
+}
+
 // ListCredUsers provides a mock function with given fields: _a0, _a1
 func (_m *MilvusServiceServer) ListCredUsers(_a0 context.Context, _a1 *milvuspb.ListCredUsersRequest) (*milvuspb.ListCredUsersResponse, error) {
 	ret := _m.Called(_a0, _a1)
@@ -2187,6 +2424,53 @@ func (_c *MilvusServiceServer_ListImportTasks_Call) Return(_a0 *milvuspb.ListImp
 	return _c
 }
 
+// ListIndexedSegment provides a mock function with given fields: _a0, _a1
+func (_m *MilvusServiceServer) ListIndexedSegment(_a0 context.Context, _a1 *federpb.ListIndexedSegmentRequest) (*federpb.ListIndexedSegmentResponse, error) {
+	ret := _m.Called(_a0, _a1)
+
+	var r0 *federpb.ListIndexedSegmentResponse
+	if rf, ok := ret.Get(0).(func(context.Context, *federpb.ListIndexedSegmentRequest) *federpb.ListIndexedSegmentResponse); ok {
+		r0 = rf(_a0, _a1)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*federpb.ListIndexedSegmentResponse)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(context.Context, *federpb.ListIndexedSegmentRequest) error); ok {
+		r1 = rf(_a0, _a1)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// MilvusServiceServer_ListIndexedSegment_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListIndexedSegment'
+type MilvusServiceServer_ListIndexedSegment_Call struct {
+	*mock.Call
+}
+
+// ListIndexedSegment is a helper method to define mock.On call
+//  - _a0 context.Context
+//  - _a1 *federpb.ListIndexedSegmentRequest
+func (_e *MilvusServiceServer_Expecter) ListIndexedSegment(_a0 interface{}, _a1 interface{}) *MilvusServiceServer_ListIndexedSegment_Call {
+	return &MilvusServiceServer_ListIndexedSegment_Call{Call: _e.mock.On("ListIndexedSegment", _a0, _a1)}
+}
+
+func (_c *MilvusServiceServer_ListIndexedSegment_Call) Run(run func(_a0 context.Context, _a1 *federpb.ListIndexedSegmentRequest)) *MilvusServiceServer_ListIndexedSegment_Call {
+	_c.Call.Run(func(args mock.Arguments) {
+		run(args[0].(context.Context), args[1].(*federpb.ListIndexedSegmentRequest))
+	})
+	return _c
+}
+
+func (_c *MilvusServiceServer_ListIndexedSegment_Call) Return(_a0 *federpb.ListIndexedSegmentResponse, _a1 error) *MilvusServiceServer_ListIndexedSegment_Call {
+	_c.Call.Return(_a0, _a1)
+	return _c
+}
+
 // ListResourceGroups provides a mock function with given fields: _a0, _a1
 func (_m *MilvusServiceServer) ListResourceGroups(_a0 context.Context, _a1 *milvuspb.ListResourceGroupsRequest) (*milvuspb.ListResourceGroupsResponse, error) {
 	ret := _m.Called(_a0, _a1)


Test patch:
diff --git a/client/client_mock_test.go b/client/client_mock_test.go
index c0058e4..d1dd726 100644
--- a/client/client_mock_test.go
+++ b/client/client_mock_test.go
@@ -9,8 +9,10 @@ import (
 
 	"github.com/golang/protobuf/proto"
 	common "github.com/milvus-io/milvus-proto/go-api/commonpb"
+	"github.com/milvus-io/milvus-proto/go-api/federpb"
 	server "github.com/milvus-io/milvus-proto/go-api/milvuspb"
 	"github.com/milvus-io/milvus-sdk-go/v2/mocks"
+	"github.com/stretchr/testify/mock"
 	"github.com/stretchr/testify/suite"
 	"google.golang.org/grpc"
 	"google.golang.org/grpc/test/bufconn"
@@ -74,6 +76,17 @@ func (s *MockSuiteBase) resetMock() {
 	}
 }
 
+func (s *MockSuiteBase) setupHasCollection(collName string) {
+	s.mock.EXPECT().HasCollection(mock.Anything, mock.AnythingOfType("*milvuspb.HasCollectionRequest")).
+		Call.Return(func(ctx context.Context, req *server.HasCollectionRequest) *server.BoolResponse {
+		resp := &server.BoolResponse{Status: &common.Status{}}
+		if req.GetCollectionName() == collName {
+			resp.Value = true
+		}
+		return resp
+	}, nil)
+}
+
 // ref https://stackoverflow.com/questions/42102496/testing-a-grpc-service
 
 var (
@@ -700,6 +713,30 @@ func (m *MockServer) RenameCollection(_ context.Context, _ *server.RenameCollect
 	panic("not implemented") // TODO: Implement
 }
 
+func (m *MockServer) DescribeAlias(_ context.Context, _ *server.DescribeAliasRequest) (*server.DescribeAliasResponse, error) {
+	panic("not implemented") // TODO: Implement
+}
+
+func (m *MockServer) ListAliases(_ context.Context, _ *server.ListAliasesRequest) (*server.ListAliasesResponse, error) {
+	panic("not implemented") // TODO: Implement
+}
+
+func (m *MockServer) FlushAll(_ context.Context, _ *server.FlushAllRequest) (*server.FlushAllResponse, error) {
+	panic("not implemented") // TODO: Implement
+}
+
+func (m *MockServer) GetFlushAllState(_ context.Context, _ *server.GetFlushAllStateRequest) (*server.GetFlushAllStateResponse, error) {
+	panic("not implemented") // TODO: Implement
+}
+
+func (m *MockServer) ListIndexedSegment(_ context.Context, _ *federpb.ListIndexedSegmentRequest) (*federpb.ListIndexedSegmentResponse, error) {
+	panic("not implemented") // TODO: Implement
+}
+
+func (m *MockServer) DescribeSegmentIndexData(_ context.Context, _ *federpb.DescribeSegmentIndexDataRequest) (*federpb.DescribeSegmentIndexDataResponse, error) {
+	panic("not implemented") // TODO: Implement
+}
+
 //func (m *MockServer) DescribePartition(ctx context.Context, req *server.DescribePartitionRequest) (*server.DescribePartitionResponse, error) {
 //	panic("not implemented") // TODO: Implement
 //}
diff --git a/client/client_grpc_collection_test.go b/client/collection_test.go
similarity index 92%
rename from client/client_grpc_collection_test.go
rename to client/collection_test.go
index bb1c05b..60e23ca 100644
--- a/client/client_grpc_collection_test.go
+++ b/client/collection_test.go
@@ -374,6 +374,72 @@ func (s *CollectionSuite) TestRenameCollection() {
 	})
 }
 
+func (s *CollectionSuite) TestAlterCollection() {
+	c := s.client
+	ctx, cancel := context.WithCancel(context.Background())
+	defer cancel()
+
+	s.Run("normal_run", func() {
+		defer s.resetMock()
+
+		s.setupHasCollection(testCollectionName)
+		s.mock.EXPECT().AlterCollection(mock.Anything, mock.AnythingOfType("*milvuspb.AlterCollectionRequest")).
+			Return(&common.Status{}, nil)
+
+		err := c.AlterCollection(ctx, testCollectionName, entity.CollectionTTL(100000))
+		s.NoError(err)
+	})
+
+	s.Run("collection_not_exist", func() {
+		defer s.resetMock()
+
+		s.mock.EXPECT().HasCollection(mock.Anything, mock.AnythingOfType("*milvuspb.HasCollectionRequest")).
+			Return(&server.BoolResponse{
+				Status: &common.Status{},
+				Value:  false,
+			}, nil)
+
+		err := c.AlterCollection(ctx, testCollectionName, entity.CollectionTTL(100000))
+		s.Error(err)
+	})
+
+	s.Run("no_attributes", func() {
+		defer s.resetMock()
+
+		s.setupHasCollection(testCollectionName)
+		err := c.AlterCollection(ctx, testCollectionName)
+		s.Error(err)
+	})
+
+	s.Run("request_fails", func() {
+		defer s.resetMock()
+
+		s.setupHasCollection(testCollectionName)
+		s.mock.EXPECT().AlterCollection(mock.Anything, mock.AnythingOfType("*milvuspb.AlterCollectionRequest")).
+			Return(nil, errors.New("mocked"))
+
+		err := c.AlterCollection(ctx, testCollectionName, entity.CollectionTTL(100000))
+		s.Error(err)
+	})
+
+	s.Run("server_return_error", func() {
+		defer s.resetMock()
+
+		s.setupHasCollection(testCollectionName)
+		s.mock.EXPECT().AlterCollection(mock.Anything, mock.AnythingOfType("*milvuspb.AlterCollectionRequest")).
+			Return(&common.Status{ErrorCode: common.ErrorCode_UnexpectedError}, nil)
+
+		err := c.AlterCollection(ctx, testCollectionName, entity.CollectionTTL(100000))
+		s.Error(err)
+	})
+
+	s.Run("service_not_ready", func() {
+		c := &GrpcClient{}
+		err := c.AlterCollection(ctx, testCollectionName, entity.CollectionTTL(100000))
+		s.ErrorIs(err, ErrClientNotReady)
+	})
+}
+
 func TestCollectionSuite(t *testing.T) {
 	suite.Run(t, new(CollectionSuite))
 }
diff --git a/entity/collection_attr_test.go b/entity/collection_attr_test.go
new file mode 100644
index 0000000..fb570bd
--- /dev/null
+++ b/entity/collection_attr_test.go
@@ -0,0 +1,83 @@
+// Copyright (C) 2019-2021 Zilliz. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software distributed under the License
+// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+// or implied. See the License for the specific language governing permissions and limitations under the License.
+
+package entity
+
+import (
+	"fmt"
+	"strconv"
+	"testing"
+
+	"github.com/stretchr/testify/suite"
+)
+
+type CollectionTTLSuite struct {
+	suite.Suite
+}
+
+func (s *CollectionTTLSuite) TestValid() {
+	type testCase struct {
+		input     string
+		expectErr bool
+	}
+
+	cases := []testCase{
+		{input: "a", expectErr: true},
+		{input: "1000", expectErr: false},
+		{input: "0", expectErr: false},
+		{input: "-10", expectErr: true},
+	}
+
+	for _, tc := range cases {
+		s.Run(tc.input, func() {
+			ca := ttlCollAttr{}
+			ca.value = tc.input
+			err := ca.Valid()
+			if tc.expectErr {
+				s.Error(err)
+			} else {
+				s.NoError(err)
+			}
+		})
+	}
+}
+
+func (s *CollectionTTLSuite) TestCollectionTTL() {
+	type testCase struct {
+		input     int64
+		expectErr bool
+	}
+
+	cases := []testCase{
+		{input: 1000, expectErr: false},
+		{input: 0, expectErr: false},
+		{input: -10, expectErr: true},
+	}
+
+	for _, tc := range cases {
+		s.Run(fmt.Sprintf("%d", tc.input), func() {
+			ca := CollectionTTL(tc.input)
+			key, value := ca.KeyValue()
+			s.Equal(cakTTL, key)
+			s.Equal(strconv.FormatInt(tc.input, 10), value)
+			err := ca.Valid()
+			if tc.expectErr {
+				s.Error(err)
+			} else {
+				s.NoError(err)
+			}
+		})
+	}
+}
+
+func TestCollectionTTL(t *testing.T) {
+	suite.Run(t, new(CollectionTTLSuite))
+}