github.com/milvus-io/milvus-sdk-go/v2@v2.4.1/client/collection.go (about)

     1  // Copyright (C) 2019-2021 Zilliz. All rights reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance
     4  // with the License. You may obtain a copy of the License at
     5  //
     6  // http://www.apache.org/licenses/LICENSE-2.0
     7  //
     8  // Unless required by applicable law or agreed to in writing, software distributed under the License
     9  // is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
    10  // or implied. See the License for the specific language governing permissions and limitations under the License.
    11  
    12  package client
    13  
    14  import (
    15  	"context"
    16  	"time"
    17  
    18  	"github.com/cockroachdb/errors"
    19  
    20  	"github.com/golang/protobuf/proto"
    21  
    22  	"github.com/milvus-io/milvus-sdk-go/v2/entity"
    23  
    24  	"github.com/milvus-io/milvus-proto/go-api/v2/commonpb"
    25  	"github.com/milvus-io/milvus-proto/go-api/v2/milvuspb"
    26  )
    27  
    28  // handles response status
    29  // if status is nil returns ErrStatusNil
    30  // if status.ErrorCode is commonpb.ErrorCode_Success, returns nil
    31  // otherwise, try use Reason into ErrServiceFailed
    32  // if Reason is empty, returns ErrServiceFailed with default string
    33  func handleRespStatus(status *commonpb.Status) error {
    34  	if status == nil {
    35  		return ErrStatusNil
    36  	}
    37  	if status.ErrorCode != commonpb.ErrorCode_Success {
    38  		if status.GetReason() != "" {
    39  			return ErrServiceFailed(errors.New(status.GetReason()))
    40  		}
    41  		return ErrServiceFailed(errors.New("Service failed"))
    42  	}
    43  	return nil
    44  }
    45  
    46  // ListCollections list collections from connection
    47  // Note that schema info are not provided in collection list
    48  func (c *GrpcClient) ListCollections(ctx context.Context, opts ...ListCollectionOption) ([]*entity.Collection, error) {
    49  	if c.Service == nil {
    50  		return []*entity.Collection{}, ErrClientNotReady
    51  	}
    52  
    53  	o := &listCollectionOpt{}
    54  	for _, opt := range opts {
    55  		opt(o)
    56  	}
    57  
    58  	req := &milvuspb.ShowCollectionsRequest{
    59  		DbName:    "",
    60  		TimeStamp: 0, // means now
    61  	}
    62  
    63  	if o.showInMemory {
    64  		req.Type = milvuspb.ShowType_InMemory
    65  	}
    66  
    67  	resp, err := c.Service.ShowCollections(ctx, req)
    68  	if err != nil {
    69  		return []*entity.Collection{}, err
    70  	}
    71  	err = handleRespStatus(resp.GetStatus())
    72  	if err != nil {
    73  		return []*entity.Collection{}, err
    74  	}
    75  	collections := make([]*entity.Collection, 0, len(resp.GetCollectionIds()))
    76  	for idx, item := range resp.CollectionIds {
    77  		collection := &entity.Collection{
    78  			ID:   item,
    79  			Name: resp.GetCollectionNames()[idx],
    80  		}
    81  		if len(resp.GetInMemoryPercentages()) > idx {
    82  			collection.Loaded = resp.GetInMemoryPercentages()[idx] == 100
    83  		}
    84  		collections = append(collections, collection)
    85  	}
    86  	return collections, nil
    87  }
    88  
    89  // NewCollection creates a common simple collection with pre-defined attributes.
    90  func (c *GrpcClient) NewCollection(ctx context.Context, collName string, dimension int64, opts ...CreateCollectionOption) error {
    91  	if c.Service == nil {
    92  		return ErrClientNotReady
    93  	}
    94  
    95  	//	shardNum := entity.DefaultShardNumber
    96  	opt := &createCollOpt{
    97  		ConsistencyLevel:    entity.DefaultConsistencyLevel,
    98  		PrimaryKeyFieldName: "id",
    99  		PrimaryKeyFieldType: entity.FieldTypeInt64,
   100  		VectorFieldName:     "vector",
   101  		MetricsType:         entity.IP,
   102  		AutoID:              false,
   103  		EnableDynamicSchema: true,
   104  	}
   105  
   106  	for _, o := range opts {
   107  		o(opt)
   108  	}
   109  
   110  	pkField := entity.NewField().WithName(opt.PrimaryKeyFieldName).WithDataType(opt.PrimaryKeyFieldType).WithIsAutoID(opt.AutoID).WithIsPrimaryKey(true)
   111  	if opt.PrimaryKeyFieldType == entity.FieldTypeVarChar && opt.PrimaryKeyMaxLength > 0 {
   112  		pkField = pkField.WithMaxLength(opt.PrimaryKeyMaxLength)
   113  	}
   114  
   115  	sch := entity.NewSchema().WithName(collName).WithAutoID(opt.AutoID).WithDynamicFieldEnabled(opt.EnableDynamicSchema).
   116  		WithField(pkField).
   117  		WithField(entity.NewField().WithName(opt.VectorFieldName).WithDataType(entity.FieldTypeFloatVector).WithDim(dimension))
   118  
   119  	if err := c.validateSchema(sch); err != nil {
   120  		return err
   121  	}
   122  
   123  	if err := c.requestCreateCollection(ctx, sch, opt, entity.DefaultShardNumber); err != nil {
   124  		return err
   125  	}
   126  
   127  	idx := entity.NewGenericIndex("", "", map[string]string{
   128  		"metric_type": string(opt.MetricsType),
   129  	})
   130  
   131  	if err := c.CreateIndex(ctx, collName, opt.VectorFieldName, idx, false); err != nil {
   132  		return err
   133  	}
   134  
   135  	return c.LoadCollection(ctx, collName, false)
   136  }
   137  
   138  // CreateCollection create collection with specified schema
   139  func (c *GrpcClient) CreateCollection(ctx context.Context, collSchema *entity.Schema, shardNum int32, opts ...CreateCollectionOption) error {
   140  	if c.Service == nil {
   141  		return ErrClientNotReady
   142  	}
   143  	if err := c.validateSchema(collSchema); err != nil {
   144  		return err
   145  	}
   146  
   147  	opt := &createCollOpt{
   148  		ConsistencyLevel: entity.DefaultConsistencyLevel,
   149  		NumPartitions:    0,
   150  	}
   151  	// apply options on request
   152  	for _, o := range opts {
   153  		o(opt)
   154  	}
   155  
   156  	return c.requestCreateCollection(ctx, collSchema, opt, shardNum)
   157  }
   158  
   159  func (c *GrpcClient) requestCreateCollection(ctx context.Context, sch *entity.Schema, opt *createCollOpt, shardNum int32) error {
   160  	if opt.EnableDynamicSchema {
   161  		sch.EnableDynamicField = true
   162  	}
   163  	bs, err := proto.Marshal(sch.ProtoMessage())
   164  	if err != nil {
   165  		return err
   166  	}
   167  
   168  	req := &milvuspb.CreateCollectionRequest{
   169  		Base:             opt.MsgBase,
   170  		DbName:           "", // reserved fields, not used for now
   171  		CollectionName:   sch.CollectionName,
   172  		Schema:           bs,
   173  		ShardsNum:        shardNum,
   174  		ConsistencyLevel: opt.ConsistencyLevel.CommonConsistencyLevel(),
   175  		NumPartitions:    opt.NumPartitions,
   176  		Properties:       entity.MapKvPairs(opt.Properties),
   177  	}
   178  
   179  	resp, err := c.Service.CreateCollection(ctx, req)
   180  	if err != nil {
   181  		return err
   182  	}
   183  	err = handleRespStatus(resp)
   184  	if err != nil {
   185  		return err
   186  	}
   187  	return nil
   188  }
   189  
   190  func (c *GrpcClient) validateSchema(sch *entity.Schema) error {
   191  	if sch == nil {
   192  		return errors.New("nil schema")
   193  	}
   194  	if sch.CollectionName == "" {
   195  		return errors.New("collection name cannot be empty")
   196  	}
   197  
   198  	primaryKey := false
   199  	autoID := false
   200  	vectors := 0
   201  	hasPartitionKey := false
   202  	hasDynamicSchema := sch.EnableDynamicField
   203  	hasJSON := false
   204  	for _, field := range sch.Fields {
   205  		if field.PrimaryKey {
   206  			if primaryKey { // another primary key found, only one primary key field for now
   207  				return errors.New("only one primary key only")
   208  			}
   209  			if field.DataType != entity.FieldTypeInt64 && field.DataType != entity.FieldTypeVarChar { // string key not supported yet
   210  				return errors.New("only int64 and varchar column can be primary key for now")
   211  			}
   212  			primaryKey = true
   213  		}
   214  		if field.AutoID {
   215  			if autoID {
   216  				return errors.New("only one auto id is available")
   217  			}
   218  			autoID = true
   219  		}
   220  		if field.DataType == entity.FieldTypeJSON {
   221  			hasJSON = true
   222  		}
   223  		if field.IsDynamic {
   224  			hasDynamicSchema = true
   225  		}
   226  		if field.IsPartitionKey {
   227  			hasPartitionKey = true
   228  		}
   229  		if field.DataType == entity.FieldTypeFloatVector ||
   230  			field.DataType == entity.FieldTypeBinaryVector ||
   231  			field.DataType == entity.FieldTypeBFloat16Vector ||
   232  			field.DataType == entity.FieldTypeFloat16Vector ||
   233  			field.DataType == entity.FieldTypeSparseVector {
   234  			vectors++
   235  		}
   236  	}
   237  	if vectors <= 0 {
   238  		return errors.New("vector field not set")
   239  	}
   240  	switch {
   241  	case hasJSON && c.config.hasFlags(disableJSON):
   242  		return ErrFeatureNotSupported
   243  	case hasDynamicSchema && c.config.hasFlags(disableDynamicSchema):
   244  		return ErrFeatureNotSupported
   245  	case hasPartitionKey && c.config.hasFlags(disableParitionKey):
   246  		return ErrFeatureNotSupported
   247  	}
   248  	return nil
   249  }
   250  
   251  func (c *GrpcClient) checkCollectionExists(ctx context.Context, collName string) error {
   252  	has, err := c.HasCollection(ctx, collName)
   253  	if err != nil {
   254  		return err
   255  	}
   256  	if !has {
   257  		return collNotExistsErr(collName)
   258  	}
   259  	return nil
   260  }
   261  
   262  // DescribeCollection describe the collection by name
   263  func (c *GrpcClient) DescribeCollection(ctx context.Context, collName string) (*entity.Collection, error) {
   264  	if c.Service == nil {
   265  		return nil, ErrClientNotReady
   266  	}
   267  	req := &milvuspb.DescribeCollectionRequest{
   268  		CollectionName: collName,
   269  	}
   270  	resp, err := c.Service.DescribeCollection(ctx, req)
   271  	if err != nil {
   272  		return nil, err
   273  	}
   274  	err = handleRespStatus(resp.GetStatus())
   275  	if err != nil {
   276  		return nil, err
   277  	}
   278  	collection := &entity.Collection{
   279  		ID:               resp.GetCollectionID(),
   280  		Name:             collName,
   281  		Schema:           entity.NewSchema().ReadProto(resp.GetSchema()),
   282  		PhysicalChannels: resp.GetPhysicalChannelNames(),
   283  		VirtualChannels:  resp.GetVirtualChannelNames(),
   284  		ConsistencyLevel: entity.ConsistencyLevel(resp.ConsistencyLevel),
   285  		ShardNum:         resp.GetShardsNum(),
   286  		Properties:       entity.KvPairsMap(resp.GetProperties()),
   287  	}
   288  	collection.Name = collection.Schema.CollectionName
   289  	colInfo := collInfo{
   290  		ID:               collection.ID,
   291  		Name:             collection.Name,
   292  		Schema:           collection.Schema,
   293  		ConsistencyLevel: collection.ConsistencyLevel,
   294  	}
   295  	MetaCache.setCollectionInfo(collName, &colInfo)
   296  	return collection, nil
   297  }
   298  
   299  // DropCollection drop collection by name
   300  func (c *GrpcClient) DropCollection(ctx context.Context, collName string, opts ...DropCollectionOption) error {
   301  	if c.Service == nil {
   302  		return ErrClientNotReady
   303  	}
   304  	if err := c.checkCollectionExists(ctx, collName); err != nil {
   305  		return err
   306  	}
   307  
   308  	req := &milvuspb.DropCollectionRequest{
   309  		CollectionName: collName,
   310  	}
   311  	for _, opt := range opts {
   312  		opt(req)
   313  	}
   314  	resp, err := c.Service.DropCollection(ctx, req)
   315  	if err != nil {
   316  		return err
   317  	}
   318  	err = handleRespStatus(resp)
   319  	if err == nil {
   320  		MetaCache.setCollectionInfo(collName, nil)
   321  	}
   322  	return err
   323  }
   324  
   325  // HasCollection check whether collection name exists
   326  func (c *GrpcClient) HasCollection(ctx context.Context, collName string) (bool, error) {
   327  	if c.Service == nil {
   328  		return false, ErrClientNotReady
   329  	}
   330  
   331  	req := &milvuspb.HasCollectionRequest{
   332  		DbName:         "", // reserved
   333  		CollectionName: collName,
   334  		TimeStamp:      0, // 0 for now
   335  	}
   336  
   337  	resp, err := c.Service.HasCollection(ctx, req)
   338  	if err != nil {
   339  		return false, err
   340  	}
   341  	if err := handleRespStatus(resp.GetStatus()); err != nil {
   342  		return false, err
   343  	}
   344  	return resp.GetValue(), nil
   345  }
   346  
   347  // GetCollectionStatistcis show collection statistics
   348  func (c *GrpcClient) GetCollectionStatistics(ctx context.Context, collName string) (map[string]string, error) {
   349  	if c.Service == nil {
   350  		return nil, ErrClientNotReady
   351  	}
   352  
   353  	if err := c.checkCollectionExists(ctx, collName); err != nil {
   354  		return nil, err
   355  	}
   356  
   357  	req := &milvuspb.GetCollectionStatisticsRequest{
   358  		CollectionName: collName,
   359  	}
   360  	resp, err := c.Service.GetCollectionStatistics(ctx, req)
   361  	if err != nil {
   362  		return nil, err
   363  	}
   364  	if err := handleRespStatus(resp.GetStatus()); err != nil {
   365  		return nil, err
   366  	}
   367  	return entity.KvPairsMap(resp.GetStats()), nil
   368  }
   369  
   370  // ShowCollection show collection status, used to check whether it is loaded or not
   371  func (c *GrpcClient) ShowCollection(ctx context.Context, collName string) (*entity.Collection, error) {
   372  	if c.Service == nil {
   373  		return nil, ErrClientNotReady
   374  	}
   375  	if err := c.checkCollectionExists(ctx, collName); err != nil {
   376  		return nil, err
   377  	}
   378  
   379  	req := &milvuspb.ShowCollectionsRequest{
   380  		Type:            milvuspb.ShowType_InMemory,
   381  		CollectionNames: []string{collName},
   382  	}
   383  
   384  	resp, err := c.Service.ShowCollections(ctx, req)
   385  	if err != nil {
   386  		return nil, err
   387  	}
   388  	if err := handleRespStatus(resp.GetStatus()); err != nil {
   389  		return nil, err
   390  	}
   391  
   392  	if len(resp.CollectionIds) != 1 || len(resp.InMemoryPercentages) != 1 {
   393  		return nil, errors.New("response len not valid")
   394  	}
   395  	return &entity.Collection{
   396  		ID:     resp.CollectionIds[0],
   397  		Loaded: resp.InMemoryPercentages[0] == 100, // TODO silverxia, the percentage can be either 0 or 100
   398  	}, nil
   399  }
   400  
   401  // RenameCollection performs renaming for provided collection.
   402  func (c *GrpcClient) RenameCollection(ctx context.Context, collName, newName string) error {
   403  	if c.Service == nil {
   404  		return ErrClientNotReady
   405  	}
   406  
   407  	if err := c.checkCollectionExists(ctx, collName); err != nil {
   408  		return err
   409  	}
   410  
   411  	req := &milvuspb.RenameCollectionRequest{
   412  		OldName: collName,
   413  		NewName: newName,
   414  	}
   415  	resp, err := c.Service.RenameCollection(ctx, req)
   416  	if err != nil {
   417  		return err
   418  	}
   419  	return handleRespStatus(resp)
   420  }
   421  
   422  // LoadCollection load collection into memory
   423  func (c *GrpcClient) LoadCollection(ctx context.Context, collName string, async bool, opts ...LoadCollectionOption) error {
   424  	if c.Service == nil {
   425  		return ErrClientNotReady
   426  	}
   427  
   428  	if err := c.checkCollectionExists(ctx, collName); err != nil {
   429  		return err
   430  	}
   431  
   432  	req := &milvuspb.LoadCollectionRequest{
   433  		CollectionName: collName,
   434  		ReplicaNumber:  1, // default replica number
   435  	}
   436  
   437  	for _, opt := range opts {
   438  		opt(req)
   439  	}
   440  
   441  	resp, err := c.Service.LoadCollection(ctx, req)
   442  	if err != nil {
   443  		return err
   444  	}
   445  	if err := handleRespStatus(resp); err != nil {
   446  		return err
   447  	}
   448  
   449  	if !async {
   450  		ticker := time.NewTicker(200 * time.Millisecond)
   451  		defer ticker.Stop()
   452  		for {
   453  			select {
   454  			case <-ctx.Done():
   455  				return ctx.Err()
   456  			case <-ticker.C:
   457  				progress, err := c.getLoadingProgress(ctx, collName)
   458  				if err != nil {
   459  					return err
   460  				}
   461  				if progress == 100 {
   462  					return nil
   463  				}
   464  			}
   465  		}
   466  	}
   467  	return nil
   468  }
   469  
   470  // ReleaseCollection release loaded collection
   471  func (c *GrpcClient) ReleaseCollection(ctx context.Context, collName string, opts ...ReleaseCollectionOption) error {
   472  	if c.Service == nil {
   473  		return ErrClientNotReady
   474  	}
   475  	if err := c.checkCollectionExists(ctx, collName); err != nil {
   476  		return err
   477  	}
   478  
   479  	req := &milvuspb.ReleaseCollectionRequest{
   480  		DbName:         "", // reserved
   481  		CollectionName: collName,
   482  	}
   483  	for _, opt := range opts {
   484  		opt(req)
   485  	}
   486  	resp, err := c.Service.ReleaseCollection(ctx, req)
   487  	if err != nil {
   488  		return err
   489  	}
   490  	return handleRespStatus(resp)
   491  }
   492  
   493  // GetReplicas gets the replica groups as well as their querynodes and shards information
   494  func (c *GrpcClient) GetReplicas(ctx context.Context, collName string) ([]*entity.ReplicaGroup, error) {
   495  	if c.Service == nil {
   496  		return nil, ErrClientNotReady
   497  	}
   498  	coll, err := c.ShowCollection(ctx, collName)
   499  	if err != nil {
   500  		return nil, err
   501  	}
   502  
   503  	req := &milvuspb.GetReplicasRequest{
   504  		CollectionID:   coll.ID,
   505  		WithShardNodes: true, // return nodes by default
   506  	}
   507  
   508  	resp, err := c.Service.GetReplicas(ctx, req)
   509  	if err != nil {
   510  		return nil, err
   511  	}
   512  	if err = handleRespStatus(resp.GetStatus()); err != nil {
   513  		return nil, err
   514  	}
   515  
   516  	groups := make([]*entity.ReplicaGroup, 0, len(resp.GetReplicas()))
   517  	for _, rp := range resp.GetReplicas() {
   518  		group := &entity.ReplicaGroup{
   519  			ReplicaID:     rp.ReplicaID,
   520  			NodeIDs:       rp.NodeIds,
   521  			ShardReplicas: make([]*entity.ShardReplica, 0, len(rp.ShardReplicas)),
   522  		}
   523  		for _, s := range rp.ShardReplicas {
   524  			shard := &entity.ShardReplica{
   525  				LeaderID:      s.LeaderID,
   526  				NodesIDs:      s.NodeIds,
   527  				DmChannelName: s.DmChannelName,
   528  			}
   529  			group.ShardReplicas = append(group.ShardReplicas, shard)
   530  		}
   531  		groups = append(groups, group)
   532  	}
   533  	return groups, nil
   534  }
   535  
   536  // GetLoadingProgress get the collection or partitions loading progress
   537  func (c *GrpcClient) GetLoadingProgress(ctx context.Context, collName string, partitionNames []string) (int64, error) {
   538  	if c.Service == nil {
   539  		return 0, ErrClientNotReady
   540  	}
   541  	if err := c.checkCollectionExists(ctx, collName); err != nil {
   542  		return 0, err
   543  	}
   544  
   545  	req := &milvuspb.GetLoadingProgressRequest{
   546  		CollectionName: collName,
   547  		PartitionNames: partitionNames,
   548  	}
   549  	resp, err := c.Service.GetLoadingProgress(ctx, req)
   550  	if err != nil {
   551  		return 0, err
   552  	}
   553  
   554  	return resp.GetProgress(), nil
   555  }
   556  
   557  // GetLoadState get the collection or partitions load state
   558  func (c *GrpcClient) GetLoadState(ctx context.Context, collName string, partitionNames []string) (entity.LoadState, error) {
   559  	if c.Service == nil {
   560  		return 0, ErrClientNotReady
   561  	}
   562  	if err := c.checkCollectionExists(ctx, collName); err != nil {
   563  		return 0, err
   564  	}
   565  
   566  	req := &milvuspb.GetLoadStateRequest{
   567  		CollectionName: collName,
   568  		PartitionNames: partitionNames,
   569  	}
   570  	resp, err := c.Service.GetLoadState(ctx, req)
   571  	if err != nil {
   572  		return 0, err
   573  	}
   574  
   575  	return entity.LoadState(resp.GetState()), nil
   576  }
   577  
   578  // AlterCollection changes the collection attribute.
   579  func (c *GrpcClient) AlterCollection(ctx context.Context, collName string, attrs ...entity.CollectionAttribute) error {
   580  	if c.Service == nil {
   581  		return ErrClientNotReady
   582  	}
   583  	if err := c.checkCollectionExists(ctx, collName); err != nil {
   584  		return err
   585  	}
   586  
   587  	if len(attrs) == 0 {
   588  		return errors.New("no collection attribute provided")
   589  	}
   590  
   591  	keys := make(map[string]struct{})
   592  
   593  	props := make([]*commonpb.KeyValuePair, 0, len(attrs))
   594  	for _, attr := range attrs {
   595  		k, v := attr.KeyValue()
   596  		if _, exists := keys[k]; exists {
   597  			return errors.New("duplicated attributed received")
   598  		}
   599  		keys[k] = struct{}{}
   600  		props = append(props, &commonpb.KeyValuePair{
   601  			Key:   k,
   602  			Value: v,
   603  		})
   604  	}
   605  
   606  	req := &milvuspb.AlterCollectionRequest{
   607  		CollectionName: collName,
   608  		Properties:     props,
   609  	}
   610  
   611  	resp, err := c.Service.AlterCollection(ctx, req)
   612  	if err != nil {
   613  		return err
   614  	}
   615  	return handleRespStatus(resp)
   616  }
   617  
   618  func (c *GrpcClient) getLoadingProgress(ctx context.Context, collectionName string, partitionNames ...string) (int64, error) {
   619  	req := &milvuspb.GetLoadingProgressRequest{
   620  		Base:           &commonpb.MsgBase{},
   621  		DbName:         "",
   622  		CollectionName: collectionName,
   623  		PartitionNames: partitionNames,
   624  	}
   625  
   626  	resp, err := c.Service.GetLoadingProgress(ctx, req)
   627  	if err != nil {
   628  		return -1, err
   629  	}
   630  	if err := handleRespStatus(resp.GetStatus()); err != nil {
   631  		return -1, err
   632  	}
   633  	return resp.GetProgress(), nil
   634  }