diff --git a/test/common/utils.go b/test/common/utils.go index 3fef2537..aa6fc949 100644 --- a/test/common/utils.go +++ b/test/common/utils.go @@ -412,28 +412,42 @@ func GenArrayColumnData(start int, nb int, fieldName string, opts ...GenColumnDa } } +type JSONStruct struct { + Number int32 `json:"number" milvus:"name:number"` + String string `json:"string" milvus:"name:string"` + Bool bool `json:"bool" milvus:"name:bool"` + List []int64 `json:"list" milvus:"name:list"` +} + // GenDefaultJSONData gen default column with data func GenDefaultJSONData(columnName string, start int, nb int) *entity.ColumnJSONBytes { - type JSONStruct struct { - Number int32 `json:"number" milvus:"name:number"` - String string `json:"string" milvus:"name:string"` - Bool bool `json:"bool" milvus:"name:bool"` - List []int64 `json:"list" milvus:"name:list"` - } jsonValues := make([][]byte, 0, nb) - var m JSONStruct + var m interface{} for i := start; i < start+nb; i++ { - if i%2 == 0 { - m = JSONStruct{ - String: strconv.Itoa(i), - Bool: i%2 == 0, + if i < (start+nb)/2 { + if i%2 == 0 { + m = JSONStruct{ + String: strconv.Itoa(i), + Bool: i%2 == 0, + } + } else { + m = JSONStruct{ + Number: int32(i), + String: strconv.Itoa(i), + Bool: i%2 == 0, + List: []int64{int64(i), int64(i + 1)}, + } } } else { - m = JSONStruct{ - Number: int32(i), - String: strconv.Itoa(i), - Bool: i%2 == 0, - List: []int64{int64(i), int64(i + 1)}, + switch i % 4 { + case 0: + m = i + case 1: + m = float32(i) + case 2: + m = strconv.Itoa(i) + case 3: + m = []int64{int64(i), int64(i + 1)} } } bs, err := json.Marshal(&m) @@ -668,12 +682,6 @@ func GenDefaultVarcharRows(start int, nb int, dim int64, enableDynamicField bool func GenDefaultJSONRows(start int, nb int, dim int64, enableDynamicField bool) []interface{} { rows := make([]interface{}, 0, nb) - type JSONStruct struct { - Number int32 `json:"number" milvus:"name:number"` - String string `json:"string" milvus:"name:string"` - Bool bool `json:"bool" milvus:"name:bool"` - List []int64 `json:"list" milvus:"name:list"` - } // BaseRow generate insert rows type BaseRow struct { @@ -1030,26 +1038,37 @@ func MergeColumnsToDynamic(nb int, columns []entity.Column) *entity.ColumnJSONBy // --- gen row data --- // --- index utils --- +var SupportFloatMetricType = []entity.MetricType{ + entity.L2, + entity.IP, + entity.COSINE, +} + +var SupportBinFlatMetricType = []entity.MetricType{ + entity.JACCARD, + entity.HAMMING, + entity.SUBSTRUCTURE, + entity.SUPERSTRUCTURE, +} + +var SupportBinIvfFlatMetricType = []entity.MetricType{ + entity.JACCARD, + entity.HAMMING, +} // GenAllFloatIndex gen all float vector index -func GenAllFloatIndex(metricType entity.MetricType) []entity.Index { +func GenAllFloatIndex() []entity.Index { nlist := 128 - idxFlat, _ := entity.NewIndexFlat(metricType) - idxIvfFlat, _ := entity.NewIndexIvfFlat(metricType, nlist) - idxIvfSq8, _ := entity.NewIndexIvfSQ8(metricType, nlist) - idxIvfPq, _ := entity.NewIndexIvfPQ(metricType, nlist, 16, 8) - idxHnsw, _ := entity.NewIndexHNSW(metricType, 8, 96) - idxScann, _ := entity.NewIndexSCANN(metricType, 16, false) - idxDiskAnn, _ := entity.NewIndexDISKANN(metricType) - - allFloatIndex := []entity.Index{ - idxFlat, - idxIvfFlat, - idxIvfSq8, - idxIvfPq, - idxHnsw, - idxScann, - idxDiskAnn, + var allFloatIndex []entity.Index + for _, metricType := range SupportFloatMetricType { + idxFlat, _ := entity.NewIndexFlat(metricType) + idxIvfFlat, _ := entity.NewIndexIvfFlat(metricType, nlist) + idxIvfSq8, _ := entity.NewIndexIvfSQ8(metricType, nlist) + idxIvfPq, _ := entity.NewIndexIvfPQ(metricType, nlist, 16, 8) + idxHnsw, _ := entity.NewIndexHNSW(metricType, 8, 96) + idxScann, _ := entity.NewIndexSCANN(metricType, 16, false) + idxDiskAnn, _ := entity.NewIndexDISKANN(metricType) + allFloatIndex = append(allFloatIndex, idxFlat, idxIvfFlat, idxIvfSq8, idxIvfPq, idxHnsw, idxScann, idxDiskAnn) } return allFloatIndex } @@ -1084,15 +1103,17 @@ type InvalidExprStruct struct { } var InvalidExpressions = []InvalidExprStruct{ - {Expr: "id in [0]", ErrNil: true, ErrMsg: "fieldName(id) not found"}, // not exist field but no error - {Expr: "int64 in not [0]", ErrNil: false, ErrMsg: "cannot parse expression"}, // wrong term expr keyword - {Expr: "int64 < floatVec", ErrNil: false, ErrMsg: "not supported"}, // unsupported compare field - {Expr: "floatVec in [0]", ErrNil: false, ErrMsg: "cannot be casted to FloatVector"}, // value and field type mismatch - {Expr: fmt.Sprintf("%s == 1", DefaultJSONFieldName), ErrNil: false, ErrMsg: "can not comparisons jsonField directly"}, - {Expr: fmt.Sprintf("%s == 1", DefaultDynamicFieldName), ErrNil: false, ErrMsg: "can not comparisons jsonField directly"}, + {Expr: "id in [0]", ErrNil: true, ErrMsg: "fieldName(id) not found"}, // not exist field but no error + {Expr: "int64 in not [0]", ErrNil: false, ErrMsg: "cannot parse expression"}, // wrong term expr keyword + {Expr: "int64 < floatVec", ErrNil: false, ErrMsg: "not supported"}, // unsupported compare field + {Expr: "floatVec in [0]", ErrNil: false, ErrMsg: "cannot be casted to FloatVector"}, // value and field type mismatch + {Expr: fmt.Sprintf("%s == 1", DefaultJSONFieldName), ErrNil: true, ErrMsg: ""}, // hist empty + {Expr: fmt.Sprintf("%s like 'a%%' ", DefaultJSONFieldName), ErrNil: true, ErrMsg: ""}, // hist empty + {Expr: fmt.Sprintf("%s > 1", DefaultDynamicFieldName), ErrNil: true, ErrMsg: ""}, // hits empty {Expr: fmt.Sprintf("%s[\"dynamicList\"] == [2, 3]", DefaultDynamicFieldName), ErrNil: true, ErrMsg: ""}, {Expr: fmt.Sprintf("%s['a'] == [2, 3]", DefaultJSONFieldName), ErrNil: true, ErrMsg: ""}, // json field not exist {Expr: fmt.Sprintf("%s['number'] == [2, 3]", DefaultJSONFieldName), ErrNil: true, ErrMsg: ""}, // field exist but type not match + {Expr: fmt.Sprintf("%s[0] == [2, 3]", DefaultJSONFieldName), ErrNil: true, ErrMsg: ""}, // field exist but type not match {Expr: fmt.Sprintf("json_contains (%s['number'], 2)", DefaultJSONFieldName), ErrNil: true, ErrMsg: ""}, {Expr: fmt.Sprintf("json_contains (%s['list'], [2])", DefaultJSONFieldName), ErrNil: true, ErrMsg: ""}, {Expr: fmt.Sprintf("json_contains_all (%s['list'], 2)", DefaultJSONFieldName), ErrNil: false, ErrMsg: "contains_all operation element must be an array"}, @@ -1100,6 +1121,7 @@ var InvalidExpressions = []InvalidExprStruct{ {Expr: fmt.Sprintf("json_contains_aby (%s['list'], 2)", DefaultJSONFieldName), ErrNil: false, ErrMsg: "invalid expression: json_contains_aby"}, {Expr: fmt.Sprintf("json_contains_aby (%s['list'], 2)", DefaultJSONFieldName), ErrNil: false, ErrMsg: "invalid expression: json_contains_aby"}, {Expr: fmt.Sprintf("%s[-1] > %d", DefaultInt8ArrayField, TestCapacity), ErrNil: false, ErrMsg: "cannot parse expression"}, // array[-1] > + {Expr: fmt.Sprintf(fmt.Sprintf("%s[-1] > 1", DefaultJSONFieldName)), ErrNil: false, ErrMsg: "invalid expression"}, // json[-1] > } // --- search utils --- diff --git a/test/testcases/collection_test.go b/test/testcases/collection_test.go index 8d18c8b9..e8ccabbc 100644 --- a/test/testcases/collection_test.go +++ b/test/testcases/collection_test.go @@ -372,7 +372,7 @@ func TestCreateCollectionDescription(t *testing.T) { } // test create collection with invalid dim -func TestCreateCollectionInvalidDim(t *testing.T) { +func TestCreateBinaryCollectionInvalidDim(t *testing.T) { t.Parallel() type invalidDimStruct struct { dim string @@ -380,11 +380,13 @@ func TestCreateCollectionInvalidDim(t *testing.T) { } invalidDims := []invalidDimStruct{ {dim: "10", errMsg: "should be multiple of 8"}, - {dim: "0", errMsg: "should be in range 1 ~ 32768"}, + {dim: "0", errMsg: "should be in range 2 ~ 32768"}, + {dim: "1", errMsg: "should be in range 2 ~ 32768"}, {dim: "", errMsg: "invalid syntax"}, {dim: "中文", errMsg: "invalid syntax"}, {dim: "%$#", errMsg: "invalid syntax"}, - {dim: fmt.Sprintf("%d", common.MaxDim+1), errMsg: "should be in range 1 ~ 32768"}, + {dim: fmt.Sprintf("%d", common.MaxDim*9), errMsg: "binary vector dimension should be in range 2 ~ 262144"}, + {dim: fmt.Sprintf("%d", common.MaxDim*8+1), errMsg: "binary vector dimension should be multiple of 8"}, } // connect @@ -405,6 +407,39 @@ func TestCreateCollectionInvalidDim(t *testing.T) { } } +func TestCreateFloatCollectionInvalidDim(t *testing.T) { + t.Parallel() + type invalidDimStruct struct { + dim string + errMsg string + } + invalidDims := []invalidDimStruct{ + {dim: "0", errMsg: "should be in range 2 ~ 32768"}, + {dim: "1", errMsg: "should be in range 2 ~ 32768"}, + {dim: "", errMsg: "invalid syntax"}, + {dim: "中文", errMsg: "invalid syntax"}, + {dim: "%$#", errMsg: "invalid syntax"}, + {dim: fmt.Sprintf("%d", common.MaxDim+1), errMsg: "float vector dimension should be in range 2 ~ 32768"}, + } + + // connect + ctx := createContext(t, time.Second*common.DefaultTimeout) + mc := createMilvusClient(ctx, t) + + // create binary collection with autoID true + pkField := common.GenField("", entity.FieldTypeInt64, common.WithIsPrimaryKey(true), common.WithAutoID(true)) + for _, invalidDim := range invalidDims { + collName := common.GenRandomString(6) + binaryFields := entity.NewField(). + WithName(common.DefaultFloatVecFieldName). + WithDataType(entity.FieldTypeFloatVector). + WithTypeParams(entity.TypeParamDim, invalidDim.dim) + schema := common.GenSchema(collName, true, []*entity.Field{pkField, binaryFields}) + errCreate := mc.CreateCollection(ctx, schema, common.DefaultShards) + common.CheckErr(t, errCreate, false, invalidDim.errMsg) + } +} + func TestCreateJsonCollection(t *testing.T) { ctx := createContext(t, time.Second*common.DefaultTimeout) mc := createMilvusClient(ctx, t) diff --git a/test/testcases/gorupby_search_test.go b/test/testcases/gorupby_search_test.go new file mode 100644 index 00000000..005d5d61 --- /dev/null +++ b/test/testcases/gorupby_search_test.go @@ -0,0 +1,371 @@ +//go:build L0 + +package testcases + +import ( + "context" + "fmt" + "log" + "testing" + "time" + + "github.com/milvus-io/milvus-sdk-go/v2/client" + "github.com/milvus-io/milvus-sdk-go/v2/test/base" + + "github.com/milvus-io/milvus-sdk-go/v2/entity" + "github.com/milvus-io/milvus-sdk-go/v2/test/common" + + "github.com/stretchr/testify/require" +) + +// Generate groupBy-supported vector indexes +func genGroupByVectorIndex(metricType entity.MetricType) []entity.Index { + nlist := 128 + idxFlat, _ := entity.NewIndexFlat(metricType) + idxIvfFlat, _ := entity.NewIndexIvfFlat(metricType, nlist) + idxHnsw, _ := entity.NewIndexHNSW(metricType, 8, 96) + + allFloatIndex := []entity.Index{ + idxFlat, + idxIvfFlat, + idxHnsw, + } + return allFloatIndex +} + +// Generate groupBy-supported vector indexes +func genGroupByBinaryIndex(metricType entity.MetricType) []entity.Index { + nlist := 128 + idxBinFlat, _ := entity.NewIndexBinFlat(metricType, nlist) + idxBinIvfFlat, _ := entity.NewIndexBinIvfFlat(metricType, nlist) + + allFloatIndex := []entity.Index{ + idxBinFlat, + idxBinIvfFlat, + } + return allFloatIndex +} + +func genUnsupportedFloatGroupByIndex() []entity.Index { + idxIvfSq8, _ := entity.NewIndexIvfSQ8(entity.L2, 128) + idxIvfPq, _ := entity.NewIndexIvfPQ(entity.L2, 128, 16, 8) + idxScann, _ := entity.NewIndexSCANN(entity.L2, 16, false) + idxDiskAnn, _ := entity.NewIndexDISKANN(entity.L2) + return []entity.Index{ + idxIvfSq8, + idxIvfPq, + idxScann, + idxDiskAnn, + } +} + +func prepareDataForGroupBySearch(t *testing.T, loopInsert int, idx entity.Index, withGrowing bool) (*base.MilvusClient, context.Context, string) { + ctx := createContext(t, time.Second*common.DefaultTimeout) + mc := createMilvusClient(ctx, t) + + // create collection with all datatype + cp := CollectionParams{CollectionFieldsType: AllFields, AutoID: false, EnableDynamicField: true, + ShardsNum: common.DefaultShards, Dim: common.DefaultDim} + collName := createCollection(ctx, t, mc, cp) + + // insert + dp := DataParams{CollectionName: collName, PartitionName: "", CollectionFieldsType: AllFields, + start: 0, nb: 1000, dim: common.DefaultDim, EnableDynamicField: true, WithRows: false} + for i := 0; i < loopInsert; i++ { + _, _ = insertData(ctx, t, mc, dp) + } + + if !withGrowing { + mc.Flush(ctx, collName, false) + } + + // create vector index and scalar index + supportedGroupByFields := []string{common.DefaultIntFieldName, "int8", "int16", "int32", "varchar", "bool"} + idxScalar := entity.NewScalarIndex() + for _, groupByField := range supportedGroupByFields { + mc.CreateIndex(ctx, collName, groupByField, idxScalar, false) + //common.CheckErr(t, err, true) + } + err := mc.CreateIndex(ctx, collName, common.DefaultFloatVecFieldName, idx, false) + common.CheckErr(t, err, true) + + // load collection + err = mc.LoadCollection(ctx, collName, false) + common.CheckErr(t, err, true) + + return mc, ctx, collName +} + +// create coll with all datatype -> build all supported index +// -> search with WithGroupByField (int* + varchar + bool +// -> verify every top passage is the top of whole group +// output_fields: pk + groupBy +func TestSearchGroupByFloatDefault(t *testing.T) { + t.Parallel() + for _, metricType := range common.SupportFloatMetricType { + for _, idx := range genGroupByVectorIndex(metricType) { + // prepare data + mc, ctx, collName := prepareDataForGroupBySearch(t, 10, idx, false) + + // search params + queryVec := common.GenSearchVectors(common.DefaultNq, common.DefaultDim, entity.FieldTypeFloatVector) + sp, _ := entity.NewIndexIvfFlatSearchParam(32) + + // search with groupBy field + supportedGroupByFields := []string{common.DefaultIntFieldName, "int8", "int16", "int32", "varchar", "bool"} + for _, groupByField := range supportedGroupByFields { + resGroupBy, _ := mc.Search(ctx, collName, []string{}, "", []string{common.DefaultIntFieldName, groupByField}, queryVec, + common.DefaultFloatVecFieldName, metricType, common.DefaultTopK, sp, client.WithGroupByField(groupByField)) + + // verify each topK entity is the top1 of the whole group + hitsNum := 0 + total := 0 + for _, rs := range resGroupBy { + for i := 0; i < rs.ResultCount; i++ { + groupByValue, _ := rs.Fields.GetColumn(groupByField).Get(i) + pkValue, _ := rs.IDs.GetAsInt64(i) + var expr string + if groupByField == "varchar" { + expr = fmt.Sprintf("%s == '%v' ", groupByField, groupByValue) + } else { + expr = fmt.Sprintf("%s == %v", groupByField, groupByValue) + } + + // search filter with groupByValue is the top1 + resFilter, _ := mc.Search(ctx, collName, []string{}, expr, []string{common.DefaultIntFieldName, + groupByField}, queryVec, common.DefaultFloatVecFieldName, metricType, 1, sp) + filterTop1Pk, _ := resFilter[0].IDs.GetAsInt64(0) + //log.Printf("Search top1 with %s: groupByValue: %v, pkValue: %d. The returned pk by filter search is: %d", + // groupByField, groupByValue, pkValue, filterTop1Pk) + if filterTop1Pk == pkValue { + hitsNum += 1 + } + total += 1 + } + } + + // verify hits rate + hitsRate := float32(hitsNum) / float32(total) + _str := fmt.Sprintf("GroupBy search with field %s, nq=%d and limit=%d , then hitsNum= %d, hitsRate=%v\n", + groupByField, common.DefaultNq, common.DefaultTopK, hitsNum, hitsRate) + log.Println(_str) + require.GreaterOrEqualf(t, hitsRate, float32(0.2), _str) + } + } + } +} + +// binary vector -> not supported +func TestSearchGroupByBinaryDefault(t *testing.T) { + t.Parallel() + for _, metricType := range common.SupportBinIvfFlatMetricType { + for _, idx := range genGroupByBinaryIndex(metricType) { + ctx := createContext(t, time.Second*common.DefaultTimeout) + // connect + mc := createMilvusClient(ctx, t) + + // create collection with all datatype + cp := CollectionParams{CollectionFieldsType: VarcharBinaryVec, AutoID: false, EnableDynamicField: true, + ShardsNum: common.DefaultShards, Dim: common.DefaultDim} + collName := createCollection(ctx, t, mc, cp) + + // insert + dp := DataParams{CollectionName: collName, PartitionName: "", CollectionFieldsType: VarcharBinaryVec, + start: 0, nb: 1000, dim: common.DefaultDim, EnableDynamicField: true, WithRows: false} + for i := 0; i < 2; i++ { + _, _ = insertData(ctx, t, mc, dp) + } + mc.Flush(ctx, collName, false) + + // create index and load + err := mc.CreateIndex(ctx, collName, common.DefaultBinaryVecFieldName, idx, false) + common.CheckErr(t, err, true) + err = mc.LoadCollection(ctx, collName, false) + common.CheckErr(t, err, true) + + // search params + queryVec := common.GenSearchVectors(1, common.DefaultDim, entity.FieldTypeBinaryVector) + sp, _ := entity.NewIndexBinIvfFlatSearchParam(32) + supportedGroupByFields := []string{common.DefaultVarcharFieldName, common.DefaultBinaryVecFieldName} + + // search with groupBy field + for _, groupByField := range supportedGroupByFields { + _, err := mc.Search(ctx, collName, []string{}, "", []string{common.DefaultVarcharFieldName, groupByField}, queryVec, + common.DefaultBinaryVecFieldName, metricType, common.DefaultTopK, sp, client.WithGroupByField(groupByField)) + common.CheckErr(t, err, false, "Unsupported dataType for chunk brute force iterator:VECTOR_BINARY") + } + } + } +} + +// binary vector -> growing segments, maybe brute force +// default Bounded ConsistencyLevel -> succ ?? +// strong ConsistencyLevel -> error +func TestSearchGroupByBinaryGrowing(t *testing.T) { + //t.Skip("#31134") + t.Parallel() + for _, metricType := range common.SupportBinIvfFlatMetricType { + idxBinIvfFlat, _ := entity.NewIndexBinIvfFlat(metricType, 128) + ctx := createContext(t, time.Second*common.DefaultTimeout) + // connect + mc := createMilvusClient(ctx, t) + + // create collection with all datatype + cp := CollectionParams{CollectionFieldsType: VarcharBinaryVec, AutoID: false, EnableDynamicField: true, + ShardsNum: common.DefaultShards, Dim: common.DefaultDim} + collName := createCollection(ctx, t, mc, cp) + + // create index and load + err := mc.CreateIndex(ctx, collName, common.DefaultBinaryVecFieldName, idxBinIvfFlat, false) + common.CheckErr(t, err, true) + err = mc.LoadCollection(ctx, collName, false) + common.CheckErr(t, err, true) + + // insert + dp := DataParams{CollectionName: collName, PartitionName: "", CollectionFieldsType: VarcharBinaryVec, + start: 0, nb: 1000, dim: common.DefaultDim, EnableDynamicField: true, WithRows: false} + _, _ = insertData(ctx, t, mc, dp) + + // search params + queryVec := common.GenSearchVectors(common.DefaultNq, common.DefaultDim, entity.FieldTypeBinaryVector) + sp, _ := entity.NewIndexBinIvfFlatSearchParam(64) + supportedGroupByFields := []string{common.DefaultVarcharFieldName} + + // search with groupBy field + for _, groupByField := range supportedGroupByFields { + _, err := mc.Search(ctx, collName, []string{}, "", []string{common.DefaultVarcharFieldName, + groupByField}, queryVec, common.DefaultBinaryVecFieldName, metricType, common.DefaultTopK, sp, + client.WithGroupByField(groupByField), client.WithSearchQueryConsistencyLevel(entity.ClStrong)) + common.CheckErr(t, err, false, "Unsupported dataType for chunk brute force iterator:VECTOR_BINARY") + } + } +} + +// groupBy in growing segments, maybe growing index or brute force +func TestSearchGroupByFloatGrowing(t *testing.T) { + for _, metricType := range common.SupportFloatMetricType { + idxHnsw, _ := entity.NewIndexHNSW(metricType, 8, 96) + mc, ctx, collName := prepareDataForGroupBySearch(t, 1, idxHnsw, true) + + // search params + queryVec := common.GenSearchVectors(common.DefaultNq, common.DefaultDim, entity.FieldTypeFloatVector) + sp, _ := entity.NewIndexIvfFlatSearchParam(32) + supportedGroupByFields := []string{common.DefaultIntFieldName, "int8", "int16", "int32", "varchar", "bool"} + + // search with groupBy field + hitsNum := 0 + total := 0 + for _, groupByField := range supportedGroupByFields { + resGroupBy, _ := mc.Search(ctx, collName, []string{}, "", []string{common.DefaultIntFieldName, groupByField}, queryVec, + common.DefaultFloatVecFieldName, metricType, common.DefaultTopK, sp, client.WithGroupByField(groupByField), + client.WithSearchQueryConsistencyLevel(entity.ClStrong)) + + // verify each topK entity is the top1 in the group + for _, rs := range resGroupBy { + for i := 0; i < rs.ResultCount; i++ { + groupByValue, _ := rs.Fields.GetColumn(groupByField).Get(i) + pkValue, _ := rs.IDs.GetAsInt64(i) + var expr string + if groupByField == "varchar" { + expr = fmt.Sprintf("%s == '%v' ", groupByField, groupByValue) + } else { + expr = fmt.Sprintf("%s == %v", groupByField, groupByValue) + } + + resFilter, _ := mc.Search(ctx, collName, []string{}, expr, []string{common.DefaultIntFieldName, + groupByField}, queryVec, common.DefaultFloatVecFieldName, metricType, 1, sp) + + // search filter with groupByValue is the top1 + filterTop1Pk, _ := resFilter[0].IDs.GetAsInt64(0) + //log.Printf("Search top1 with %s: groupByValue: %v, pkValue: %d. The returned pk by filter search is: %d", + // groupByField, groupByValue, pkValue, filterTop1Pk) + if filterTop1Pk == pkValue { + hitsNum += 1 + } + total += 1 + } + } + // verify hits rate + hitsRate := float32(hitsNum) / float32(total) + _str := fmt.Sprintf("GroupBy search with field %s, nq=%d and limit=%d , then hitsNum= %d, hitsRate=%v\n", + groupByField, common.DefaultNq, common.DefaultTopK, hitsNum, hitsRate) + log.Println(_str) + require.GreaterOrEqual(t, hitsRate, float32(0.6), _str) + } + } +} + +// groupBy + pagination +func TestSearchGroupByPagination(t *testing.T) { + // create index and load + idx, _ := entity.NewIndexHNSW(entity.COSINE, 8, 96) + mc, ctx, collName := prepareDataForGroupBySearch(t, 5, idx, false) + + // search params + queryVec := common.GenSearchVectors(common.DefaultNq, common.DefaultDim, entity.FieldTypeFloatVector) + sp, _ := entity.NewIndexIvfFlatSearchParam(32) + var offset = int64(10) + + // search pagination & groupBy + resGroupByPagination, _ := mc.Search(ctx, collName, []string{}, "", []string{common.DefaultIntFieldName, common.DefaultVarcharFieldName}, + queryVec, common.DefaultFloatVecFieldName, entity.COSINE, common.DefaultTopK, sp, + client.WithGroupByField(common.DefaultVarcharFieldName), client.WithOffset(offset)) + + common.CheckSearchResult(t, resGroupByPagination, common.DefaultNq, common.DefaultTopK) + + // search limit=origin limit + offset + resGroupByDefault, _ := mc.Search(ctx, collName, []string{}, "", []string{common.DefaultIntFieldName, common.DefaultVarcharFieldName}, + queryVec, common.DefaultFloatVecFieldName, entity.COSINE, common.DefaultTopK+int(offset), sp, + client.WithGroupByField(common.DefaultVarcharFieldName)) + for i := 0; i < common.DefaultNq; i++ { + require.Equal(t, resGroupByDefault[i].IDs.(*entity.ColumnInt64).Data()[10:], resGroupByPagination[i].IDs.(*entity.ColumnInt64).Data()) + } +} + +// only support: "FLAT", "IVF_FLAT", "HNSW" +func TestSearchGroupByUnsupportedIndex(t *testing.T) { + t.Parallel() + for _, idx := range genUnsupportedFloatGroupByIndex() { + mc, ctx, collName := prepareDataForGroupBySearch(t, 3, idx, false) + + // groupBy search + queryVec := common.GenSearchVectors(common.DefaultNq, common.DefaultDim, entity.FieldTypeFloatVector) + sp, _ := entity.NewIndexIvfFlatSearchParam(32) + _, err := mc.Search(ctx, collName, []string{}, "", []string{common.DefaultIntFieldName, common.DefaultVarcharFieldName}, + queryVec, common.DefaultFloatVecFieldName, entity.MetricType(idx.Params()["metrics_type"]), + common.DefaultTopK, sp, client.WithGroupByField(common.DefaultVarcharFieldName)) + common.CheckErr(t, err, false, "trying to groupBy on unsupported index type will fail, "+ + "currently only support ivf-flat, ivf_cc and HNSW") + } +} + +// FLOAT, DOUBLE, JSON, ARRAY +func TestSearchGroupByUnsupportedDataType(t *testing.T) { + idxHnsw, _ := entity.NewIndexHNSW(entity.L2, 8, 96) + mc, ctx, collName := prepareDataForGroupBySearch(t, 1, idxHnsw, true) + + // groupBy search with unsupported field type + queryVec := common.GenSearchVectors(common.DefaultNq, common.DefaultDim, entity.FieldTypeFloatVector) + sp, _ := entity.NewIndexIvfFlatSearchParam(32) + for _, unsupportedField := range []string{"float", "double", "json", "floatVec", "int8Array", "floatArray"} { + _, err := mc.Search(ctx, collName, []string{}, "", []string{common.DefaultIntFieldName, common.DefaultVarcharFieldName}, + queryVec, common.DefaultFloatVecFieldName, entity.L2, + common.DefaultTopK, sp, client.WithGroupByField(unsupportedField)) + common.CheckErr(t, err, false, "unsupported data type") + } +} + +// groupBy + iterator -> not supported +func TestSearchGroupByIterator(t *testing.T) { + // TODO: sdk support +} + +// groupBy + range search +func TestSearchGroupByRangeSearch(t *testing.T) { + // TODO: sdk support +} + +// groupBy + advanced search +func TestSearchGroupByAdvancedSearch(t *testing.T) { + // TODO +} diff --git a/test/testcases/index_test.go b/test/testcases/index_test.go index 1da9e655..10114324 100644 --- a/test/testcases/index_test.go +++ b/test/testcases/index_test.go @@ -16,12 +16,26 @@ import ( "github.com/milvus-io/milvus-sdk-go/v2/test/common" ) +func supportScalarIndexFieldType(field entity.FieldType) bool { + vectorFieldTypes := []entity.FieldType{ + entity.FieldTypeBinaryVector, entity.FieldTypeFloatVector, + entity.FieldTypeFloat16Vector, entity.FieldTypeBFloat16Vector, + entity.FieldTypeArray, entity.FieldTypeJSON, + } + for _, vectorFieldType := range vectorFieldTypes { + if field == vectorFieldType { + return false + } + } + return true +} + // test create index with supported float vector index, L2 metric type func TestCreateIndex(t *testing.T) { t.Parallel() // create index - allFloatIndexes := common.GenAllFloatIndex(entity.L2) + allFloatIndexes := common.GenAllFloatIndex() for _, idx := range allFloatIndexes { ctx := createContext(t, time.Second*common.DefaultTimeout*3) // connect @@ -60,21 +74,37 @@ func TestCreateIndexDup(t *testing.T) { common.CheckErr(t, err, false, "CreateIndex failed: at most one distinct index is allowed per field") } -// test create index for varchar field -func TestCreateIndexString(t *testing.T) { +// test create scalar index on all scalar field +func TestCreateScalarIndex(t *testing.T) { + t.Skip("https://github.com/milvus-io/milvus-sdk-go/issues/671") ctx := createContext(t, time.Second*common.DefaultTimeout) //connect mc := createMilvusClient(ctx, t) - collName, _ := createVarcharCollectionWithDataIndex(ctx, t, mc, false) - idx := entity.NewScalarIndex() - err := mc.CreateIndex(ctx, collName, common.DefaultVarcharFieldName, idx, false, client.WithIndexName("scalar_index")) - common.CheckErr(t, err, true) + // create collection with all datatype + cp := CollectionParams{CollectionFieldsType: AllFields, AutoID: false, EnableDynamicField: true, + ShardsNum: common.DefaultShards, Dim: common.DefaultDim} + collName := createCollection(ctx, t, mc, cp) - // describe index - indexes, _ := mc.DescribeIndex(ctx, collName, common.DefaultVarcharFieldName) - expIndex := entity.NewGenericIndex("scalar_index", "", idx.Params()) - common.CheckIndexResult(t, indexes, expIndex) + // insert + dp := DataParams{CollectionName: collName, PartitionName: "", CollectionFieldsType: AllFields, + start: 0, nb: common.DefaultNb, dim: common.DefaultDim, EnableDynamicField: true, WithRows: false} + _, _ = insertData(ctx, t, mc, dp) + mc.Flush(ctx, collName, false) + + coll, _ := mc.DescribeCollection(ctx, collName) + idx := entity.NewScalarIndex() + for _, field := range coll.Schema.Fields { + if supportScalarIndexFieldType(field.DataType) { + err := mc.CreateIndex(ctx, collName, field.Name, idx, false, client.WithIndexName(field.Name)) + common.CheckErr(t, err, true) + + // describe index + indexes, _ := mc.DescribeIndex(ctx, collName, field.Name) + expIndex := entity.NewGenericIndex(field.Name, "", idx.Params()) + common.CheckIndexResult(t, indexes, expIndex) + } + } } // test create scalar index with vector field name @@ -172,38 +202,12 @@ func TestCreateIndexArrayField(t *testing.T) { } } -// test create index with supported float vector index, Ip metric type -func TestCreateIndexIp(t *testing.T) { - t.Parallel() - - // create index - allFloatIndexes := common.GenAllFloatIndex(entity.IP) - for _, idx := range allFloatIndexes { - ctx := createContext(t, time.Second*common.DefaultTimeout*3) - // connect - mc := createMilvusClient(ctx, t) - // create default collection with flush data - collName, _ := createCollectionWithDataIndex(ctx, t, mc, false, false) - err := mc.CreateIndex(ctx, collName, common.DefaultFloatVecFieldName, idx, false, client.WithIndexName("my_index")) - common.CheckErr(t, err, true) - - // describe index - indexes, _ := mc.DescribeIndex(ctx, collName, common.DefaultFloatVecFieldName) - expIndex := entity.NewGenericIndex("my_index", idx.IndexType(), idx.Params()) - common.CheckIndexResult(t, indexes, expIndex) - } -} - // test create index with supported binary vector index func TestCreateIndexBinaryFlat(t *testing.T) { t.Parallel() // create index - metricTypes := []entity.MetricType{ - entity.JACCARD, - entity.HAMMING, - } - for _, metricType := range metricTypes { + for _, metricType := range common.SupportBinFlatMetricType { idx, _ := entity.NewIndexBinFlat(metricType, 128) ctx := createContext(t, time.Second*common.DefaultTimeout) // connect @@ -226,11 +230,7 @@ func TestCreateIndexBinaryIvfFlat(t *testing.T) { t.Parallel() // create index - metricTypes := []entity.MetricType{ - entity.JACCARD, - entity.HAMMING, - } - for _, metricType := range metricTypes { + for _, metricType := range common.SupportBinIvfFlatMetricType { idx, _ := entity.NewIndexBinIvfFlat(metricType, 128) ctx := createContext(t, time.Second*common.DefaultTimeout) // connect @@ -258,6 +258,8 @@ func TestCreateBinaryIndexNotSupportedMetricsType(t *testing.T) { // create BinIvfFlat, BinFlat index with not supported metric type invalidMetricTypes := []entity.MetricType{ entity.L2, + entity.COSINE, + entity.IP, entity.TANIMOTO, } for _, metricType := range invalidMetricTypes { @@ -265,11 +267,22 @@ func TestCreateBinaryIndexNotSupportedMetricsType(t *testing.T) { idxBinFlat, _ := entity.NewIndexBinFlat(metricType, 128) err := mc.CreateIndex(ctx, collName, common.DefaultBinaryVecFieldName, idxBinFlat, false, client.WithIndexName("my_index")) common.CheckErr(t, err, false, "supported: [HAMMING JACCARD SUBSTRUCTURE SUPERSTRUCTURE]") + } + + invalidMetricTypes2 := []entity.MetricType{ + entity.L2, + entity.COSINE, + entity.IP, + entity.TANIMOTO, + entity.SUBSTRUCTURE, + entity.SUPERSTRUCTURE, + } + for _, metricType := range invalidMetricTypes2 { // create BinIvfFlat index idxBinIvfFlat, _ := entity.NewIndexBinIvfFlat(metricType, 128) errIvf := mc.CreateIndex(ctx, collName, common.DefaultBinaryVecFieldName, idxBinIvfFlat, false, client.WithIndexName("my_index2")) - common.CheckErr(t, errIvf, false, "supported: [HAMMING JACCARD SUBSTRUCTURE SUPERSTRUCTURE]") + common.CheckErr(t, errIvf, false, fmt.Sprintf("metric type %v not found or not supported", metricType)) } } diff --git a/test/testcases/query_test.go b/test/testcases/query_test.go index 5d7f579c..938bd064 100644 --- a/test/testcases/query_test.go +++ b/test/testcases/query_test.go @@ -3,6 +3,7 @@ package testcases import ( + "encoding/json" "fmt" "log" "strconv" @@ -484,7 +485,12 @@ func TestQueryJsonDynamicField(t *testing.T) { outputFields, ) common.CheckErr(t, err, true) - jsonColumn := common.GenDefaultJSONData(common.DefaultJSONFieldName, 0, 2) + m0 := common.JSONStruct{String: strconv.Itoa(0), Bool: true} + j0, _ := json.Marshal(&m0) + m1 := common.JSONStruct{Number: int32(1), String: strconv.Itoa(1), Bool: false, List: []int64{int64(1), int64(2)}} + j1, _ := json.Marshal(&m1) + jsonValues := [][]byte{j0, j1} + jsonColumn := entity.NewColumnJSONBytes(common.DefaultJSONFieldName, jsonValues) actualColumns := []entity.Column{pkColumn, jsonColumn} if dynamicField { dynamicColumn := common.MergeColumnsToDynamic(2, common.GenDynamicFieldData(0, 2)) @@ -578,11 +584,11 @@ func TestQueryCountJsonDynamicExpr(t *testing.T) { {expr: fmt.Sprintf("%s < 1000", common.DefaultFloatFieldName), count: 1000}, // json and dynamic field filter expr: == < in bool/ list/ int - {expr: fmt.Sprintf("%s['number'] == 0", common.DefaultJSONFieldName), count: 1500}, + {expr: fmt.Sprintf("%s['number'] == 0", common.DefaultJSONFieldName), count: 1500 / 2}, {expr: fmt.Sprintf("%s['number'] < 100 and %s['number'] != 0", common.DefaultJSONFieldName, common.DefaultJSONFieldName), count: 50}, {expr: fmt.Sprintf("%s < 100", common.DefaultDynamicNumberField), count: 100}, {expr: "dynamicNumber % 2 == 0", count: 1500}, - {expr: fmt.Sprintf("%s['bool'] == true", common.DefaultJSONFieldName), count: 1500}, + {expr: fmt.Sprintf("%s['bool'] == true", common.DefaultJSONFieldName), count: 1500 / 2}, {expr: fmt.Sprintf("%s == false", common.DefaultDynamicBoolField), count: 2000}, {expr: fmt.Sprintf("%s in ['1', '2'] ", common.DefaultDynamicStringField), count: 2}, {expr: fmt.Sprintf("%s['string'] in ['1', '2', '5'] ", common.DefaultJSONFieldName), count: 3}, @@ -600,12 +606,20 @@ func TestQueryCountJsonDynamicExpr(t *testing.T) { {expr: "dynamicString like '1%' ", count: 1111}, // key exist - {expr: fmt.Sprintf("exists %s['list']", common.DefaultJSONFieldName), count: common.DefaultNb}, + {expr: fmt.Sprintf("exists %s['list']", common.DefaultJSONFieldName), count: common.DefaultNb / 2}, {expr: fmt.Sprintf("exists a "), count: 0}, {expr: fmt.Sprintf("exists %s ", common.DefaultDynamicListField), count: 0}, {expr: fmt.Sprintf("exists %s ", common.DefaultDynamicStringField), count: common.DefaultNb}, // data type not match and no error {expr: fmt.Sprintf("%s['number'] == '0' ", common.DefaultJSONFieldName), count: 0}, + + // json field + {expr: fmt.Sprintf("%s >= 1500", common.DefaultJSONFieldName), count: 1500 / 2}, // json >= 1500 + {expr: fmt.Sprintf("%s > 1499.5", common.DefaultJSONFieldName), count: 1500 / 2}, // json >= 1500.0 + {expr: fmt.Sprintf("%s like '21%%'", common.DefaultJSONFieldName), count: 100 / 4}, // json like '21%' + {expr: fmt.Sprintf("%s == [1503, 1504]", common.DefaultJSONFieldName), count: 1}, // json == [1,2] + {expr: fmt.Sprintf("%s[0] > 1", common.DefaultJSONFieldName), count: 1500 / 4}, // json[0] > 1 + {expr: fmt.Sprintf("%s[0][0] > 1", common.DefaultJSONFieldName), count: 0}, // json == [1,2] } for _, _exprCount := range exprCounts { @@ -838,7 +852,13 @@ func TestQueryJsonDynamicFieldRows(t *testing.T) { []string{common.DefaultIntFieldName, common.DefaultJSONFieldName, common.DefaultDynamicFieldName}, ) common.CheckErr(t, err, true) - jsonColumn := common.GenDefaultJSONData(common.DefaultJSONFieldName, 0, 2) + //jsonColumn := common.GenDefaultJSONData(common.DefaultJSONFieldName, 0, 2) + m0 := common.JSONStruct{String: strconv.Itoa(0), Bool: true} + j0, _ := json.Marshal(&m0) + m1 := common.JSONStruct{Number: int32(1), String: strconv.Itoa(1), Bool: false, List: []int64{int64(1), int64(2)}} + j1, _ := json.Marshal(&m1) + jsonValues := [][]byte{j0, j1} + jsonColumn := entity.NewColumnJSONBytes(common.DefaultJSONFieldName, jsonValues) dynamicColumn := common.MergeColumnsToDynamic(2, common.GenDynamicFieldData(0, 2)) // gen dynamic json column diff --git a/test/testcases/search_test.go b/test/testcases/search_test.go index 7c03ecfa..bbab1bb7 100644 --- a/test/testcases/search_test.go +++ b/test/testcases/search_test.go @@ -1,4 +1,4 @@ -///go:build L0 +//go:build L0 package testcases @@ -46,6 +46,80 @@ func TestSearch(t *testing.T) { common.CheckSearchResult(t, searchResult, common.DefaultNq, common.DefaultTopK) } +func TestSearchFloatGrowing(t *testing.T) { + t.Parallel() + for _, idx := range common.GenAllFloatIndex() { + ctx := createContext(t, time.Second*common.DefaultTimeout) + // connect + mc := createMilvusClient(ctx, t) + + // create collection with all datatype + cp := CollectionParams{CollectionFieldsType: Int64FloatVec, AutoID: false, EnableDynamicField: true, + ShardsNum: common.DefaultShards, Dim: common.DefaultDim} + collName := createCollection(ctx, t, mc, cp) + + // create index and load + err := mc.CreateIndex(ctx, collName, common.DefaultFloatVecFieldName, idx, false) + common.CheckErr(t, err, true) + err = mc.LoadCollection(ctx, collName, false) + common.CheckErr(t, err, true) + + // insert + dp := DataParams{CollectionName: collName, PartitionName: "", CollectionFieldsType: Int64FloatVec, + start: 0, nb: common.DefaultNb, dim: common.DefaultDim, EnableDynamicField: true, WithRows: false} + _, err = insertData(ctx, t, mc, dp) + common.CheckErr(t, err, true) + + // search params + queryVec := common.GenSearchVectors(common.DefaultNq, common.DefaultDim, entity.FieldTypeFloatVector) + sp, _ := entity.NewIndexBinIvfFlatSearchParam(64) + searchResult, errSearch := mc.Search(ctx, collName, []string{}, "", []string{"*"}, queryVec, + common.DefaultFloatVecFieldName, entity.MetricType(idx.Params()["metrics_type"]), common.DefaultTopK, sp, + client.WithSearchQueryConsistencyLevel(entity.ClStrong)) + common.CheckErr(t, errSearch, true) + common.CheckOutputFields(t, searchResult[0].Fields, []string{common.DefaultIntFieldName, common.DefaultFloatVecFieldName, + common.DefaultFloatFieldName, common.DefaultDynamicFieldName}) + common.CheckSearchResult(t, searchResult, common.DefaultNq, common.DefaultTopK) + } +} + +func TestSearchBinaryGrowing(t *testing.T) { + t.Parallel() + for _, metricType := range common.SupportBinIvfFlatMetricType { + idxBinIvfFlat, _ := entity.NewIndexBinIvfFlat(metricType, 128) + ctx := createContext(t, time.Second*common.DefaultTimeout) + // connect + mc := createMilvusClient(ctx, t) + + // create collection with all datatype + cp := CollectionParams{CollectionFieldsType: VarcharBinaryVec, AutoID: false, EnableDynamicField: false, + ShardsNum: common.DefaultShards, Dim: common.DefaultDim} + collName := createCollection(ctx, t, mc, cp) + + // create index and load + err := mc.CreateIndex(ctx, collName, common.DefaultBinaryVecFieldName, idxBinIvfFlat, false) + common.CheckErr(t, err, true) + err = mc.LoadCollection(ctx, collName, false) + common.CheckErr(t, err, true) + + // insert + dp := DataParams{CollectionName: collName, PartitionName: "", CollectionFieldsType: VarcharBinaryVec, + start: 0, nb: common.DefaultNb, dim: common.DefaultDim, EnableDynamicField: false, WithRows: false} + _, err = insertData(ctx, t, mc, dp) + common.CheckErr(t, err, true) + + // search params + queryVec := common.GenSearchVectors(common.DefaultNq, common.DefaultDim, entity.FieldTypeBinaryVector) + sp, _ := entity.NewIndexBinIvfFlatSearchParam(64) + searchResult, errSearch := mc.Search(ctx, collName, []string{}, "", []string{"*"}, queryVec, + common.DefaultBinaryVecFieldName, metricType, common.DefaultTopK, sp, + client.WithSearchQueryConsistencyLevel(entity.ClStrong)) + common.CheckErr(t, errSearch, true) + common.CheckOutputFields(t, searchResult[0].Fields, []string{common.DefaultVarcharFieldName, common.DefaultBinaryVecFieldName}) + common.CheckSearchResult(t, searchResult, common.DefaultNq, common.DefaultTopK) + } +} + // test search collection not exist func TestSearchCollectionNotExist(t *testing.T) { ctx := createContext(t, time.Second*common.DefaultTimeout) @@ -66,7 +140,7 @@ func TestSearchCollectionNotExist(t *testing.T) { common.DefaultTopK, sp, ) - common.CheckErr(t, errSearch, false, "collection not found") + common.CheckErr(t, errSearch, false, "can't find collection") } // test search empty collection -> return empty @@ -877,12 +951,18 @@ func TestSearchJsonFieldExpr(t *testing.T) { fmt.Sprintf("exists %s['number'] ", common.DefaultJSONFieldName), // exists "json[\"number\"] > 1 and json[\"number\"] < 1000", // > and fmt.Sprintf("%s[\"number\"] > 10", common.DefaultJSONFieldName), // number > + fmt.Sprintf("%s != 10 ", common.DefaultJSONFieldName), // json != 10 fmt.Sprintf("%s[\"number\"] < 2000", common.DefaultJSONFieldName), // number < fmt.Sprintf("%s[\"bool\"] != true", common.DefaultJSONFieldName), // bool != fmt.Sprintf("%s[\"bool\"] == False", common.DefaultJSONFieldName), // bool == fmt.Sprintf("%s[\"bool\"] in [true]", common.DefaultJSONFieldName), // bool in fmt.Sprintf("%s[\"string\"] >= '1' ", common.DefaultJSONFieldName), // string >= fmt.Sprintf("%s['list'][0] > 200", common.DefaultJSONFieldName), // list filter + fmt.Sprintf("%s['list'] != [2, 3]", common.DefaultJSONFieldName), // json[list] != + fmt.Sprintf("%s > 2000", common.DefaultJSONFieldName), // json > 2000 + fmt.Sprintf("%s like '2%%' ", common.DefaultJSONFieldName), // json like '2%' + fmt.Sprintf("%s[0] > 2000 ", common.DefaultJSONFieldName), // json[0] > 2000 + fmt.Sprintf("%s > 2000.5 ", common.DefaultJSONFieldName), // json > 2000.5 } // search with jsonField expr key datatype and json data type mismatch @@ -1230,7 +1310,7 @@ func TestRangeSearchScannL2(t *testing.T) { sp.AddRangeFilter(20) _, errRange := mc.Search(ctx, collName, []string{}, "", []string{"*"}, queryVec, common.DefaultFloatVecFieldName, entity.L2, common.DefaultTopK, sp) - common.CheckErr(t, errRange, false, "range_filter(20) must be less than radius(15) for L2/HAMMING/JACCARD") + common.CheckErr(t, errRange, false, "range_filter must be less than radius for L2/HAMMING/JACCARD") } // test range search with scann index and IP COSINE metric type @@ -1289,15 +1369,14 @@ func TestRangeSearchScannIPCosine(t *testing.T) { sp.AddRangeFilter(10) _, errRange := mc.Search(ctx, collName, []string{}, "", []string{"*"}, queryVec, common.DefaultFloatVecFieldName, metricType, common.DefaultTopK, sp) - common.CheckErr(t, errRange, false, "range_filter(10) must be greater than radius(20) for IP/COSINE") + common.CheckErr(t, errRange, false, "range_filter must be greater than radius for IP/COSINE") } } -// test range search with scann index and entity.HAMMING, entity.JACCARD, entity.TANIMOTO metric type +// test range search with scann index and entity.HAMMING, entity.JACCARD metric type func TestRangeSearchScannBinary(t *testing.T) { - // not supported entity.TANIMOTO ?? t.Parallel() - for _, metricType := range []entity.MetricType{entity.HAMMING, entity.JACCARD} { + for _, metricType := range common.SupportBinIvfFlatMetricType { ctx := createContext(t, time.Second*common.DefaultTimeout) // connect mc := createMilvusClient(ctx, t) @@ -1350,7 +1429,7 @@ func TestRangeSearchScannBinary(t *testing.T) { sp.AddRangeFilter(100) _, errRange := mc.Search(ctx, collName, []string{}, "", []string{"*"}, queryVec, common.DefaultBinaryVecFieldName, metricType, common.DefaultTopK, sp) - common.CheckErr(t, errRange, false, "range_filter(100) must be less than radius(0) for L2/HAMMING/JACCARD") + common.CheckErr(t, errRange, false, "range_filter must be less than radius for L2/HAMMING/JACCARD") } }