This is an automated email from the ASF dual-hosted git repository.
xiangfu0 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pinot.git
The following commit(s) were added to refs/heads/master by this push:
new e6787e17864 Enforce one null pre-filter contract across all
filter-aware vector readers (#19464)
e6787e17864 is described below
commit e6787e178645fd3c5a887e2aba7e0de8343cd524
Author: Xiang Fu <[email protected]>
AuthorDate: Fri Sep 4 16:37:58 2026 -0700
Enforce one null pre-filter contract across all filter-aware vector readers
(#19464)
FilterAwareVectorIndexReader requires a non-null pre-filter bitmap, but the
five implementations enforced that four
different ways: two threw NullPointerException with a contract message, one
threw IllegalArgumentException with a
different message, and two had no guard at all and dereferenced the null. A
caller that lost its filter therefore
got a different failure purely based on which backend indexed the column,
and in two cases an unmessaged
NullPointerException raised from inside Lucene's traversal.
- State the failure mode once on the SPI method, including that an empty
bitmap admits nothing.
- Guard IvfFlat and IvfPq, which had none, and align IvfOnDisk on the same
exception type and message.
- Short-circuit an empty bitmap in HNSW before building the query, whose
filter clause would otherwise walk every
doc in every leaf to assemble an accept set that admits nothing.
- Report supportsPreFilter() in HNSW and IvfOnDisk debug info instead of a
hardcoded literal the type system
already owns.
- Cover the null contract for every reader, and restore two tests dropped
from #19303 before it merged: the
mutable null-bitmap rejection and the IVF_PQ assertion that candidate
generation honors the pre-filter rather
than intersecting an unfiltered top-K afterwards.
---
.../readers/vector/HnswVectorIndexReader.java | 11 +++-
.../readers/vector/IvfFlatVectorIndexReader.java | 1 +
.../readers/vector/IvfOnDiskVectorIndexReader.java | 4 +-
.../readers/vector/IvfPqVectorIndexReader.java | 1 +
.../impl/vector/MutableVectorIndexTest.java | 14 +++++
.../index/creator/HnswVectorIndexCreatorTest.java | 33 ++++++++++
.../readers/vector/IvfFlatFilterAwareTest.java | 23 +++++++
.../readers/vector/IvfOnDiskFilterAwareTest.java | 23 +++++++
.../segment/index/vector/IvfPqVectorIndexTest.java | 70 ++++++++++++++++++++++
.../index/reader/FilterAwareVectorIndexReader.java | 5 +-
10 files changed, 181 insertions(+), 4 deletions(-)
diff --git
a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/index/readers/vector/HnswVectorIndexReader.java
b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/index/readers/vector/HnswVectorIndexReader.java
index 53f5744f9bb..5c731fe917d 100644
---
a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/index/readers/vector/HnswVectorIndexReader.java
+++
b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/index/readers/vector/HnswVectorIndexReader.java
@@ -18,6 +18,7 @@
*/
package org.apache.pinot.segment.local.segment.index.readers.vector;
+import com.google.common.base.Preconditions;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
@@ -195,6 +196,14 @@ public class HnswVectorIndexReader implements
FilterAwareVectorIndexReader, EfSe
@Override
public ImmutableRoaringBitmap getDocIds(float[] searchQuery, int topK,
ImmutableRoaringBitmap preFilterBitmap) {
+ // Without this the null reaches BaseFilterQuery's iterator and surfaces
as an unmessaged NullPointerException
+ // from inside Lucene's traversal, where the cause is far harder to read
than the contract it violated.
+ Preconditions.checkNotNull(preFilterBitmap, "Pre-filter bitmap must not be
null for filtered vector search");
+ if (preFilterBitmap.isEmpty()) {
+ // Nothing is admitted, so skip the Lucene search entirely -- its filter
clause would otherwise walk every doc
+ // in every leaf, translating ids and testing membership, to build an
accept set guaranteed to be empty.
+ return new MutableRoaringBitmap();
+ }
try {
Query filterQuery = new RoaringBitmapFilterQuery(preFilterBitmap,
_docIdTranslator);
return translateTopDocs(search(searchQuery, topK, filterQuery));
@@ -222,7 +231,7 @@ public class HnswVectorIndexReader implements
FilterAwareVectorIndexReader, EfSe
info.put("effectiveEfSearch", getEffectiveEfSearch());
info.put("effectiveHnswUseRelativeDistance",
getEffectiveUseRelativeDistance());
info.put("effectiveHnswUseBoundedQueue", getEffectiveUseBoundedQueue());
- info.put("supportsPreFilter", true);
+ info.put("supportsPreFilter", supportsPreFilter());
return info;
}
diff --git
a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/index/readers/vector/IvfFlatVectorIndexReader.java
b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/index/readers/vector/IvfFlatVectorIndexReader.java
index f2930d0fe0e..e61e419f121 100644
---
a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/index/readers/vector/IvfFlatVectorIndexReader.java
+++
b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/index/readers/vector/IvfFlatVectorIndexReader.java
@@ -250,6 +250,7 @@ public class IvfFlatVectorIndexReader
@Override
public ImmutableRoaringBitmap getDocIds(float[] searchQuery, int topK,
ImmutableRoaringBitmap preFilterBitmap) {
+ Preconditions.checkNotNull(preFilterBitmap, "Pre-filter bitmap must not be
null for filtered vector search");
Preconditions.checkArgument(searchQuery.length == _dimension,
"Query dimension mismatch: expected %s, got %s", _dimension,
searchQuery.length);
Preconditions.checkArgument(topK > 0, "topK must be positive, got: %s",
topK);
diff --git
a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/index/readers/vector/IvfOnDiskVectorIndexReader.java
b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/index/readers/vector/IvfOnDiskVectorIndexReader.java
index dda0ed153e0..faccfc80a96 100644
---
a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/index/readers/vector/IvfOnDiskVectorIndexReader.java
+++
b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/index/readers/vector/IvfOnDiskVectorIndexReader.java
@@ -220,7 +220,7 @@ public class IvfOnDiskVectorIndexReader
@Override
public ImmutableRoaringBitmap getDocIds(float[] searchQuery, int topK,
ImmutableRoaringBitmap preFilterBitmap) {
- Preconditions.checkArgument(preFilterBitmap != null, "preFilterBitmap must
not be null");
+ Preconditions.checkNotNull(preFilterBitmap, "Pre-filter bitmap must not be
null for filtered vector search");
if (preFilterBitmap.isEmpty()) {
return new MutableRoaringBitmap();
}
@@ -498,7 +498,7 @@ public class IvfOnDiskVectorIndexReader
info.put("totalSearches", _totalSearches.get());
info.put("filteredSearches", _filteredSearches.get());
info.put("unfilteredSearches", _unfilteredSearches.get());
- info.put("supportsPreFilter", true);
+ info.put("supportsPreFilter", supportsPreFilter());
info.put("storageMode", "pinotDataBuffer");
// Compute cache warmth estimate: fraction of centroids accessed at least
once
diff --git
a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/index/readers/vector/IvfPqVectorIndexReader.java
b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/index/readers/vector/IvfPqVectorIndexReader.java
index 376bc7f60bf..d43231e3390 100644
---
a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/index/readers/vector/IvfPqVectorIndexReader.java
+++
b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/index/readers/vector/IvfPqVectorIndexReader.java
@@ -219,6 +219,7 @@ public class IvfPqVectorIndexReader
@Override
public ImmutableRoaringBitmap getDocIds(float[] searchQuery, int topK,
ImmutableRoaringBitmap preFilterBitmap) {
+ Preconditions.checkNotNull(preFilterBitmap, "Pre-filter bitmap must not be
null for filtered vector search");
Preconditions.checkArgument(searchQuery.length == _dimension,
"Query dimension mismatch: expected %s, got %s", _dimension,
searchQuery.length);
Preconditions.checkArgument(topK > 0, "topK must be positive, got: %s",
topK);
diff --git
a/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/realtime/impl/vector/MutableVectorIndexTest.java
b/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/realtime/impl/vector/MutableVectorIndexTest.java
index f077be3aff0..b2814b4398a 100644
---
a/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/realtime/impl/vector/MutableVectorIndexTest.java
+++
b/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/realtime/impl/vector/MutableVectorIndexTest.java
@@ -597,6 +597,20 @@ public class MutableVectorIndexTest {
}
}
+ /// A null bitmap must not silently degrade into an unfiltered search, which
would return doc ids the query is
+ /// not allowed to see. The reader rejects it with its contract message
rather than throwing a bare
+ /// NullPointerException from deep inside Lucene.
+ @Test
+ public void testFilteredSearchRejectsNullBitmap() {
+ try (MutableVectorIndex index = createIndexWithoutCommits()) {
+ NullPointerException thrown =
Assert.expectThrows(NullPointerException.class,
+ () -> index.getDocIds(new float[]{1.0F, 0.0F, 0.0F, 0.0F, 0.0F}, 1,
(ImmutableRoaringBitmap) null));
+ Assert.assertNotNull(thrown.getMessage(), "The rejection must carry the
pre-filter contract message");
+ Assert.assertTrue(thrown.getMessage().contains("must not be null"),
+ "Expected the pre-filter contract message, got: " +
thrown.getMessage());
+ }
+ }
+
private static MutableVectorIndex createIndex() {
return createIndex("mutableVectorIndexTest_" + System.nanoTime(),
COLUMN_NAME);
}
diff --git
a/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/creator/HnswVectorIndexCreatorTest.java
b/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/creator/HnswVectorIndexCreatorTest.java
index 0c42ec0f9e6..fc86c747495 100644
---
a/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/creator/HnswVectorIndexCreatorTest.java
+++
b/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/creator/HnswVectorIndexCreatorTest.java
@@ -26,6 +26,7 @@ import org.apache.commons.io.FileUtils;
import
org.apache.pinot.segment.local.segment.creator.impl.vector.HnswVectorIndexCreator;
import
org.apache.pinot.segment.local.segment.index.readers.vector.HnswVectorIndexReader;
import org.apache.pinot.segment.spi.index.creator.VectorIndexConfig;
+import org.roaringbitmap.buffer.ImmutableRoaringBitmap;
import org.roaringbitmap.buffer.MutableRoaringBitmap;
import org.testng.Assert;
import org.testng.annotations.AfterMethod;
@@ -109,6 +110,36 @@ public class HnswVectorIndexCreatorTest {
}
}
+ /// A null bitmap must not fall through to an unfiltered search, which would
return doc ids outside the filter.
+ /// The reader rejects it with its contract message rather than a bare
NullPointerException from inside Lucene.
+ @Test
+ public void testFilteredReaderRejectsNullBitmap()
+ throws IOException {
+ try (HnswVectorIndexReader reader = new HnswVectorIndexReader("foo",
INDEX_DIR, 4, _config)) {
+ float[] queryVector = {5.0F, 42.0F, 54.33333F, 42.24F, 1001.045F};
+ NullPointerException thrown =
Assert.expectThrows(NullPointerException.class,
+ () -> reader.getDocIds(queryVector, 2, (ImmutableRoaringBitmap)
null));
+ Assert.assertNotNull(thrown.getMessage(), "The rejection must carry the
pre-filter contract message");
+ Assert.assertTrue(thrown.getMessage().contains("must not be null"),
+ "Expected the pre-filter contract message, got: " +
thrown.getMessage());
+ }
+ }
+
+ /// An empty filter admits nothing, so the search is skipped entirely.
Asserting only that the result is empty
+ /// would not discriminate -- Lucene returns no hits for a zero-match filter
anyway. Disabling the bounded queue
+ /// without an efSearch makes runtime-control validation throw when a query
is actually built, so reaching an
+ /// empty result here proves the short-circuit ran before query construction.
+ @Test
+ public void testFilteredReaderShortCircuitsEmptyBitmapBeforeBuildingQuery()
+ throws IOException {
+ try (HnswVectorIndexReader reader = new HnswVectorIndexReader("foo",
INDEX_DIR, 4, _config)) {
+ float[] queryVector = {5.0F, 42.0F, 54.33333F, 42.24F, 1001.045F};
+ reader.setUseBoundedQueue(false);
+ Assert.assertTrue(reader.getDocIds(queryVector, 2, new
MutableRoaringBitmap()).isEmpty(),
+ "An empty filter must short-circuit before query construction");
+ }
+ }
+
@Test
public void testEfSearchChangesRuntimeSearchBehavior()
throws IOException {
@@ -144,6 +175,8 @@ public class HnswVectorIndexCreatorTest {
Assert.assertEquals(debugInfo.get("effectiveEfSearch"), 6);
Assert.assertEquals(debugInfo.get("effectiveHnswUseRelativeDistance"),
Boolean.FALSE);
Assert.assertEquals(debugInfo.get("effectiveHnswUseBoundedQueue"),
Boolean.FALSE);
+ Assert.assertEquals(debugInfo.get("supportsPreFilter"),
reader.supportsPreFilter(),
+ "Debug info must report the reader's actual pre-filter capability,
not a hardcoded literal");
}
}
}
diff --git
a/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/readers/vector/IvfFlatFilterAwareTest.java
b/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/readers/vector/IvfFlatFilterAwareTest.java
index f4da031f14b..a6243cb01e1 100644
---
a/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/readers/vector/IvfFlatFilterAwareTest.java
+++
b/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/readers/vector/IvfFlatFilterAwareTest.java
@@ -95,6 +95,29 @@ public class IvfFlatFilterAwareTest {
}
}
+ /// Every filter-aware reader rejects a null bitmap with the same contract
failure, so a caller that loses its
+ /// filter never silently receives documents outside it. See
FilterAwareVectorIndexReader#getDocIds.
+ @Test
+ public void testPreFilterRejectsNullBitmap()
+ throws Exception {
+ int numVectors = 50;
+ int dimension = 4;
+ int nlist = 4;
+ float[][] vectors = generateVectors(numVectors, dimension, new
Random(TEST_SEED));
+ createIndex(vectors, dimension, nlist,
VectorIndexConfig.VectorDistanceFunction.EUCLIDEAN);
+
+ VectorIndexConfig config = createConfig(dimension, nlist,
VectorIndexConfig.VectorDistanceFunction.EUCLIDEAN);
+ try (IvfFlatVectorIndexReader reader = new
IvfFlatVectorIndexReader(COLUMN_NAME,
+ IvfCombinedBuffers.mapCombined(_tempDir, COLUMN_NAME, config,
"test-vector"), config)) {
+ NullPointerException thrown =
Assert.expectThrows(NullPointerException.class,
+ () -> reader.getDocIds(vectors[0], 5, (ImmutableRoaringBitmap)
null));
+ Assert.assertNotNull(thrown.getMessage(), "The rejection must carry the
pre-filter contract message");
+ Assert.assertTrue(thrown.getMessage().contains("must not be null"),
+ "Expected the pre-filter contract message, got: " +
thrown.getMessage());
+ }
+ }
+
+
@Test
public void testPreFilterWithEmptyBitmapReturnsEmpty()
throws Exception {
diff --git
a/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/readers/vector/IvfOnDiskFilterAwareTest.java
b/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/readers/vector/IvfOnDiskFilterAwareTest.java
index 42b782414f4..0b1b2676bc7 100644
---
a/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/readers/vector/IvfOnDiskFilterAwareTest.java
+++
b/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/readers/vector/IvfOnDiskFilterAwareTest.java
@@ -119,6 +119,29 @@ public class IvfOnDiskFilterAwareTest {
}
}
+ /// Every filter-aware reader rejects a null bitmap with the same contract
failure, so a caller that loses its
+ /// filter never silently receives documents outside it. See
FilterAwareVectorIndexReader#getDocIds.
+ @Test
+ public void testPreFilterRejectsNullBitmap()
+ throws Exception {
+ int numVectors = 24;
+ int dimension = 4;
+ int nlist = 4;
+ float[][] vectors = generateVectors(numVectors, dimension, new
Random(TEST_SEED));
+ createIvfFlatIndex(vectors, dimension, nlist,
VectorIndexConfig.VectorDistanceFunction.COSINE);
+
+ VectorIndexConfig readerConfig =
+ createReaderConfig(dimension, nlist,
VectorIndexConfig.VectorDistanceFunction.COSINE);
+ try (IvfOnDiskVectorIndexReader reader = new
IvfOnDiskVectorIndexReader(COLUMN_NAME,
+ IvfCombinedBuffers.mapCombined(_tempDir, COLUMN_NAME, readerConfig,
"test-vector"), readerConfig)) {
+ NullPointerException thrown =
Assert.expectThrows(NullPointerException.class,
+ () -> reader.getDocIds(vectors[0], 5, (ImmutableRoaringBitmap)
null));
+ Assert.assertNotNull(thrown.getMessage(), "The rejection must carry the
pre-filter contract message");
+ Assert.assertTrue(thrown.getMessage().contains("must not be null"),
+ "Expected the pre-filter contract message, got: " +
thrown.getMessage());
+ }
+ }
+
@Test
public void testPreFilterEmptyBitmapReturnsEmpty()
throws Exception {
diff --git
a/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/vector/IvfPqVectorIndexTest.java
b/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/vector/IvfPqVectorIndexTest.java
index e9538a2ec42..acce0261af4 100644
---
a/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/vector/IvfPqVectorIndexTest.java
+++
b/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/vector/IvfPqVectorIndexTest.java
@@ -100,6 +100,76 @@ public class IvfPqVectorIndexTest {
}
}
+ /// Every filter-aware reader rejects a null bitmap with the same contract
failure, so a caller that loses its
+ /// filter never silently receives documents outside it. See
FilterAwareVectorIndexReader#getDocIds.
+ @Test
+ public void testPreFilterRejectsNullBitmap()
+ throws Exception {
+ int dimension = 8;
+ int nlist = 4;
+ VectorIndexConfig config =
createConfig(VectorIndexConfig.VectorDistanceFunction.EUCLIDEAN, dimension,
nlist, 2,
+ 4, 64, TEST_SEED);
+ float[][] vectors = generateClusteredVectors(8, 20, dimension, 0.04f,
TEST_SEED);
+
+ try (IvfPqVectorIndexCreator creator = new
IvfPqVectorIndexCreator(COLUMN_NAME, _tempDir, config)) {
+ for (float[] vector : vectors) {
+ creator.add(vector);
+ }
+ creator.seal();
+ }
+
+ try (IvfPqVectorIndexReader reader = new
IvfPqVectorIndexReader(COLUMN_NAME,
+ IvfCombinedBuffers.mapCombined(_tempDir, COLUMN_NAME, config,
"test-vector"), config)) {
+ NullPointerException thrown =
Assert.expectThrows(NullPointerException.class,
+ () -> reader.getDocIds(vectors[3], 5, (ImmutableRoaringBitmap)
null));
+ Assert.assertNotNull(thrown.getMessage(), "The rejection must carry the
pre-filter contract message");
+ Assert.assertTrue(thrown.getMessage().contains("must not be null"),
+ "Expected the pre-filter contract message, got: " +
thrown.getMessage());
+ }
+ }
+
+ /// The engine relies on IVF_PQ's filter-aware search to drop disallowed
documents during candidate generation
+ /// rather than intersecting an unfiltered top-K afterwards. The allowed
documents here are deliberately outside
+ /// the unfiltered top-K, so an implementation that post-intersected would
return nothing and fail.
+ @Test
+ public void testPreFilterExcludesNearestDocsFromCandidateGeneration()
+ throws IOException {
+ int dimension = 8;
+ int nlist = 4;
+ VectorIndexConfig config =
createConfig(VectorIndexConfig.VectorDistanceFunction.EUCLIDEAN, dimension,
nlist, 2,
+ 4, 64, TEST_SEED);
+ float[][] vectors = generateClusteredVectors(8, 20, dimension, 0.04f,
TEST_SEED);
+
+ try (IvfPqVectorIndexCreator creator = new
IvfPqVectorIndexCreator(COLUMN_NAME, _tempDir, config)) {
+ for (float[] vector : vectors) {
+ creator.add(vector);
+ }
+ creator.seal();
+ }
+
+ try (IvfPqVectorIndexReader reader = new
IvfPqVectorIndexReader(COLUMN_NAME,
+ IvfCombinedBuffers.mapCombined(_tempDir, COLUMN_NAME, config,
"test-vector"), config)) {
+ Assert.assertTrue(reader.supportsPreFilter(),
+ "IVF_PQ must advertise filtered search, otherwise the engine falls
back to an exact scan");
+ reader.setNprobe(nlist);
+
+ int topK = 2;
+ MutableRoaringBitmap allowed = MutableRoaringBitmap.bitmapOf(40, 100);
+ MutableRoaringBitmap unfiltered = (MutableRoaringBitmap)
reader.getDocIds(vectors[3], topK);
+ MutableRoaringBitmap unfilteredAllowedOverlap = unfiltered.clone();
+ unfilteredAllowedOverlap.and(allowed);
+ Assert.assertTrue(unfilteredAllowedOverlap.isEmpty(),
+ "Sanity check: the allowed documents must sit outside the unfiltered
top-K for this test to prove"
+ + " anything, got unfiltered=" + unfiltered);
+
+ ImmutableRoaringBitmap matches = reader.getDocIds(vectors[3], topK,
allowed);
+ Assert.assertEquals(matches, allowed,
+ "Filtered candidate generation must return the farther allowed docs;
post-intersection would be empty");
+ Assert.assertTrue(reader.getDocIds(vectors[3], topK, new
MutableRoaringBitmap()).isEmpty(),
+ "An empty filter must yield no results");
+ }
+ }
+
@Test
public void testEmptyIndexIsReadable()
throws IOException {
diff --git
a/pinot-segment-spi/src/main/java/org/apache/pinot/segment/spi/index/reader/FilterAwareVectorIndexReader.java
b/pinot-segment-spi/src/main/java/org/apache/pinot/segment/spi/index/reader/FilterAwareVectorIndexReader.java
index f3283383280..ebd0b6ebcf6 100644
---
a/pinot-segment-spi/src/main/java/org/apache/pinot/segment/spi/index/reader/FilterAwareVectorIndexReader.java
+++
b/pinot-segment-spi/src/main/java/org/apache/pinot/segment/spi/index/reader/FilterAwareVectorIndexReader.java
@@ -38,8 +38,11 @@ public interface FilterAwareVectorIndexReader extends
VectorIndexReader {
/// @param vector the query vector
/// @param topK number of closest vectors to return
/// @param preFilterBitmap bitmap of document IDs to restrict the search to;
- /// must not be null (use [#getDocIds(float[], int)]
for unfiltered search)
+ /// must not be null (use [#getDocIds(float[], int)]
for unfiltered search).
+ /// An empty bitmap admits no documents and yields an
empty result.
/// @return bitmap of top-K closest vectors from the filtered document set
+ /// @throws NullPointerException if preFilterBitmap is null. Implementations
must reject null rather than
+ /// silently searching unfiltered, which would return documents
outside the caller's filter.
ImmutableRoaringBitmap getDocIds(float[] vector, int topK,
ImmutableRoaringBitmap preFilterBitmap);
/// Returns true if this reader supports efficient pre-filter search.
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]