@@ -29,6 +29,8 @@ import {
2929 SparseFloatVector ,
3030 PlaceholderType ,
3131 SearchEmbList ,
32+ DataType ,
33+ DataTypeMap ,
3234} from '../' ;
3335
3436/**
@@ -135,6 +137,7 @@ type FormatedSearchRequest = {
135137 dsl ?: string ;
136138 dsl_type ?: DslType ;
137139 placeholder_group ?: Uint8Array ;
140+ ids ?: { int_id ?: { data : number [ ] } ; str_id ?: { data : string [ ] } } ;
138141 search_params ?: KeyValuePair [ ] ;
139142 consistency_level : ConsistencyLevelEnum ;
140143 expr ?: string ;
@@ -243,19 +246,25 @@ export const buildSearchRequest = (
243246 // build user search requests
244247 const userRequests = isHybridSearch
245248 ? searchHybridReq . data . map ( d => ( {
246- ...params ,
247- ...d ,
248- } ) )
249+ ...params ,
250+ ...d ,
251+ } ) )
249252 : [
250- {
251- ...searchSimpleReq ,
252- data :
253- searchReq . vectors || searchSimpleReq . vector || searchSimpleReq . data , // data or vector or vectors
254- anns_field :
255- searchSimpleReq . anns_field ||
256- Object . keys ( collectionInfo . anns_fields || { } ) [ 0 ] ,
257- } ,
258- ] ;
253+ {
254+ ...searchSimpleReq ,
255+ data :
256+ searchReq . vectors || searchSimpleReq . vector || searchSimpleReq . data , // data or vector or vectors
257+ anns_field :
258+ searchSimpleReq . anns_field ||
259+ Object . keys ( collectionInfo . anns_fields || { } ) [ 0 ] ,
260+ } ,
261+ ] ;
262+
263+ // get primary field type for ids
264+ const pkField = collectionInfo . schema . fields . find ( f => f . is_primary_key ) ;
265+ const pkDataType = pkField
266+ ? pkField . dataType || DataTypeMap [ pkField . data_type ]
267+ : undefined ;
259268
260269 for ( const userRequest of userRequests ) {
261270 const { data, anns_field } = userRequest ;
@@ -265,28 +274,82 @@ export const buildSearchRequest = (
265274 throw new Error ( ERROR_REASONS . NO_ANNS_FEILD_FOUND_IN_SEARCH ) ;
266275 }
267276
277+ // get ids from request
278+ const ids =
279+ userRequest . ids || searchReq . ids || searchSimpleReq . ids || undefined ;
280+
281+ // if ids is set, we use ids for search
282+ // check if ids is valid
283+ if ( ids && ids . length > 0 ) {
284+ if ( ! pkField ) {
285+ throw new Error (
286+ 'Primary field not found. Cannot use ids parameter without primary field.'
287+ ) ;
288+ }
289+
290+ // validation
291+ if ( pkDataType === DataType . Int64 ) {
292+ if (
293+ ! ( ids as any [ ] ) . every (
294+ ( id : any ) =>
295+ typeof id === 'number' || ( typeof id === 'string' && ! isNaN ( Number ( id ) ) )
296+ )
297+ ) {
298+ throw new Error (
299+ `The type of ids should be integer/string number because the primary key field ${ pkField . name } is Int64.`
300+ ) ;
301+ }
302+ } else if ( pkDataType === DataType . VarChar ) {
303+ if ( ! ( ids as any [ ] ) . every ( ( id : any ) => typeof id === 'string' ) ) {
304+ throw new Error (
305+ `The type of ids should be string because the primary key field ${ pkField . name } is VarChar.`
306+ ) ;
307+ }
308+ } else {
309+ throw new Error (
310+ `The primary key field ${ pkField . name } has unsupported type for ID search.`
311+ ) ;
312+ }
313+ }
314+
268315 // get search data
269- const searchData = formatSearchData ( data , annsField ) ;
316+ // if ids is set, we don't need to format search data
317+ // checks check if data is valid
318+ if ( ( ! ids || ids . length === 0 ) && ! data ) {
319+ throw new Error ( 'Search data is required' ) ;
320+ }
321+ const searchData =
322+ ids && ids . length > 0 ? [ ] : formatSearchData ( data ! , annsField ) ;
270323
271324 const request : FormatedSearchRequest = {
272325 collection_name : params . collection_name ,
273326 partition_names : params . partition_names || [ ] ,
274327 output_fields : params . output_fields || default_output_fields ,
275- nq : searchReq . nq || searchData . length ,
328+ nq : ids && ids . length > 0 ? ids . length : searchReq . nq || searchData . length ,
276329 dsl : userRequest . expr || searchReq . expr || searchSimpleReq . filter || '' , // expr, inner expr or outer expr
277330 dsl_type : DslType . BoolExprV1 ,
278- placeholder_group : buildPlaceholderGroupBytes (
279- milvusProto ,
280- searchData ,
281- annsField
282- ) ,
283331 search_params : parseToKeyValue (
284332 searchReq . search_params || buildSearchParams ( userRequest , anns_field )
285333 ) ,
286334 consistency_level :
287335 params . consistency_level || ( collectionInfo . consistency_level as any ) ,
288336 } ;
289337
338+ if ( ids && ids . length > 0 ) {
339+ if ( pkDataType === DataType . Int64 ) {
340+ request . ids = { int_id : { data : ids as number [ ] } } ;
341+ } else if ( pkDataType === DataType . VarChar ) {
342+ request . ids = { str_id : { data : ids as string [ ] } } ;
343+ }
344+ } else {
345+ // use placeholder_group for vector search
346+ request . placeholder_group = buildPlaceholderGroupBytes (
347+ milvusProto ,
348+ searchData ,
349+ annsField
350+ ) ;
351+ }
352+
290353 // if exprValues is set, add it to the request(inner)
291354 if ( userRequest . exprValues ) {
292355 request . expr_template_values = formatExprValues ( userRequest . exprValues ) ;
@@ -324,40 +387,40 @@ export const buildSearchRequest = (
324387 isHybridSearch : isHybridSearch ,
325388 request : isHybridSearch
326389 ? {
327- collection_name : params . collection_name ,
328- partition_names : params . partition_names ,
329- requests : requests ,
330- output_fields : requests [ 0 ] ?. output_fields ,
331- consistency_level : requests [ 0 ] ?. consistency_level ,
332-
333- // if ranker is set and it is a hybrid search, add it to the request
334- ...createFunctionScore ( rerank ) ,
335-
336- // if ranker is not exist, use RRFRanker ranker
337- ...{
338- rank_params : [
339- ...( isRerankerObj
340- ? parseToKeyValue ( convertRerankParams ( rerank as RerankerObj ) )
341- : ! hasRerankFunction && ! hasFunctionScore
390+ collection_name : params . collection_name ,
391+ partition_names : params . partition_names ,
392+ requests : requests ,
393+ output_fields : requests [ 0 ] ?. output_fields ,
394+ consistency_level : requests [ 0 ] ?. consistency_level ,
395+
396+ // if ranker is set and it is a hybrid search, add it to the request
397+ ...createFunctionScore ( rerank ) ,
398+
399+ // if ranker is not exist, use RRFRanker ranker
400+ ...{
401+ rank_params : [
402+ ...( isRerankerObj
403+ ? parseToKeyValue ( convertRerankParams ( rerank as RerankerObj ) )
404+ : ! hasRerankFunction && ! hasFunctionScore
342405 ? parseToKeyValue ( convertRerankParams ( RRFRanker ( ) ) )
343406 : [ ] ) ,
344- { key : 'round_decimal' , value : round_decimal } ,
345- {
346- key : 'limit' ,
347- value :
348- searchSimpleReq . limit ?? searchSimpleReq . topk ?? DEFAULT_TOPK ,
349- } ,
350- {
351- key : 'offset' ,
352- value : searchSimpleReq . offset ?? 0 ,
353- } ,
354- ] ,
355- } ,
356- }
357- : {
358- ...requests [ 0 ] ,
359- ...createFunctionScore ( rerank ) ,
407+ { key : 'round_decimal' , value : round_decimal } ,
408+ {
409+ key : 'limit' ,
410+ value :
411+ searchSimpleReq . limit ?? searchSimpleReq . topk ?? DEFAULT_TOPK ,
412+ } ,
413+ {
414+ key : 'offset' ,
415+ value : searchSimpleReq . offset ?? 0 ,
416+ } ,
417+ ] ,
360418 } ,
419+ }
420+ : {
421+ ...requests [ 0 ] ,
422+ ...createFunctionScore ( rerank ) ,
423+ } ,
361424 // need for parsing the search results
362425 ...( round_decimal !== - 1 ? { round_decimal } : { } ) ,
363426 nq : requests [ 0 ] . nq ,
@@ -435,8 +498,8 @@ export const formatSearchResult = (
435498 const value = isFixedSchema
436499 ? dataArray [ absoluteIndex ]
437500 : dataArray [ absoluteIndex ]
438- ? dataArray [ absoluteIndex ] [ field_name ]
439- : undefined ;
501+ ? dataArray [ absoluteIndex ] [ field_name ]
502+ : undefined ;
440503
441504 result [ field_name ] = value ;
442505 } ) ;
0 commit comments