github.com/milvus-io/milvus-sdk-go/v2@v2.4.1/client/data.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 "encoding/json" 17 "fmt" 18 "log" 19 "strconv" 20 "strings" 21 22 "github.com/cockroachdb/errors" 23 24 "github.com/milvus-io/milvus-proto/go-api/v2/commonpb" 25 "github.com/milvus-io/milvus-proto/go-api/v2/milvuspb" 26 "github.com/milvus-io/milvus-proto/go-api/v2/schemapb" 27 "github.com/milvus-io/milvus-sdk-go/v2/entity" 28 "github.com/milvus-io/milvus-sdk-go/v2/merr" 29 ) 30 31 const ( 32 offsetKey = `offset` 33 limitKey = `limit` 34 ignoreGrowingKey = `ignore_growing` 35 forTuningKey = `for_tuning` 36 groupByKey = `group_by_field` 37 iteratorKey = `iterator` 38 reduceForBestKey = `reduce_stop_for_best` 39 ) 40 41 func (c *GrpcClient) HybridSearch(ctx context.Context, collName string, partitions []string, limit int, outputFields []string, reranker Reranker, subRequests []*ANNSearchRequest, opts ...SearchQueryOptionFunc) ([]SearchResult, error) { 42 if c.Service == nil { 43 return nil, ErrClientNotReady 44 } 45 46 var schema *entity.Schema 47 collInfo, ok := MetaCache.getCollectionInfo(collName) 48 if !ok { 49 coll, err := c.DescribeCollection(ctx, collName) 50 if err != nil { 51 return nil, err 52 } 53 schema = coll.Schema 54 collInfo, _ = MetaCache.getCollectionInfo(collName) 55 } else { 56 schema = collInfo.Schema 57 } 58 59 sReqs := make([]*milvuspb.SearchRequest, 0, len(subRequests)) 60 nq := 0 61 for _, subRequest := range subRequests { 62 r, err := subRequest.getMilvusSearchRequest(collInfo, opts...) 63 if err != nil { 64 return nil, err 65 } 66 r.CollectionName = collName 67 r.PartitionNames = partitions 68 r.OutputFields = outputFields 69 nq = len(subRequest.vectors) 70 sReqs = append(sReqs, r) 71 } 72 73 opt := &SearchQueryOption{} 74 for _, o := range opts { 75 o(opt) 76 } 77 params := reranker.GetParams() 78 params = append(params, &commonpb.KeyValuePair{Key: limitKey, Value: strconv.FormatInt(int64(limit), 10)}) 79 params = append(params, &commonpb.KeyValuePair{Key: offsetKey, Value: strconv.FormatInt(int64(opt.Offset), 10)}) 80 81 req := &milvuspb.HybridSearchRequest{ 82 CollectionName: collName, 83 PartitionNames: partitions, 84 Requests: sReqs, 85 OutputFields: outputFields, 86 ConsistencyLevel: commonpb.ConsistencyLevel(collInfo.ConsistencyLevel), 87 RankParams: params, 88 } 89 90 result, err := c.Service.HybridSearch(ctx, req) 91 92 err = merr.CheckRPCCall(result, err) 93 if err != nil { 94 return nil, err 95 } 96 97 return c.handleSearchResult(schema, outputFields, nq, result) 98 } 99 100 // Search with bool expression 101 func (c *GrpcClient) Search(ctx context.Context, collName string, partitions []string, 102 expr string, outputFields []string, vectors []entity.Vector, vectorField string, metricType entity.MetricType, topK int, sp entity.SearchParam, opts ...SearchQueryOptionFunc) ([]SearchResult, error) { 103 if c.Service == nil { 104 return []SearchResult{}, ErrClientNotReady 105 } 106 var schema *entity.Schema 107 collInfo, ok := MetaCache.getCollectionInfo(collName) 108 if !ok { 109 coll, err := c.DescribeCollection(ctx, collName) 110 if err != nil { 111 return nil, err 112 } 113 schema = coll.Schema 114 } else { 115 schema = collInfo.Schema 116 } 117 118 option, err := makeSearchQueryOption(collName, opts...) 119 if err != nil { 120 return nil, err 121 } 122 // 2. Request milvus Service 123 req, err := prepareSearchRequest(collName, partitions, expr, outputFields, vectors, vectorField, metricType, topK, sp, option) 124 if err != nil { 125 return nil, err 126 } 127 128 resp, err := c.Service.Search(ctx, req) 129 if err != nil { 130 return nil, err 131 } 132 if err := handleRespStatus(resp.GetStatus()); err != nil { 133 return nil, err 134 } 135 // 3. parse result into result 136 return c.handleSearchResult(schema, outputFields, len(vectors), resp) 137 } 138 139 func (c *GrpcClient) handleSearchResult(schema *entity.Schema, outputFields []string, nq int, resp *milvuspb.SearchResults) ([]SearchResult, error) { 140 sr := make([]SearchResult, 0, nq) 141 // parse result into result 142 results := resp.GetResults() 143 offset := 0 144 fieldDataList := results.GetFieldsData() 145 gb := results.GetGroupByFieldValue() 146 147 for i := 0; i < int(results.GetNumQueries()); i++ { 148 rc := int(results.GetTopks()[i]) // result entry count for current query 149 entry := SearchResult{ 150 ResultCount: rc, 151 Scores: results.GetScores()[offset : offset+rc], 152 } 153 154 entry.IDs, entry.Err = entity.IDColumns(schema, results.GetIds(), offset, offset+rc) 155 if entry.Err != nil { 156 continue 157 } 158 // parse group-by values 159 if gb != nil { 160 entry.GroupByValue, entry.Err = entity.FieldDataColumn(gb, offset, offset+rc) 161 if entry.Err != nil { 162 offset += rc 163 continue 164 } 165 } 166 entry.Fields, entry.Err = c.parseSearchResult(schema, outputFields, fieldDataList, i, offset, offset+rc) 167 sr = append(sr, entry) 168 169 offset += rc 170 } 171 return sr, nil 172 } 173 174 func (c *GrpcClient) parseSearchResult(sch *entity.Schema, outputFields []string, fieldDataList []*schemapb.FieldData, _, from, to int) ([]entity.Column, error) { 175 var wildcard bool 176 outputFields, wildcard = expandWildcard(sch, outputFields) 177 // duplicated name will have only one column now 178 outputSet := make(map[string]struct{}) 179 for _, output := range outputFields { 180 outputSet[output] = struct{}{} 181 } 182 // fields := make(map[string]*schemapb.FieldData) 183 columns := make([]entity.Column, 0, len(outputFields)) 184 var dynamicColumn *entity.ColumnJSONBytes 185 for _, fieldData := range fieldDataList { 186 column, err := entity.FieldDataColumn(fieldData, from, to) 187 if err != nil { 188 return nil, err 189 } 190 if fieldData.GetIsDynamic() { 191 var ok bool 192 dynamicColumn, ok = column.(*entity.ColumnJSONBytes) 193 if !ok { 194 return nil, errors.New("dynamic field not json") 195 } 196 197 // return json column only explicitly specified in output fields and not in wildcard mode 198 if _, ok := outputSet[fieldData.GetFieldName()]; !ok && !wildcard { 199 continue 200 } 201 } 202 203 // remove processed field 204 delete(outputSet, fieldData.GetFieldName()) 205 206 columns = append(columns, column) 207 } 208 209 if len(outputSet) > 0 && dynamicColumn == nil { 210 var extraFields []string 211 for output := range outputSet { 212 extraFields = append(extraFields, output) 213 } 214 return nil, errors.Newf("extra output fields %v found and result does not dynamic field", extraFields) 215 } 216 // add dynamic column for extra fields 217 for outputField := range outputSet { 218 column := entity.NewColumnDynamic(dynamicColumn, outputField) 219 columns = append(columns, column) 220 } 221 222 return columns, nil 223 } 224 225 func expandWildcard(schema *entity.Schema, outputFields []string) ([]string, bool) { 226 wildcard := false 227 for _, outputField := range outputFields { 228 if outputField == "*" { 229 wildcard = true 230 } 231 } 232 if !wildcard { 233 return outputFields, false 234 } 235 236 set := make(map[string]struct{}) 237 result := make([]string, 0, len(schema.Fields)) 238 for _, field := range schema.Fields { 239 result = append(result, field.Name) 240 set[field.Name] = struct{}{} 241 } 242 243 // add dynamic fields output 244 for _, output := range outputFields { 245 if output == "*" { 246 continue 247 } 248 _, ok := set[output] 249 if !ok { 250 result = append(result, output) 251 } 252 } 253 return result, true 254 } 255 256 func PKs2Expr(backName string, ids entity.Column) string { 257 var expr string 258 var pkName = ids.Name() 259 if ids.Name() == "" { 260 pkName = backName 261 } 262 switch ids.Type() { 263 case entity.FieldTypeInt64: 264 expr = fmt.Sprintf("%s in %s", pkName, strings.Join(strings.Fields(fmt.Sprint(ids.FieldData().GetScalars().GetLongData().GetData())), ",")) 265 case entity.FieldTypeVarChar: 266 data := ids.FieldData().GetScalars().GetData().(*schemapb.ScalarField_StringData).StringData.GetData() 267 for i := range data { 268 data[i] = fmt.Sprintf("\"%s\"", data[i]) 269 } 270 expr = fmt.Sprintf("%s in [%s]", pkName, strings.Join(data, ",")) 271 } 272 return expr 273 } 274 275 // Get grabs the inserted entities using the primary key from the Collection. 276 func (c *GrpcClient) Get(ctx context.Context, collectionName string, ids entity.Column, opts ...GetOption) (ResultSet, error) { 277 if c.Service == nil { 278 return nil, ErrClientNotReady 279 } 280 281 o := &getOption{} 282 for _, opt := range opts { 283 opt(o) 284 } 285 286 if len(o.outputFields) == 0 { 287 coll, err := c.DescribeCollection(ctx, collectionName) 288 if err != nil { 289 return nil, err 290 } 291 for _, f := range coll.Schema.Fields { 292 o.outputFields = append(o.outputFields, f.Name) 293 } 294 } 295 296 return c.QueryByPks(ctx, collectionName, o.partitionNames, ids, o.outputFields) 297 } 298 299 // QueryByPks query record by specified primary key(s) 300 func (c *GrpcClient) QueryByPks(ctx context.Context, collectionName string, partitionNames []string, ids entity.Column, outputFields []string, opts ...SearchQueryOptionFunc) (ResultSet, error) { 301 if c.Service == nil { 302 return nil, ErrClientNotReady 303 } 304 // check primary keys 305 if ids.Len() == 0 { 306 return nil, errors.New("ids len must not be zero") 307 } 308 if ids.Type() != entity.FieldTypeInt64 && ids.Type() != entity.FieldTypeVarChar { // string key not supported yet 309 return nil, errors.New("only int64 and varchar column can be primary key for now") 310 } 311 312 expr := PKs2Expr("", ids) 313 314 return c.Query(ctx, collectionName, partitionNames, expr, outputFields, opts...) 315 } 316 317 // Query performs query by expression. 318 func (c *GrpcClient) Query(ctx context.Context, collectionName string, partitionNames []string, expr string, outputFields []string, opts ...SearchQueryOptionFunc) (ResultSet, error) { 319 if c.Service == nil { 320 return nil, ErrClientNotReady 321 } 322 323 var sch *entity.Schema 324 collInfo, ok := MetaCache.getCollectionInfo(collectionName) 325 if !ok { 326 coll, err := c.DescribeCollection(ctx, collectionName) 327 if err != nil { 328 return nil, err 329 } 330 sch = coll.Schema 331 } else { 332 sch = collInfo.Schema 333 } 334 335 option, err := makeSearchQueryOption(collectionName, opts...) 336 if err != nil { 337 return nil, err 338 } 339 340 req := &milvuspb.QueryRequest{ 341 DbName: "", // reserved field 342 CollectionName: collectionName, 343 Expr: expr, 344 OutputFields: outputFields, 345 PartitionNames: partitionNames, 346 GuaranteeTimestamp: option.GuaranteeTimestamp, 347 } 348 if option.Offset > 0 { 349 req.QueryParams = append(req.QueryParams, &commonpb.KeyValuePair{Key: offsetKey, Value: strconv.FormatInt(option.Offset, 10)}) 350 } 351 if option.Limit > 0 { 352 req.QueryParams = append(req.QueryParams, &commonpb.KeyValuePair{Key: limitKey, Value: strconv.FormatInt(option.Limit, 10)}) 353 } 354 if option.IgnoreGrowing { 355 req.QueryParams = append(req.QueryParams, &commonpb.KeyValuePair{Key: ignoreGrowingKey, Value: strconv.FormatBool(option.IgnoreGrowing)}) 356 } 357 if option.isIterator { 358 req.QueryParams = append(req.QueryParams, &commonpb.KeyValuePair{Key: iteratorKey, Value: strconv.FormatBool(true)}) 359 } 360 if option.reduceForBest { 361 req.QueryParams = append(req.QueryParams, &commonpb.KeyValuePair{Key: reduceForBestKey, Value: strconv.FormatBool(true)}) 362 } 363 364 resp, err := c.Service.Query(ctx, req) 365 if err != nil { 366 return nil, err 367 } 368 err = handleRespStatus(resp.GetStatus()) 369 if err != nil { 370 return nil, err 371 } 372 373 fieldsData := resp.GetFieldsData() 374 375 columns, err := c.parseSearchResult(sch, outputFields, fieldsData, 0, 0, -1) //entity.FieldDataColumn(fieldData, 0, -1) 376 if err != nil { 377 return nil, err 378 } 379 380 return columns, nil 381 } 382 383 func getPKField(schema *entity.Schema) *entity.Field { 384 for _, f := range schema.Fields { 385 if f.PrimaryKey { 386 return f 387 } 388 } 389 return nil 390 } 391 392 func getVectorField(schema *entity.Schema) *entity.Field { 393 for _, f := range schema.Fields { 394 if f.DataType == entity.FieldTypeFloatVector || f.DataType == entity.FieldTypeBinaryVector { 395 return f 396 } 397 } 398 return nil 399 } 400 401 func prepareSearchRequest(collName string, partitions []string, 402 expr string, outputFields []string, vectors []entity.Vector, vectorField string, 403 metricType entity.MetricType, topK int, sp entity.SearchParam, opt *SearchQueryOption) (*milvuspb.SearchRequest, error) { 404 params := sp.Params() 405 params[forTuningKey] = opt.ForTuning 406 bs, err := json.Marshal(params) 407 if err != nil { 408 return nil, err 409 } 410 411 spMap := map[string]string{ 412 "anns_field": vectorField, 413 "topk": fmt.Sprintf("%d", topK), 414 "params": string(bs), 415 "metric_type": string(metricType), 416 "round_decimal": "-1", 417 ignoreGrowingKey: strconv.FormatBool(opt.IgnoreGrowing), 418 offsetKey: fmt.Sprintf("%d", opt.Offset), 419 groupByKey: opt.GroupByField, 420 } 421 if opt.GroupByField != "" { 422 spMap[groupByKey] = opt.GroupByField 423 } 424 searchParams := entity.MapKvPairs(spMap) 425 426 req := &milvuspb.SearchRequest{ 427 DbName: "", 428 CollectionName: collName, 429 PartitionNames: partitions, 430 Dsl: expr, 431 PlaceholderGroup: vector2PlaceholderGroupBytes(vectors), 432 DslType: commonpb.DslType_BoolExprV1, 433 OutputFields: outputFields, 434 SearchParams: searchParams, 435 GuaranteeTimestamp: opt.GuaranteeTimestamp, 436 Nq: int64(len(vectors)), 437 } 438 return req, nil 439 } 440 441 // GetPersistentSegmentInfo get persistent segment info 442 func (c *GrpcClient) GetPersistentSegmentInfo(ctx context.Context, collName string) ([]*entity.Segment, error) { 443 if c.Service == nil { 444 return []*entity.Segment{}, ErrClientNotReady 445 } 446 req := &milvuspb.GetPersistentSegmentInfoRequest{ 447 DbName: "", // reserved 448 CollectionName: collName, 449 } 450 resp, err := c.Service.GetPersistentSegmentInfo(ctx, req) 451 if err != nil { 452 return []*entity.Segment{}, err 453 } 454 if err := handleRespStatus(resp.GetStatus()); err != nil { 455 return []*entity.Segment{}, err 456 } 457 segments := make([]*entity.Segment, 0, len(resp.GetInfos())) 458 for _, info := range resp.GetInfos() { 459 segments = append(segments, &entity.Segment{ 460 ID: info.GetSegmentID(), 461 CollectionID: info.GetCollectionID(), 462 ParititionID: info.GetPartitionID(), 463 NumRows: info.GetNumRows(), 464 State: info.GetState(), 465 }) 466 } 467 468 return segments, nil 469 } 470 471 // GetQuerySegmentInfo get query query cluster segment loaded info 472 func (c *GrpcClient) GetQuerySegmentInfo(ctx context.Context, collName string) ([]*entity.Segment, error) { 473 if c.Service == nil { 474 return []*entity.Segment{}, ErrClientNotReady 475 } 476 req := &milvuspb.GetQuerySegmentInfoRequest{ 477 DbName: "", // reserved 478 CollectionName: collName, 479 } 480 resp, err := c.Service.GetQuerySegmentInfo(ctx, req) 481 if err != nil { 482 return []*entity.Segment{}, err 483 } 484 if err := handleRespStatus(resp.GetStatus()); err != nil { 485 return []*entity.Segment{}, err 486 } 487 488 segments := make([]*entity.Segment, 0, len(resp.GetInfos())) 489 for _, info := range resp.GetInfos() { 490 segments = append(segments, &entity.Segment{ 491 ID: info.GetSegmentID(), 492 CollectionID: info.GetCollectionID(), 493 ParititionID: info.GetPartitionID(), 494 IndexID: info.GetIndexID(), 495 NumRows: info.GetNumRows(), 496 }) 497 } 498 499 return segments, nil 500 } 501 502 func (c *GrpcClient) CalcDistance(ctx context.Context, collName string, partitions []string, 503 metricType entity.MetricType, opLeft, opRight entity.Column) (entity.Column, error) { 504 if c.Service == nil { 505 return nil, ErrClientNotReady 506 } 507 if opLeft == nil || opRight == nil { 508 return nil, errors.New("operators cannot be nil") 509 } 510 511 // check meta 512 if err := c.checkCollectionExists(ctx, collName); err != nil { 513 return nil, err 514 } 515 for _, partition := range partitions { 516 if err := c.checkPartitionExists(ctx, collName, partition); err != nil { 517 return nil, err 518 } 519 } 520 if err := c.checkCollField(ctx, collName, opLeft.Name(), isVectorField); err != nil { 521 return nil, err 522 } 523 if err := c.checkCollField(ctx, collName, opRight.Name(), isVectorField); err != nil { 524 return nil, err 525 } 526 527 req := &milvuspb.CalcDistanceRequest{ 528 OpLeft: columnToVectorsArray(collName, partitions, opLeft), 529 OpRight: columnToVectorsArray(collName, partitions, opRight), 530 Params: entity.MapKvPairs(map[string]string{ 531 "metric": string(metricType), 532 }), 533 } 534 if req.OpLeft == nil || req.OpRight == nil { 535 return nil, errors.New("invalid operator passed") 536 } 537 538 resp, err := c.Service.CalcDistance(ctx, req) 539 if err != nil { 540 return nil, err 541 } 542 if err := handleRespStatus(resp.GetStatus()); err != nil { 543 return nil, err 544 } 545 546 if fd := resp.GetFloatDist(); fd != nil { 547 return entity.NewColumnFloat("distance", fd.GetData()), nil 548 } 549 if id := resp.GetIntDist(); id != nil { 550 return entity.NewColumnInt32("distance", id.GetData()), nil 551 } 552 553 return nil, errors.New("distance field not supported") 554 } 555 556 func columnToVectorsArray(collName string, partitions []string, column entity.Column) *milvuspb.VectorsArray { 557 result := &milvuspb.VectorsArray{} 558 switch column.Type() { 559 case entity.FieldTypeInt64: // int64 id 560 int64Column, ok := column.(*entity.ColumnInt64) 561 if !ok { 562 return nil // server shall report error 563 } 564 ids := &milvuspb.VectorIDs{ 565 CollectionName: collName, 566 PartitionNames: partitions, 567 FieldName: column.Name(), // TODO use field name or column name? 568 IdArray: &schemapb.IDs{ 569 IdField: &schemapb.IDs_IntId{ 570 IntId: &schemapb.LongArray{ 571 Data: int64Column.Data(), 572 }, 573 }, 574 }, 575 } 576 result.Array = &milvuspb.VectorsArray_IdArray{IdArray: ids} 577 case entity.FieldTypeString: // string id 578 stringColumn, ok := column.(*entity.ColumnString) 579 if !ok { 580 return nil 581 } 582 ids := &milvuspb.VectorIDs{ 583 CollectionName: collName, 584 PartitionNames: partitions, 585 FieldName: column.Name(), 586 IdArray: &schemapb.IDs{ 587 IdField: &schemapb.IDs_StrId{ 588 StrId: &schemapb.StringArray{ 589 Data: stringColumn.Data(), 590 }, 591 }, 592 }, 593 } 594 result.Array = &milvuspb.VectorsArray_IdArray{IdArray: ids} 595 case entity.FieldTypeFloatVector: 596 fvColumn, ok := column.(*entity.ColumnFloatVector) 597 if !ok { 598 return nil 599 } 600 fvdata := fvColumn.Data() 601 data := make([]float32, 0, fvColumn.Len()*fvColumn.Dim()) 602 for _, row := range fvdata { 603 data = append(data, row...) 604 } 605 result.Array = &milvuspb.VectorsArray_DataArray{DataArray: &schemapb.VectorField{ 606 Dim: int64(fvColumn.Dim()), 607 Data: &schemapb.VectorField_FloatVector{ 608 FloatVector: &schemapb.FloatArray{ 609 Data: data, 610 }, 611 }, 612 }} 613 case entity.FieldTypeBinaryVector: 614 bvColumn, ok := column.(*entity.ColumnBinaryVector) 615 if !ok { 616 return nil 617 } 618 bvdata := bvColumn.Data() 619 data := make([]byte, 0, bvColumn.Dim()*bvColumn.Len()/8) 620 for _, row := range bvdata { 621 data = append(data, row...) 622 } 623 result.Array = &milvuspb.VectorsArray_DataArray{DataArray: &schemapb.VectorField{ 624 Dim: int64(bvColumn.Dim()), 625 Data: &schemapb.VectorField_BinaryVector{ 626 BinaryVector: data, 627 }, 628 }} 629 default: 630 return nil 631 } 632 return result 633 } 634 635 func isCollectionPrimaryKey(coll *entity.Collection, column entity.Column) bool { 636 if coll == nil || coll.Schema == nil || column == nil { 637 return false 638 } 639 640 // temporary check logic, since only one primary field is supported 641 for _, field := range coll.Schema.Fields { 642 if field.PrimaryKey { 643 if field.Name == column.Name() && field.DataType == column.Type() { 644 return true 645 } 646 return false 647 } 648 } 649 return false 650 } 651 652 // estRowSize estimate size per row for the specified schema 653 func estRowSize(sch *entity.Schema, selected []string) int64 { 654 var total int64 655 for _, field := range sch.Fields { 656 if len(selected) > 0 { 657 found := false 658 for _, sel := range selected { 659 if field.Name == sel { 660 found = true 661 break 662 } 663 } 664 if !found { 665 continue 666 } 667 } 668 switch field.DataType { 669 case entity.FieldTypeBool: 670 total++ 671 case entity.FieldTypeInt8: 672 total++ 673 case entity.FieldTypeInt16: 674 total += 2 675 case entity.FieldTypeInt32: 676 total += 4 677 case entity.FieldTypeInt64: 678 total += 8 679 case entity.FieldTypeFloat: 680 total += 4 681 case entity.FieldTypeDouble: 682 total += 8 683 case entity.FieldTypeString: 684 // TODO string need varchar[max] syntax like limitation 685 case entity.FieldTypeVarChar: 686 maxLength, err := strconv.Atoi(field.TypeParams[entity.TypeParamMaxLength]) 687 if err != nil { 688 log.Fatalf("got invalid varchar max length = %s", field.TypeParams[entity.TypeParamMaxLength]) 689 } 690 total += int64(maxLength) 691 case entity.FieldTypeFloatVector: 692 dimStr := field.TypeParams[entity.TypeParamDim] 693 dim, _ := strconv.ParseInt(dimStr, 10, 64) 694 total += 4 * dim 695 case entity.FieldTypeBinaryVector: 696 dimStr := field.TypeParams[entity.TypeParamDim] 697 dim, _ := strconv.ParseInt(dimStr, 10, 64) 698 total += 4 * dim / 8 699 } 700 } 701 return total 702 }