Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@
*/
public class QueryComponent extends SearchComponent {
public static final String COMPONENT_NAME = "query";
private static final String FIELD_EXISTS_QUERY_REQUIRES_PREFIX = "FieldExistsQuery requires";
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

@Override
Expand Down Expand Up @@ -435,6 +436,9 @@ public void process(ResponseBuilder rb) throws IOException {
}
} catch (SyntaxError e) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, e);
} catch (IllegalStateException e) {
maybeThrowBadRequestForFieldExistsQueryFailure(e);
throw e;
Comment on lines +439 to +441
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR changes error mapping for the grouped execution path (the groupingSpec != null branch), but the added tests only exercise the ungrouped doProcessUngroupedSearch path. Please add a test that triggers the grouped path and asserts the same BAD_REQUEST mapping, to prevent regressions specific to grouped execution.

Copilot uses AI. Check for mistakes.
}
return;
}
Expand Down Expand Up @@ -1835,6 +1839,9 @@ private void doProcessUngroupedSearch(ResponseBuilder rb, QueryCommand cmd) thro
result = searcher.search(cmd);
} catch (FuzzyTermsEnum.FuzzyTermsException e) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, e);
} catch (IllegalStateException e) {
maybeThrowBadRequestForFieldExistsQueryFailure(e);
throw e;
}
rb.setResult(result);

Expand Down Expand Up @@ -1885,4 +1892,14 @@ private static String generateQueryID(SolrQueryRequest req) {

return localQueryID;
}

private static void maybeThrowBadRequestForFieldExistsQueryFailure(IllegalStateException e) {
for (Throwable cause = e; cause != null; cause = cause.getCause()) {
if (cause instanceof IllegalStateException
&& cause.getMessage() != null
&& cause.getMessage().startsWith(FIELD_EXISTS_QUERY_REQUIRES_PREFIX)) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, cause.getMessage(), e);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.solr.handler.component;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import org.apache.solr.SolrTestCaseJ4;
import org.apache.solr.common.SolrException;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.response.SolrQueryResponse;
import org.apache.solr.search.QueryCommand;
import org.apache.solr.search.SolrIndexSearcher;
import org.junit.BeforeClass;
import org.junit.Test;
import org.mockito.Mockito;

public class QueryComponentFieldExistsQueryErrorHandlingTest extends SolrTestCaseJ4 {

@BeforeClass
public static void beforeClass() {
assumeWorkingMockito();
}

@Test
public void mapsFieldExistsQueryIllegalStateExceptionToBadRequest() throws Exception {
SolrIndexSearcher searcher = Mockito.mock(SolrIndexSearcher.class);
IllegalStateException fieldExistsError =
new IllegalStateException("FieldExistsQuery requires norms, docValues, or vectors");
Mockito.when(searcher.search(Mockito.any(QueryCommand.class))).thenThrow(fieldExistsError);

InvocationTargetException thrown =
expectThrows(
InvocationTargetException.class,
() -> invokeDoProcessUngroupedSearch(newResponseBuilder(searcher), new QueryCommand()));

assertTrue(thrown.getCause() instanceof SolrException);
SolrException solrException = (SolrException) thrown.getCause();
assertEquals(SolrException.ErrorCode.BAD_REQUEST.code, solrException.code());
assertTrue(solrException.getMessage().startsWith("FieldExistsQuery requires"));
}

@Test
public void mapsNestedFieldExistsQueryIllegalStateExceptionToBadRequest() throws Exception {
SolrIndexSearcher searcher = Mockito.mock(SolrIndexSearcher.class);
IllegalStateException rootCause =
new IllegalStateException("FieldExistsQuery requires norms, docValues, or vectors");
IllegalStateException wrapped = new IllegalStateException("outer", rootCause);
Mockito.when(searcher.search(Mockito.any(QueryCommand.class))).thenThrow(wrapped);

InvocationTargetException thrown =
expectThrows(
InvocationTargetException.class,
() -> invokeDoProcessUngroupedSearch(newResponseBuilder(searcher), new QueryCommand()));

assertTrue(thrown.getCause() instanceof SolrException);
SolrException solrException = (SolrException) thrown.getCause();
assertEquals(SolrException.ErrorCode.BAD_REQUEST.code, solrException.code());
assertTrue(solrException.getMessage().startsWith("FieldExistsQuery requires"));
}

@Test
public void leavesOtherIllegalStateExceptionsUnchanged() throws Exception {
SolrIndexSearcher searcher = Mockito.mock(SolrIndexSearcher.class);
IllegalStateException nonFieldExistsError =
new IllegalStateException("not a field exists query");
Mockito.when(searcher.search(Mockito.any(QueryCommand.class))).thenThrow(nonFieldExistsError);

InvocationTargetException thrown =
expectThrows(
InvocationTargetException.class,
() -> invokeDoProcessUngroupedSearch(newResponseBuilder(searcher), new QueryCommand()));

assertSame(nonFieldExistsError, thrown.getCause());
}

private static void invokeDoProcessUngroupedSearch(ResponseBuilder rb, QueryCommand cmd)
throws Exception {
Method method =
QueryComponent.class.getDeclaredMethod(
"doProcessUngroupedSearch", ResponseBuilder.class, QueryCommand.class);
method.setAccessible(true);
method.invoke(new QueryComponent(), rb, cmd);
Comment on lines +93 to +97
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test uses reflection with setAccessible(true) to reach a private method, but it isn’t annotated with @SuppressForbidden. The build enables forbidden-apis’ jdk-reflection signatures (see gradle/validation/forbidden-apis.gradle), and similar reflection-based tests in this package suppress it (e.g., AsyncTrackerSemaphoreLeakTest.java:236). Add an appropriate @SuppressForbidden annotation (method or class) with a brief reason to avoid forbidden-apis failures.

Copilot uses AI. Check for mistakes.
}

private static ResponseBuilder newResponseBuilder(SolrIndexSearcher searcher) {
SolrQueryRequest req = Mockito.mock(SolrQueryRequest.class);
SolrQueryResponse rsp = Mockito.mock(SolrQueryResponse.class);
Mockito.when(req.getSearcher()).thenReturn(searcher);
return new ResponseBuilder(req, rsp, new ArrayList<>(0));
}
}
Loading