diff --git a/internal/distributed/proxy/httpserver/handler_v1.go b/internal/distributed/proxy/httpserver/handler_v1.go index 8e66d43ba9..db46cd222a 100644 --- a/internal/distributed/proxy/httpserver/handler_v1.go +++ b/internal/distributed/proxy/httpserver/handler_v1.go @@ -911,7 +911,7 @@ func (h *HandlersV1) search(c *gin.Context) { DbName: httpReq.DbName, CollectionName: httpReq.CollectionName, Dsl: httpReq.Filter, - PlaceholderGroup: vector2PlaceholderGroupBytes(httpReq.Vector), + PlaceholderGroup: vectors2PlaceholderGroupBytes([][]float32{httpReq.Vector}), DslType: commonpb.DslType_BoolExprV1, OutputFields: httpReq.OutputFields, SearchParams: searchParams, diff --git a/internal/distributed/proxy/httpserver/handler_v2.go b/internal/distributed/proxy/httpserver/handler_v2.go index 40443562c0..5142ac7b2f 100644 --- a/internal/distributed/proxy/httpserver/handler_v2.go +++ b/internal/distributed/proxy/httpserver/handler_v2.go @@ -750,7 +750,6 @@ func generateSearchParams(ctx context.Context, c *gin.Context, reqParams map[str bs, _ := json.Marshal(params) searchParams := []*commonpb.KeyValuePair{ {Key: Params, Value: string(bs)}, - {Key: ParamRoundDecimal, Value: "-1"}, } return searchParams, nil } @@ -764,11 +763,12 @@ func (h *HandlersV2) search(ctx context.Context, c *gin.Context, anyReq any, dbN searchParams = append(searchParams, &commonpb.KeyValuePair{Key: common.TopKKey, Value: strconv.FormatInt(int64(httpReq.Limit), 10)}) searchParams = append(searchParams, &commonpb.KeyValuePair{Key: ParamOffset, Value: strconv.FormatInt(int64(httpReq.Offset), 10)}) searchParams = append(searchParams, &commonpb.KeyValuePair{Key: ParamGroupByField, Value: httpReq.GroupByField}) + searchParams = append(searchParams, &commonpb.KeyValuePair{Key: ParamRoundDecimal, Value: "-1"}) req := &milvuspb.SearchRequest{ DbName: dbName, CollectionName: httpReq.CollectionName, Dsl: httpReq.Filter, - PlaceholderGroup: vector2PlaceholderGroupBytes(httpReq.Vector), + PlaceholderGroup: vectors2PlaceholderGroupBytes(httpReq.Vector), DslType: commonpb.DslType_BoolExprV1, OutputFields: httpReq.OutputFields, PartitionNames: httpReq.PartitionNames, @@ -816,11 +816,12 @@ func (h *HandlersV2) hybridSearch(ctx context.Context, c *gin.Context, anyReq an searchParams = append(searchParams, &commonpb.KeyValuePair{Key: ParamOffset, Value: strconv.FormatInt(int64(subReq.Offset), 10)}) searchParams = append(searchParams, &commonpb.KeyValuePair{Key: ParamGroupByField, Value: subReq.GroupByField}) searchParams = append(searchParams, &commonpb.KeyValuePair{Key: proxy.AnnsFieldKey, Value: subReq.AnnsField}) + searchParams = append(searchParams, &commonpb.KeyValuePair{Key: ParamRoundDecimal, Value: "-1"}) searchReq := &milvuspb.SearchRequest{ DbName: dbName, CollectionName: httpReq.CollectionName, Dsl: subReq.Filter, - PlaceholderGroup: vector2PlaceholderGroupBytes(subReq.Vector), + PlaceholderGroup: vectors2PlaceholderGroupBytes(subReq.Vector), DslType: commonpb.DslType_BoolExprV1, OutputFields: httpReq.OutputFields, PartitionNames: httpReq.PartitionNames, @@ -835,7 +836,7 @@ func (h *HandlersV2) hybridSearch(ctx context.Context, c *gin.Context, anyReq an {Key: proxy.RankTypeKey, Value: httpReq.Rerank.Strategy}, {Key: proxy.RankParamsKey, Value: string(bs)}, {Key: ParamLimit, Value: strconv.FormatInt(int64(httpReq.Limit), 10)}, - {Key: "round_decimal", Value: strconv.FormatInt(int64(-1), 10)}, + {Key: ParamRoundDecimal, Value: "-1"}, } resp, err := wrapperProxy(ctx, c, req, h.checkAuth, false, func(reqCtx context.Context, req any) (interface{}, error) { return h.proxy.HybridSearch(reqCtx, req.(*milvuspb.HybridSearchRequest)) @@ -1351,6 +1352,11 @@ func (h *HandlersV2) listIndexes(ctx context.Context, c *gin.Context, anyReq any IndexDescriptions: []*milvuspb.IndexDescription{}, }, nil } + if resp != nil && errors.Is(merr.Error(resp.Status), merr.ErrIndexNotFound) { + return &milvuspb.DescribeIndexResponse{ + IndexDescriptions: []*milvuspb.IndexDescription{}, + }, nil + } return resp, err }) if err != nil { diff --git a/internal/distributed/proxy/httpserver/handler_v2_test.go b/internal/distributed/proxy/httpserver/handler_v2_test.go index 1618bfffcb..021d808ed8 100644 --- a/internal/distributed/proxy/httpserver/handler_v2_test.go +++ b/internal/distributed/proxy/httpserver/handler_v2_test.go @@ -604,6 +604,9 @@ func TestMethodGet(t *testing.T) { mp.EXPECT().GetLoadState(mock.Anything, mock.Anything).Return(&DefaultLoadStateResp, nil).Twice() mp.EXPECT().DescribeIndex(mock.Anything, mock.Anything).Return(&DefaultDescIndexesReqp, nil).Times(3) mp.EXPECT().DescribeIndex(mock.Anything, mock.Anything).Return(nil, merr.WrapErrIndexNotFoundForCollection(DefaultCollectionName)).Once() + mp.EXPECT().DescribeIndex(mock.Anything, mock.Anything).Return(&milvuspb.DescribeIndexResponse{ + Status: merr.Status(merr.WrapErrIndexNotFoundForCollection(DefaultCollectionName)), + }, nil).Once() mp.EXPECT().GetCollectionStatistics(mock.Anything, mock.Anything).Return(&milvuspb.GetCollectionStatisticsResponse{ Status: commonSuccessStatus, Stats: []*commonpb.KeyValuePair{ @@ -779,6 +782,9 @@ func TestMethodGet(t *testing.T) { queryTestCases = append(queryTestCases, rawTestCase{ path: versionalV2(IndexCategory, ListAction), }) + queryTestCases = append(queryTestCases, rawTestCase{ + path: versionalV2(IndexCategory, ListAction), + }) queryTestCases = append(queryTestCases, rawTestCase{ path: versionalV2(AliasCategory, ListAction), }) @@ -1020,35 +1026,41 @@ func TestDML(t *testing.T) { queryTestCases := []requestBodyTestCase{} queryTestCases = append(queryTestCases, requestBodyTestCase{ path: SearchAction, - requestBody: []byte(`{"collectionName": "book", "vector": [0.1, 0.2], "filter": "book_id in [2, 4, 6, 8]", "limit": 4, "outputFields": ["word_count"]}`), + requestBody: []byte(`{"collectionName": "book", "vector": [[0.1, 0.2]], "filter": "book_id in [2, 4, 6, 8]", "limit": 4, "outputFields": ["word_count"]}`), }) queryTestCases = append(queryTestCases, requestBodyTestCase{ path: SearchAction, - requestBody: []byte(`{"collectionName": "book", "vector": [0.1, 0.2], "filter": "book_id in [2, 4, 6, 8]", "limit": 4, "outputFields": ["word_count"], "params": {"radius":0.9}}`), + requestBody: []byte(`{"collectionName": "book", "vector": [[0.1, 0.2]], "filter": "book_id in [2, 4, 6, 8]", "limit": 4, "outputFields": ["word_count"], "params": {"radius":0.9}}`), }) queryTestCases = append(queryTestCases, requestBodyTestCase{ path: SearchAction, - requestBody: []byte(`{"collectionName": "book", "vector": [0.1, 0.2], "filter": "book_id in [2, 4, 6, 8]", "limit": 4, "outputFields": ["word_count"], "params": {"range_filter": 0.1}}`), + requestBody: []byte(`{"collectionName": "book", "vector": [[0.1, 0.2]], "filter": "book_id in [2, 4, 6, 8]", "limit": 4, "outputFields": ["word_count"], "params": {"range_filter": 0.1}}`), errMsg: "can only accept json format request, error: invalid search params", errCode: 1801, // ErrIncorrectParameterFormat }) queryTestCases = append(queryTestCases, requestBodyTestCase{ path: SearchAction, - requestBody: []byte(`{"collectionName": "book", "vector": [0.1, 0.2], "filter": "book_id in [2, 4, 6, 8]", "limit": 4, "outputFields": ["word_count"], "params": {"radius":0.9, "range_filter": 0.1}, "groupingField": "word_count"}`), + requestBody: []byte(`{"collectionName": "book", "vector": [[0.1, 0.2]], "filter": "book_id in [2, 4, 6, 8]", "limit": 4, "outputFields": ["word_count"], "params": {"radius":0.9, "range_filter": 0.1}, "groupingField": "word_count"}`), }) queryTestCases = append(queryTestCases, requestBodyTestCase{ path: SearchAction, - requestBody: []byte(`{"collectionName": "book", "vector": [0.1, 0.2], "filter": "book_id in [2, 4, 6, 8]", "limit": 4, "outputFields": ["word_count"], "params": {"radius":0.9, "range_filter": 0.1}, "groupingField": "test"}`), + requestBody: []byte(`{"collectionName": "book", "vector": [[0.1, 0.2]], "filter": "book_id in [2, 4, 6, 8]", "limit": 4, "outputFields": ["word_count"], "params": {"radius":0.9, "range_filter": 0.1}, "groupingField": "test"}`), errMsg: "groupBy field not found in schema: field not found[field=test]", errCode: 65535, }) queryTestCases = append(queryTestCases, requestBodyTestCase{ - path: HybridSearchAction, - requestBody: []byte(`{"collectionName": "hello_milvus", "search": [{"vector": [0.1, 0.2], "annsField": "float_vector1", "metricType": "L2", "limit": 3}, {"vector": [0.1, 0.2], "annsField": "float_vector2", "metricType": "L2", "limit": 3}], "rerank": {"strategy": "rrf", "params": {"k": 1}}}`), + path: SearchAction, + requestBody: []byte(`{"collectionName": "book", "vector": [["0.1", "0.2"]], "filter": "book_id in [2, 4, 6, 8]", "limit": 4, "outputFields": ["word_count"], "params": {"radius":0.9, "range_filter": 0.1}, "groupingField": "test"}`), + errMsg: "can only accept json format request, error: json: cannot unmarshal string into Go struct field SearchReqV2.vector of type float32", + errCode: 1801, }) queryTestCases = append(queryTestCases, requestBodyTestCase{ path: HybridSearchAction, - requestBody: []byte(`{"collectionName": "hello_milvus", "search": [{"vector": [0.1, 0.2], "annsField": "float_vector1", "metricType": "L2", "limit": 3}, {"vector": [0.1, 0.2], "annsField": "float_vector2", "metricType": "L2", "limit": 3}], "rerank": {"strategy": "weighted", "params": {"weights": [0.9, 0.8]}}}`), + requestBody: []byte(`{"collectionName": "hello_milvus", "search": [{"vector": [[0.1, 0.2]], "annsField": "float_vector1", "metricType": "L2", "limit": 3}, {"vector": [[0.1, 0.2]], "annsField": "float_vector2", "metricType": "L2", "limit": 3}], "rerank": {"strategy": "rrf", "params": {"k": 1}}}`), + }) + queryTestCases = append(queryTestCases, requestBodyTestCase{ + path: HybridSearchAction, + requestBody: []byte(`{"collectionName": "hello_milvus", "search": [{"vector": [[0.1, 0.2]], "annsField": "float_vector1", "metricType": "L2", "limit": 3}, {"vector": [[0.1, 0.2]], "annsField": "float_vector2", "metricType": "L2", "limit": 3}], "rerank": {"strategy": "weighted", "params": {"weights": [0.9, 0.8]}}}`), }) queryTestCases = append(queryTestCases, requestBodyTestCase{ path: QueryAction, diff --git a/internal/distributed/proxy/httpserver/request_v2.go b/internal/distributed/proxy/httpserver/request_v2.go index b99182cc0e..69acf8e465 100644 --- a/internal/distributed/proxy/httpserver/request_v2.go +++ b/internal/distributed/proxy/httpserver/request_v2.go @@ -127,13 +127,13 @@ func (req *CollectionDataReq) GetDbName() string { return req.DbName } type SearchReqV2 struct { DbName string `json:"dbName"` CollectionName string `json:"collectionName" binding:"required"` + Vector [][]float32 `json:"vector"` PartitionNames []string `json:"partitionNames"` Filter string `json:"filter"` GroupByField string `json:"groupingField"` Limit int32 `json:"limit"` Offset int32 `json:"offset"` OutputFields []string `json:"outputFields"` - Vector []float32 `json:"vector"` Params map[string]float64 `json:"params"` } @@ -145,7 +145,7 @@ type Rand struct { } type SubSearchReq struct { - Vector []float32 `json:"vector"` + Vector [][]float32 `json:"vector"` AnnsField string `json:"annsField"` Filter string `json:"filter"` GroupByField string `json:"groupingField"` diff --git a/internal/distributed/proxy/httpserver/utils.go b/internal/distributed/proxy/httpserver/utils.go index ba63d107d7..949dd53157 100644 --- a/internal/distributed/proxy/httpserver/utils.go +++ b/internal/distributed/proxy/httpserver/utils.go @@ -906,7 +906,8 @@ func serialize(fv []float32) []byte { return data } -func vector2PlaceholderGroupBytes(vectors []float32) []byte { +// todo: support [][]byte for BinaryVector +func vectors2PlaceholderGroupBytes(vectors [][]float32) []byte { var placeHolderType commonpb.PlaceholderType ph := &commonpb.PlaceholderValue{ Tag: "$0", @@ -916,7 +917,9 @@ func vector2PlaceholderGroupBytes(vectors []float32) []byte { placeHolderType = commonpb.PlaceholderType_FloatVector ph.Type = placeHolderType - ph.Values = append(ph.Values, serialize(vectors)) + for _, vector := range vectors { + ph.Values = append(ph.Values, serialize(vector)) + } } phg := &commonpb.PlaceholderGroup{ Placeholders: []*commonpb.PlaceholderValue{ diff --git a/internal/distributed/proxy/httpserver/utils_test.go b/internal/distributed/proxy/httpserver/utils_test.go index 0595a8f8dc..2f444ee7e1 100644 --- a/internal/distributed/proxy/httpserver/utils_test.go +++ b/internal/distributed/proxy/httpserver/utils_test.go @@ -490,10 +490,8 @@ func TestInsertWithInt64(t *testing.T) { func TestSerialize(t *testing.T) { parameters := []float32{0.11111, 0.22222} - // assert.Equal(t, "\ufffd\ufffd\ufffd=\ufffd\ufffdc\u003e", string(serialize(parameters))) - // assert.Equal(t, "vector2PlaceholderGroupBytes", string(vector2PlaceholderGroupBytes(parameters))) // todo assert.Equal(t, "\xa4\x8d\xe3=\xa4\x8dc>", string(serialize(parameters))) - assert.Equal(t, "\n\x10\n\x02$0\x10e\x1a\b\xa4\x8d\xe3=\xa4\x8dc>", string(vector2PlaceholderGroupBytes(parameters))) // todo + assert.Equal(t, "\n\x10\n\x02$0\x10e\x1a\b\xa4\x8d\xe3=\xa4\x8dc>", string(vectors2PlaceholderGroupBytes([][]float32{parameters}))) // todo } func compareRow64(m1 map[string]interface{}, m2 map[string]interface{}) bool {