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 }