This is an automated email from the ASF dual-hosted git repository. dsmiley pushed a commit to branch branch_10x in repository https://gitbox.apache.org/repos/asf/solr.git
commit 1fae678fc992ac806d1d08a13040c135bd4d3dd0 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]> (cherry picked from commit 751c858ba7af3ff3afb59f9323bd095dff19a088) --- ...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(); + } + } }
