diff --git a/lucene/join/src/java/org/apache/lucene/search/join/ToParentBlockJoinByteKnnVectorQuery.java b/lucene/join/src/java/org/apache/lucene/search/join/ToParentBlockJoinByteKnnVectorQuery.java index 13fb0749b116..4c13ab8e007b 100644 --- a/lucene/join/src/java/org/apache/lucene/search/join/ToParentBlockJoinByteKnnVectorQuery.java +++ b/lucene/join/src/java/org/apache/lucene/search/join/ToParentBlockJoinByteKnnVectorQuery.java @@ -75,6 +75,9 @@ protected TopDocs exactSearch(LeafReaderContext context, DocIdSetIterator accept return null; } BitSet parentBitSet = parentsFilter.getBitSet(context); + if (parentBitSet == null) { + return NO_RESULTS; + } ParentBlockJoinByteVectorScorer vectorScorer = new ParentBlockJoinByteVectorScorer( context.reader().getByteVectorValues(field), @@ -112,6 +115,9 @@ protected TopDocs exactSearch(LeafReaderContext context, DocIdSetIterator accept protected TopDocs approximateSearch(LeafReaderContext context, Bits acceptDocs, int visitedLimit) throws IOException { BitSet parentBitSet = parentsFilter.getBitSet(context); + if (parentBitSet == null) { + return NO_RESULTS; + } KnnCollector collector = new ToParentJoinKnnCollector(k, visitedLimit, parentBitSet); context.reader().searchNearestVectors(field, query, collector, acceptDocs); return collector.topDocs(); diff --git a/lucene/join/src/java/org/apache/lucene/search/join/ToParentBlockJoinFloatKnnVectorQuery.java b/lucene/join/src/java/org/apache/lucene/search/join/ToParentBlockJoinFloatKnnVectorQuery.java index 2327b3d178a7..bde0b62733dc 100644 --- a/lucene/join/src/java/org/apache/lucene/search/join/ToParentBlockJoinFloatKnnVectorQuery.java +++ b/lucene/join/src/java/org/apache/lucene/search/join/ToParentBlockJoinFloatKnnVectorQuery.java @@ -77,6 +77,9 @@ protected TopDocs exactSearch(LeafReaderContext context, DocIdSetIterator accept return null; } BitSet parentBitSet = parentsFilter.getBitSet(context); + if (parentBitSet == null) { + return NO_RESULTS; + } ParentBlockJoinFloatVectorScorer vectorScorer = new ParentBlockJoinFloatVectorScorer( context.reader().getFloatVectorValues(field), @@ -114,6 +117,9 @@ protected TopDocs exactSearch(LeafReaderContext context, DocIdSetIterator accept protected TopDocs approximateSearch(LeafReaderContext context, Bits acceptDocs, int visitedLimit) throws IOException { BitSet parentBitSet = parentsFilter.getBitSet(context); + if (parentBitSet == null) { + return NO_RESULTS; + } KnnCollector collector = new ToParentJoinKnnCollector(k, visitedLimit, parentBitSet); context.reader().searchNearestVectors(field, query, collector, acceptDocs); return collector.topDocs(); diff --git a/lucene/join/src/test/org/apache/lucene/search/join/ParentBlockJoinKnnVectorQueryTestCase.java b/lucene/join/src/test/org/apache/lucene/search/join/ParentBlockJoinKnnVectorQueryTestCase.java index 6402fe133e62..5868369aa1f1 100644 --- a/lucene/join/src/test/org/apache/lucene/search/join/ParentBlockJoinKnnVectorQueryTestCase.java +++ b/lucene/join/src/test/org/apache/lucene/search/join/ParentBlockJoinKnnVectorQueryTestCase.java @@ -88,6 +88,78 @@ public void testEmptyIndex() throws IOException { } } + public void testIndexWithNoVectorsNorParents() throws IOException { + try (Directory d = newDirectory()) { + try (IndexWriter w = new IndexWriter(d, new IndexWriterConfig())) { + // Add some documents without a vector + for (int i = 0; i < 5; i++) { + Document doc = new Document(); + doc.add(new StringField("other", "value", Field.Store.NO)); + w.addDocument(doc); + } + } + try (IndexReader reader = DirectoryReader.open(d)) { + IndexSearcher searcher = new IndexSearcher(reader); + // Create parent filter directly, tests use "check" to verify parentIds exist. Production + // may not + // verify we handle it gracefully + BitSetProducer parentFilter = + new QueryBitSetProducer(new TermQuery(new Term("docType", "_parent"))); + Query query = getParentJoinKnnQuery("field", new float[] {2, 2}, null, 3, parentFilter); + TopDocs topDocs = searcher.search(query, 3); + assertEquals(0, topDocs.totalHits.value); + assertEquals(0, topDocs.scoreDocs.length); + + // Test with match_all filter and large k to test exact search + query = + getParentJoinKnnQuery( + "field", new float[] {2, 2}, new MatchAllDocsQuery(), 10, parentFilter); + topDocs = searcher.search(query, 3); + assertEquals(0, topDocs.totalHits.value); + assertEquals(0, topDocs.scoreDocs.length); + } + } + } + + public void testIndexWithNoParents() throws IOException { + try (Directory d = newDirectory()) { + try (IndexWriter w = new IndexWriter(d, new IndexWriterConfig())) { + for (int i = 0; i < 3; ++i) { + Document doc = new Document(); + doc.add(getKnnVectorField("field", new float[] {2, 2})); + doc.add(newStringField("id", Integer.toString(i), Field.Store.YES)); + w.addDocument(doc); + } + // Add some documents without a vector + for (int i = 0; i < 5; i++) { + Document doc = new Document(); + doc.add(new StringField("other", "value", Field.Store.NO)); + w.addDocument(doc); + } + } + try (IndexReader reader = DirectoryReader.open(d)) { + IndexSearcher searcher = new IndexSearcher(reader); + // Create parent filter directly, tests use "check" to verify parentIds exist. Production + // may not + // verify we handle it gracefully + BitSetProducer parentFilter = + new QueryBitSetProducer(new TermQuery(new Term("docType", "_parent"))); + Query query = getParentJoinKnnQuery("field", new float[] {2, 2}, null, 3, parentFilter); + TopDocs topDocs = searcher.search(query, 3); + assertEquals(0, topDocs.totalHits.value); + assertEquals(0, topDocs.scoreDocs.length); + + // Test with match_all filter and large k to test exact search + query = + getParentJoinKnnQuery( + "field", new float[] {2, 2}, new MatchAllDocsQuery(), 10, parentFilter); + topDocs = searcher.search(query, 3); + assertEquals(0, topDocs.totalHits.value); + assertEquals(0, topDocs.scoreDocs.length); + } + } + } + public void testFilterWithNoVectorMatches() throws IOException { try (Directory indexStore = getIndexStore("field", new float[] {0, 1}, new float[] {1, 2}, new float[] {0, 0});