@@ -302,8 +302,9 @@ public PerLeafResult searchLeaf(LeafReaderContext context, int k) throws IOExcep
302302 final BitSet filterBitSet = getFilteredDocsBitSet (context );
303303 stopStopWatchAndLog (stopWatch , "FilterBitSet creation" , segmentName );
304304
305- final int maxDoc = context .reader ().maxDoc ();
306- int filterCardinality = filterBitSet .cardinality ();
305+ // Save its cardinality, as the cardinality calculation is expensive.
306+ final int filterCardinality = filterBitSet .cardinality ();
307+
307308 // We don't need to go to JNI layer if no documents are found which satisfy the filters
308309 // We should give this condition a deeper look that where it should be placed. For now I feel this is a good
309310 // place,
@@ -320,18 +321,17 @@ public PerLeafResult searchLeaf(LeafReaderContext context, int k) throws IOExcep
320321 * This improves the recall.
321322 */
322323 if (isFilteredExactSearchPreferred (filterCardinality )) {
323- TopDocs result = doExactSearch (context , new BitSetIterator (filterBitSet , filterCardinality ), filterCardinality , k );
324- return new PerLeafResult (filterWeight == null ? null : filterBitSet , result );
324+ final TopDocs result = doExactSearch (context , new BitSetIterator (filterBitSet , filterCardinality ), filterCardinality , k );
325+ return new PerLeafResult (
326+ filterWeight == null ? null : filterBitSet ,
327+ filterCardinality ,
328+ result ,
329+ PerLeafResult .SearchMode .EXACT_SEARCH
330+ );
325331 }
326332
327- /*
328- * If filters match all docs in this segment, then null should be passed as filterBitSet
329- * so that it will not do a bitset look up in bottom search layer.
330- */
331- final BitSet annFilter = (filterWeight != null && filterCardinality == maxDoc ) ? null : filterBitSet ;
332-
333333 StopWatch annStopWatch = startStopWatch ();
334- final TopDocs topDocs = approximateSearch (context , annFilter , filterCardinality , k );
334+ final TopDocs topDocs = approximateSearch (context , filterBitSet , filterCardinality , k );
335335 stopStopWatchAndLog (annStopWatch , "ANN search" , segmentName );
336336 if (knnQuery .isExplain ()) {
337337 knnExplanation .addLeafResult (context .id (), topDocs .scoreDocs .length );
@@ -341,10 +341,21 @@ public PerLeafResult searchLeaf(LeafReaderContext context, int k) throws IOExcep
341341 // results less than K, though we have more than k filtered docs
342342 if (isExactSearchRequire (context , filterCardinality , topDocs .scoreDocs .length )) {
343343 final BitSetIterator docs = filterWeight != null ? new BitSetIterator (filterBitSet , filterCardinality ) : null ;
344- TopDocs result = doExactSearch (context , docs , filterCardinality , k );
345- return new PerLeafResult (filterWeight == null ? null : filterBitSet , result );
344+ final TopDocs result = doExactSearch (context , docs , filterCardinality , k );
345+ return new PerLeafResult (
346+ filterWeight == null ? null : filterBitSet ,
347+ filterCardinality ,
348+ result ,
349+ PerLeafResult .SearchMode .EXACT_SEARCH
350+ );
346351 }
347- return new PerLeafResult (filterWeight == null ? null : filterBitSet , topDocs );
352+
353+ return new PerLeafResult (
354+ filterWeight == null ? null : filterBitSet ,
355+ filterCardinality ,
356+ topDocs ,
357+ PerLeafResult .SearchMode .APPROXIMATE_SEARCH
358+ );
348359 }
349360
350361 private void stopStopWatchAndLog (@ Nullable final StopWatch stopWatch , final String prefixMessage , String segmentName ) {
@@ -413,9 +424,33 @@ private TopDocs doExactSearch(
413424 return exactSearch (context , exactSearcherContextBuilder .build ());
414425 }
415426
416- protected TopDocs approximateSearch (final LeafReaderContext context , final BitSet filterIdsBitSet , final int cardinality , final int k )
417- throws IOException {
427+ /**
428+ * Performs an approximate nearest neighbor (ANN) search on the provided index segment.
429+ * <p>
430+ * This method prepares all necessary query metadata before triggering the actual ANN search.
431+ * It extracts the {@code model_id} from field-level attributes if required, retrieves any
432+ * quantization or auxiliary metadata associated with the vector field, and applies quantization
433+ * to the query vector when applicable. After these preprocessing steps, it invokes
434+ * {@code doANNSearch(LeafReaderContext, BitSet, int, int)} to execute the approximate search
435+ * and obtain the top results.
436+ *
437+ * @param context the {@link LeafReaderContext} representing the current index segment
438+ * @param filterIdsBitSet an optional {@link BitSet} indicating document IDs to include in the search;
439+ * may be {@code null} if no filtering is required
440+ * @param filterCardinality the number of documents included in {@code filterIdsBitSet};
441+ * used to optimize search filtering
442+ * @param k the number of nearest neighbors to retrieve
443+ * @return a {@link TopDocs} object containing the top {@code k} approximate search results
444+ * @throws IOException if an error occurs while reading index data or accessing vector fields
445+ */
446+ public TopDocs approximateSearch (
447+ final LeafReaderContext context ,
448+ final BitSet filterIdsBitSet ,
449+ final int filterCardinality ,
450+ final int k
451+ ) throws IOException {
418452 final SegmentReader reader = Lucene .segmentReader (context .reader ());
453+
419454 FieldInfo fieldInfo = FieldInfoExtractor .getFieldInfo (reader , knnQuery .getField ());
420455
421456 if (fieldInfo == null ) {
@@ -465,6 +500,11 @@ protected TopDocs approximateSearch(final LeafReaderContext context, final BitSe
465500 // TODO: Change type of vector once more quantization methods are supported
466501 byte [] quantizedVector = maybeQuantizeVector (segmentLevelQuantizationInfo );
467502 float [] transformedVector = maybeTransformVector (segmentLevelQuantizationInfo , spaceType );
503+ /*
504+ * If filters match all docs in this segment, then null should be passed as filterBitSet
505+ * so that it will not do a bitset look up in bottom search layer.
506+ */
507+ final BitSet annFilter = filterCardinality == context .reader ().maxDoc () ? null : filterIdsBitSet ;
468508
469509 KNNCounter .GRAPH_QUERY_REQUESTS .increment ();
470510 final TopDocs results = doANNSearch (
@@ -477,8 +517,8 @@ protected TopDocs approximateSearch(final LeafReaderContext context, final BitSe
477517 quantizedVector ,
478518 transformedVector ,
479519 modelId ,
480- filterIdsBitSet ,
481- cardinality ,
520+ annFilter ,
521+ filterCardinality ,
482522 k
483523 );
484524
0 commit comments