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

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


The following commit(s) were added to refs/heads/main by this push:
     new 751c858ba7a SOLR-17870 - UnsupportedOperationException Fix - Return 
Partial Results collations when query Limit exceeded in SpellCheckCollator  
(#3496)
751c858ba7a is described below

commit 751c858ba7af3ff3afb59f9323bd095dff19a088
Author: Puneet Sharma <[email protected]>
AuthorDate: Thu Sep 10 17:54:19 2026 +0530

    SOLR-17870 - UnsupportedOperationException Fix - Return Partial Results 
collations when query Limit exceeded in SpellCheckCollator  (#3496)
    
    Co-authored-by: Puneet Sharma <[email protected]>
    Co-authored-by: Claude <[email protected]>
---
 ...R-17870-partial-spellcheck-collator-results.yml |  10 ++
 .../apache/solr/spelling/SpellCheckCollator.java   |   2 +-
 .../solr/spelling/SpellCheckCollatorTest.java      | 108 +++++++++++++++++++++
 3 files changed, 119 insertions(+), 1 deletion(-)

diff --git 
a/changelog/unreleased/SOLR-17870-partial-spellcheck-collator-results.yml 
b/changelog/unreleased/SOLR-17870-partial-spellcheck-collator-results.yml
new file mode 100644
index 00000000000..72ba578c495
--- /dev/null
+++ b/changelog/unreleased/SOLR-17870-partial-spellcheck-collator-results.yml
@@ -0,0 +1,10 @@
+title: >
+  SpellCheckCollator now returns the partial (mutable) list of collations it 
had gathered so far,
+  instead of throwing an UnsupportedOperationException, when the query time 
limit is exceeded
+  while collating results.
+type: fixed
+authors:
+  - name: Puneet Sharma
+links:
+  - name: SOLR-17870
+    url: https://issues.apache.org/jira/browse/SOLR-17870
diff --git 
a/solr/core/src/java/org/apache/solr/spelling/SpellCheckCollator.java 
b/solr/core/src/java/org/apache/solr/spelling/SpellCheckCollator.java
index fd5f465d098..344088c83ca 100644
--- a/solr/core/src/java/org/apache/solr/spelling/SpellCheckCollator.java
+++ b/solr/core/src/java/org/apache/solr/spelling/SpellCheckCollator.java
@@ -96,7 +96,7 @@ public class SpellCheckCollator {
     while (tryNo < maxTries && collNo < maxCollations && 
possibilityIter.hasNext()) {
 
       if (queryLimits.maybeExitWithPartialResults("SpellCheck collator")) {
-        return List.of();
+        return collations;
       }
 
       PossibilityIterator.RankedSpellPossibility possibility = 
possibilityIter.next();
diff --git 
a/solr/core/src/test/org/apache/solr/spelling/SpellCheckCollatorTest.java 
b/solr/core/src/test/org/apache/solr/spelling/SpellCheckCollatorTest.java
index 678ff73d848..61ad93c1c30 100644
--- a/solr/core/src/test/org/apache/solr/spelling/SpellCheckCollatorTest.java
+++ b/solr/core/src/test/org/apache/solr/spelling/SpellCheckCollatorTest.java
@@ -16,6 +16,7 @@
  */
 package org.apache.solr.spelling;
 
+import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
@@ -31,12 +32,17 @@ import org.apache.solr.common.params.SpellingParams;
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.common.util.SimpleOrderedMap;
 import org.apache.solr.core.SolrCore;
+import org.apache.solr.handler.component.QueryComponent;
+import org.apache.solr.handler.component.ResponseBuilder;
 import org.apache.solr.handler.component.SearchComponent;
 import org.apache.solr.handler.component.SpellCheckComponent;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.request.SolrQueryRequestBase;
 import org.apache.solr.request.SolrRequestHandler;
+import org.apache.solr.request.SolrRequestInfo;
 import org.apache.solr.response.SolrQueryResponse;
+import org.apache.solr.search.QueryLimit;
+import org.apache.solr.util.TestInjection;
 import org.junit.BeforeClass;
 import org.junit.Test;
 
@@ -751,4 +757,106 @@ public class SpellCheckCollatorTest extends 
SolrTestCaseJ4 {
     List<?> collations = (List<?>) collationList.getAll("collation");
     assertEquals(1, collations.size());
   }
+
+  @Test
+  @SuppressWarnings("deprecation")
+  public void testCollationWithQueryLimitsSupportsPartialResults() throws 
Exception {
+    // Create a SpellingResult with multiple suggestions to test collation
+    SpellingResult result = new SpellingResult();
+
+    // Add tokens with multiple suggestions to generate many possible 
collations
+    Token token1 = new Token();
+    token1.copyBuffer("test".toCharArray(), 0, 4);
+    token1.setOffset(0, 4);
+    result.add(token1, "best", 1);
+    result.add(token1, "rest", 2);
+    result.add(token1, "nest", 3);
+    result.add(token1, "fest", 4);
+
+    Token token2 = new Token();
+    token2.copyBuffer("query".toCharArray(), 0, 5);
+    token2.setOffset(5, 10);
+    result.add(token2, "quarry", 1);
+    result.add(token2, "quiry", 2);
+    result.add(token2, "quary", 3);
+    result.add(token2, "querry", 4);
+
+    // Create a QueryLimit that will trigger during collation processing
+    final int[] callCount = {0};
+    TestInjection.queryTimeout =
+        new QueryLimit() {
+          @Override
+          public boolean shouldExit() {
+            callCount[0]++;
+            // Return true after a few calls to force early exit during 
collation
+            return callCount[0] > 3;
+          }
+
+          @Override
+          public Object currentValue() {
+            return callCount[0];
+          }
+        };
+
+    try {
+      SolrCore core = h.getCore();
+      ModifiableSolrParams params = new ModifiableSolrParams();
+      params.add(CommonParams.Q, "test query");
+      params.add(CommonParams.DF, "teststop");
+
+      SolrQueryRequest req = new SolrQueryRequestBase(core, params);
+      SolrQueryResponse response = new SolrQueryResponse();
+      response.addResponseHeader(new SimpleOrderedMap<>());
+
+      // Set up proper request context for QueryLimits
+      SolrRequestInfo.setRequestInfo(new SolrRequestInfo(req, response));
+
+      try {
+        // Create ResponseBuilder with QueryComponent for collation
+        List<SearchComponent> components = new ArrayList<>();
+        QueryComponent queryComponent = new QueryComponent();
+        components.add(queryComponent);
+
+        ResponseBuilder rb = new ResponseBuilder(req, response, components);
+
+        // Create SpellCheckCollator with settings for multiple collations
+        SpellCheckCollator collator = new SpellCheckCollator();
+        collator.setMaxCollationTries(0); // Disable query verification
+        collator.setMaxCollations(20); // High number to ensure partial results
+
+        // This should trigger the maybeExitWithPartialResults condition
+        List<SpellCheckCollation> collations = collator.collate(result, "test 
query", rb);
+
+        // Verify that we got partial results due to timeout
+        assertNotNull("Collations should not be null", collations);
+        assertTrue("Should have some collations before timeout", 
collations.size() > 0);
+        assertTrue("Should have partial results due to timeout", 
collations.size() < 20);
+
+        // Verify that the timeout was triggered
+        assertTrue("Timeout should have been triggered", callCount[0] > 3);
+
+        // Verify that the response is marked as partial
+        Object partialResults = 
response.getResponseHeader().get("partialResults");
+        assertTrue(
+            "Response should be marked as partial when query limits are 
exceeded",
+            partialResults != null && Boolean.TRUE.equals(partialResults));
+
+        // Most importantly: verify that the list is mutable (supports partial 
results)
+        try {
+          collations.add(new SpellCheckCollation());
+          // If we get here, the list is mutable - this is the key requirement
+        } catch (UnsupportedOperationException e) {
+          fail("Collations list should be mutable to support partial results");
+        }
+
+      } finally {
+        req.close();
+      }
+
+    } finally {
+      // Clean up
+      TestInjection.queryTimeout = null;
+      SolrRequestInfo.clearRequestInfo();
+    }
+  }
 }

Reply via email to