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

magibney pushed a commit to branch branch_9_1
in repository https://gitbox.apache.org/repos/asf/solr.git


The following commit(s) were added to refs/heads/branch_9_1 by this push:
     new 8093e782eee SOLR-16585: Fix NPE in MatchAllDocs pagination (#1236)
8093e782eee is described below

commit 8093e782eeef212f2d978aaf79e2a8d0aacba6bb
Author: Michael Gibney <[email protected]>
AuthorDate: Wed Dec 21 09:57:11 2022 -0500

    SOLR-16585: Fix NPE in MatchAllDocs pagination (#1236)
    
    (cherry picked from commit ced26f7132a4162dd7eaa96de2c87712bd8525fa)
---
 solr/CHANGES.txt                                   |  2 +
 .../org/apache/solr/search/SolrIndexSearcher.java  | 20 +++++----
 .../collection1/conf/solrconfig-deeppaging.xml     |  2 +-
 .../apache/solr/search/TestMainQueryCaching.java   | 50 +++++++++++++++++++---
 4 files changed, 60 insertions(+), 14 deletions(-)

diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index bb24d8be9a7..13f18294b68 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -11,6 +11,8 @@ Bug Fixes
 
 * SOLR-16589: Large fields with large=true can be truncated when using unicode 
values (Kevin Risden)
 
+* SOLR-16585: Fixed NPE when paginating MatchAllDocs with non-zero start 
offset, like q=*:*&start=10 (Michael Gibney)
+
 ==================  9.1.0 ==================
 
 New Features
diff --git a/solr/core/src/java/org/apache/solr/search/SolrIndexSearcher.java 
b/solr/core/src/java/org/apache/solr/search/SolrIndexSearcher.java
index 219384d4ea6..de6302e6198 100644
--- a/solr/core/src/java/org/apache/solr/search/SolrIndexSearcher.java
+++ b/solr/core/src/java/org/apache/solr/search/SolrIndexSearcher.java
@@ -2240,15 +2240,19 @@ public class SolrIndexSearcher extends IndexSearcher 
implements Closeable, SolrI
 
   private DocList constantScoreDocList(int offset, int length, DocSet docs) {
     final int size = docs.size();
-    if (length == 0 || size <= offset) {
-      return new DocSlice(0, 0, new int[0], null, size, 0f, 
TotalHits.Relation.EQUAL_TO);
-    }
-    final DocIterator iter = docs.iterator();
-    for (int i = offset; i > 0; i--) {
-      iter.nextDoc(); // discard
-    }
-    final int returnSize = Math.min(length, size - offset);
+
+    // NOTE: it would be possible to special-case `length == 0 || size <= 
offset` here
+    // (returning a DocList backed by an empty array) -- but the cases that 
would practically
+    // benefit from doing so would be extremely unusual, and likely 
pathological:
+    //   1. length==0 in conjunction with offset>0 (why?)
+    //   2. specifying offset>size (paging beyond end of results)
+    // This would require special consideration in dealing with cache handling 
(and generation
+    // of the final DocList via `DocSlice.subset(int, int)`), and it's just 
not worth it.
+
+    final int returnSize = Math.min(offset + length, size);
     final int[] docIds = new int[returnSize];
+
+    final DocIterator iter = docs.iterator();
     for (int i = 0; i < returnSize; i++) {
       docIds[i] = iter.nextDoc();
     }
diff --git 
a/solr/core/src/test-files/solr/collection1/conf/solrconfig-deeppaging.xml 
b/solr/core/src/test-files/solr/collection1/conf/solrconfig-deeppaging.xml
index d4b501736fe..981b02f2b31 100644
--- a/solr/core/src/test-files/solr/collection1/conf/solrconfig-deeppaging.xml
+++ b/solr/core/src/test-files/solr/collection1/conf/solrconfig-deeppaging.xml
@@ -50,7 +50,7 @@
     <!-- no autowarming, it screws up our ability to sanity check cache stats 
in tests -->
     <filterCache size="50" initialSize="50" autowarmCount="0"/>
     <queryResultCache size="50" initialSize="50" autowarmCount="0"/>
-    <queryResultWindowSize>50</queryResultWindowSize>
+    
<queryResultWindowSize>${solr.test.queryResultWindowSize:50}</queryResultWindowSize>
     <queryResultMaxDocsCached>500</queryResultMaxDocsCached>
     <!-- randomized so we exercise cursors using various paths in 
SolrIndexSearcher -->
     
<useFilterForSortedQuery>${solr.test.useFilterForSortedQuery}</useFilterForSortedQuery>
diff --git 
a/solr/core/src/test/org/apache/solr/search/TestMainQueryCaching.java 
b/solr/core/src/test/org/apache/solr/search/TestMainQueryCaching.java
index 99b6a14f9d1..8a298be2cb1 100644
--- a/solr/core/src/test/org/apache/solr/search/TestMainQueryCaching.java
+++ b/solr/core/src/test/org/apache/solr/search/TestMainQueryCaching.java
@@ -19,6 +19,7 @@ package org.apache.solr.search;
 import static org.apache.solr.common.util.Utils.fromJSONString;
 
 import java.util.Map;
+import java.util.Random;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
@@ -40,6 +41,8 @@ public class TestMainQueryCaching extends SolrTestCaseJ4 {
   private static final int ALL_DOCS = MOST_DOCS + 1;
   private static final String TEST_UFFSQ_PROPNAME = 
"solr.test.useFilterForSortedQuery";
   static String RESTORE_UFFSQ_PROP;
+  private static final String TEST_QRC_WINDOW_SIZE_PROPNAME = 
"solr.test.queryResultWindowSize";
+  static String RESTORE_QRC_WINDOW_SIZE_PROP;
   static boolean USE_FILTER_FOR_SORTED_QUERY;
 
   @BeforeClass
@@ -47,6 +50,7 @@ public class TestMainQueryCaching extends SolrTestCaseJ4 {
     USE_FILTER_FOR_SORTED_QUERY = random().nextBoolean();
     RESTORE_UFFSQ_PROP =
         System.setProperty(TEST_UFFSQ_PROPNAME, 
Boolean.toString(USE_FILTER_FOR_SORTED_QUERY));
+    RESTORE_QRC_WINDOW_SIZE_PROP = 
System.setProperty(TEST_QRC_WINDOW_SIZE_PROPNAME, "0");
     initCore("solrconfig-deeppaging.xml", "schema-sorts.xml");
     createIndex();
   }
@@ -58,6 +62,11 @@ public class TestMainQueryCaching extends SolrTestCaseJ4 {
     } else {
       System.setProperty(TEST_UFFSQ_PROPNAME, RESTORE_UFFSQ_PROP);
     }
+    if (RESTORE_QRC_WINDOW_SIZE_PROP == null) {
+      System.clearProperty(TEST_QRC_WINDOW_SIZE_PROPNAME);
+    } else {
+      System.setProperty(TEST_QRC_WINDOW_SIZE_PROPNAME, 
RESTORE_QRC_WINDOW_SIZE_PROP);
+    }
   }
 
   public static void createIndex() {
@@ -79,14 +88,14 @@ public class TestMainQueryCaching extends SolrTestCaseJ4 {
     h.reload();
   }
 
-  private static long coreToInserts(SolrCore core) {
+  private static long coreToInserts(SolrCore core, String cacheName) {
     return (long)
         ((MetricsMap)
                 ((SolrMetricManager.GaugeWrapper<?>)
                         core.getCoreMetricManager()
                             .getRegistry()
                             .getMetrics()
-                            .get("CACHE.searcher.filterCache"))
+                            .get("CACHE.searcher.".concat(cacheName)))
                     .getGauge())
             .getValue()
             .get("inserts");
@@ -232,8 +241,36 @@ public class TestMainQueryCaching extends SolrTestCaseJ4 {
   @Test
   public void testMatchAllDocsPlain() throws Exception {
     // plain request with "score" sort should skip sort even if `rows` 
requested
-    String response = JQ(req("q", MATCH_ALL_DOCS_QUERY, "indent", "true"));
-    assertMetricCounts(response, true, 0, 0, 1);
+    Random r = random();
+    int[] counters = new int[2];
+    for (int i = 1; i < ALL_DOCS + 10; i++) {
+      // gradually expand the offset, otherwise we'd find that 
queryResultCache would intercept
+      // requests, avoiding exercising the behavior we're most interested in
+      String offset = Integer.toString(r.nextInt(i));
+      // keep the window small (again, to avoid queryResultCache intercepting 
requests)
+      String rows = Integer.toString(r.nextInt(2));
+      String response =
+          JQ(req("q", MATCH_ALL_DOCS_QUERY, "indent", "true", "start", offset, 
"rows", rows));
+      assertMetricCounts(response, counters);
+    }
+  }
+
+  private static void assertMetricCounts(String response, int[] 
expectCounters) {
+    Map<?, ?> res = (Map<?, ?>) fromJSONString(response);
+    Map<?, ?> body = (Map<?, ?>) (res.get("response"));
+    SolrCore core = h.getCore();
+    assertEquals("Bad matchAllDocs insert count", 1, 
coreToMatchAllDocsInsertCount(core));
+    assertEquals("Bad filterCache insert count", 0, coreToInserts(core, 
"filterCache"));
+    assertEquals("Bad full sort count", 0, coreToSortCount(core, "full"));
+    assertEquals("Should have exactly " + ALL_DOCS, ALL_DOCS, (long) 
(body.get("numFound")));
+    long queryCacheInsertCount = coreToInserts(core, "queryResultCache");
+    if (queryCacheInsertCount == expectCounters[0]) {
+      // should be a hit, so all insert/sort-count metrics remain unchanged.
+    } else {
+      assertEquals(++expectCounters[0], queryCacheInsertCount);
+      expectCounters[1]++;
+    }
+    assertEquals("Bad skip sort count", expectCounters[1], 
coreToSortCount(core, "skip"));
   }
 
   @Test
@@ -375,7 +412,10 @@ public class TestMainQueryCaching extends SolrTestCaseJ4 {
         "Bad matchAllDocs insert count",
         (matchAllDocs ? 1 : 0),
         coreToMatchAllDocsInsertCount(core));
-    assertEquals("Bad filterCache insert count", expectFilterCacheInsertCount, 
coreToInserts(core));
+    assertEquals(
+        "Bad filterCache insert count",
+        expectFilterCacheInsertCount,
+        coreToInserts(core, "filterCache"));
     assertEquals("Bad full sort count", expectFullSortCount, 
coreToSortCount(core, "full"));
     assertEquals("Bad skip sort count", expectSkipSortCount, 
coreToSortCount(core, "skip"));
     assertEquals(

Reply via email to