You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by be...@apache.org on 2025/10/14 11:49:18 UTC

(lucene) branch branch_10x updated: Fix potential NPE for KnnVectorQuery (#15334)

This is an automated email from the ASF dual-hosted git repository.

benwtrent pushed a commit to branch branch_10x
in repository https://gitbox.apache.org/repos/asf/lucene.git


The following commit(s) were added to refs/heads/branch_10x by this push:
     new a8c1455fdfe Fix potential NPE for KnnVectorQuery (#15334)
a8c1455fdfe is described below

commit a8c1455fdfe62c0e45082788ef860f7dc7f7eef2
Author: Benjamin Trent <be...@gmail.com>
AuthorDate: Tue Oct 14 07:29:03 2025 -0400

    Fix potential NPE for KnnVectorQuery (#15334)
    
    * Fix potential NPE for KnnVectorQuery
    
    * adding test
    
    * removing unused test code
---
 lucene/CHANGES.txt                                         |  3 +++
 .../org/apache/lucene/search/AbstractKnnVectorQuery.java   | 14 +++++++++++---
 .../org/apache/lucene/index/memory/TestMemoryIndex.java    | 12 ++++++++++++
 3 files changed, 26 insertions(+), 3 deletions(-)

diff --git a/lucene/CHANGES.txt b/lucene/CHANGES.txt
index fe663fc2baf..3e4cb16919d 100644
--- a/lucene/CHANGES.txt
+++ b/lucene/CHANGES.txt
@@ -86,6 +86,9 @@ Bug Fixes
 
 * GITHUB#15319: Fix potential hang in initialisation of TermsEnum and BaseTermsEnum (Chris Hegarty)
 
+* GITHUB#15334: Fix NPE for MemoryIndex when using nearest neighbor search. MemoryIndex does not support
+  nearest neighbor search, but it should not throw an NPE (Ben Trent)
+
 Other
 ---------------------
 * GITHUB#15237: Fix SmartChinese to only deserialize dictionary data from classpath with a native array
diff --git a/lucene/core/src/java/org/apache/lucene/search/AbstractKnnVectorQuery.java b/lucene/core/src/java/org/apache/lucene/search/AbstractKnnVectorQuery.java
index 601d7ad5ff1..fbe18afce6d 100644
--- a/lucene/core/src/java/org/apache/lucene/search/AbstractKnnVectorQuery.java
+++ b/lucene/core/src/java/org/apache/lucene/search/AbstractKnnVectorQuery.java
@@ -209,8 +209,15 @@ abstract class AbstractKnnVectorQuery extends Query {
     final int cost = acceptDocs.cost();
     QueryTimeout queryTimeout = timeLimitingKnnCollectorManager.getQueryTimeout();
 
-    float leafProportion = ctx.reader().maxDoc() / (float) ctx.parent.reader().maxDoc();
-    int perLeafTopK = perLeafTopKCalculation(k, leafProportion);
+    final int perLeafTopK;
+    // ctx.parent could be null if this is a MemoryIndex
+    if (ctx.parent != null) {
+      float leafProportion = ctx.reader().maxDoc() / (float) ctx.parent.reader().maxDoc();
+      perLeafTopK = perLeafTopKCalculation(k, leafProportion);
+      // We don't have a good way to estimate perLeafTopK here, so just do approximate search
+    } else {
+      perLeafTopK = k;
+    }
 
     if (cost <= perLeafTopK) {
       // If there are <= perLeafTopK possible matches, short-circuit and perform exact search, since
@@ -253,7 +260,8 @@ abstract class AbstractKnnVectorQuery extends Query {
         int visitedLimit, KnnSearchStrategy searchStrategy, LeafReaderContext context)
         throws IOException {
       // The delegate supports optimistic collection
-      if (delegate.isOptimistic()) {
+      // ctx.parent could be null if this is a MemoryIndex
+      if (delegate.isOptimistic() && context.parent != null) {
         @SuppressWarnings("resource")
         float leafProportion = context.reader().maxDoc() / (float) context.parent.reader().maxDoc();
         int perLeafTopK = perLeafTopKCalculation(k, leafProportion);
diff --git a/lucene/memory/src/test/org/apache/lucene/index/memory/TestMemoryIndex.java b/lucene/memory/src/test/org/apache/lucene/index/memory/TestMemoryIndex.java
index 7c592868912..c8562f734e1 100644
--- a/lucene/memory/src/test/org/apache/lucene/index/memory/TestMemoryIndex.java
+++ b/lucene/memory/src/test/org/apache/lucene/index/memory/TestMemoryIndex.java
@@ -77,11 +77,14 @@ import org.apache.lucene.index.VectorSimilarityFunction;
 import org.apache.lucene.search.CollectionStatistics;
 import org.apache.lucene.search.DocIdSetIterator;
 import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.KnnByteVectorQuery;
+import org.apache.lucene.search.KnnFloatVectorQuery;
 import org.apache.lucene.search.MatchAllDocsQuery;
 import org.apache.lucene.search.PhraseQuery;
 import org.apache.lucene.search.Query;
 import org.apache.lucene.search.TermQuery;
 import org.apache.lucene.search.TermStatistics;
+import org.apache.lucene.search.TopDocs;
 import org.apache.lucene.search.VectorScorer;
 import org.apache.lucene.search.similarities.BM25Similarity;
 import org.apache.lucene.search.similarities.ClassicSimilarity;
@@ -793,6 +796,11 @@ public class TestMemoryIndex extends LuceneTestCase {
             .get(0)
             .reader()
             .getByteVectorValues("knnByteVectorValue"));
+    TopDocs docs =
+        mi.createSearcher()
+            .search(new KnnFloatVectorQuery("knnFloatA", new float[] {1.0f, 1.0f}, 1), 10);
+    // we don't really do knn search right now for MemoryIndex
+    assertEquals(0, docs.totalHits.value());
   }
 
   public void testKnnByteVectorOnlyOneVectorAllowed() throws IOException {
@@ -840,6 +848,10 @@ public class TestMemoryIndex extends LuceneTestCase {
             .get(0)
             .reader()
             .getFloatVectorValues("knnFloatVectorValue"));
+    TopDocs docs =
+        mi.createSearcher().search(new KnnByteVectorQuery("knnByteA", new byte[] {1, 1}, 1), 10);
+    // we don't really do knn search right now for MemoryIndex
+    assertEquals(0, docs.totalHits.value());
   }
 
   private static void assertFloatVectorValue(MemoryIndex mi, String fieldName, float[] expected)