Skip to content

Commit 36d8f54

Browse files
SOLR-18093: Add 'queries' support to JsonQueryRequest (#4276)
Add top-level "queries" support to JsonQueryRequest in SolrJ. And hardened a related flaky test. SearchHandler: give a clear error if referring to a non-existent SearchComponent Co-authored-by: David Smiley <dsmiley@apache.org>
1 parent eaea72f commit 36d8f54

11 files changed

Lines changed: 224 additions & 19 deletions

File tree

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# See https://github.com/apache/solr/blob/main/dev-docs/changelog.adoc
2+
title: Add top-level "queries" support to JsonQueryRequest in SolrJ
3+
type: added
4+
authors:
5+
- name: Sonu Sharma
6+
nick: ercsonusharma
7+
links:
8+
- name: SOLR-18093
9+
url: https://issues.apache.org/jira/browse/SOLR-18093

solr/core/src/java/org/apache/solr/handler/component/SearchHandler.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,10 @@ private void initComponents() {
274274
DebugComponent dbgCmp = null;
275275
for (String c : list) {
276276
SearchComponent comp = core.getSearchComponent(c);
277+
if (comp == null) {
278+
throw new SolrException(
279+
SolrException.ErrorCode.SERVER_ERROR, "Unknown search component: " + c);
280+
}
277281
if (comp instanceof DebugComponent && makeDebugLast == true) {
278282
dbgCmp = (DebugComponent) comp;
279283
} else {

solr/core/src/test-files/solr/collection1/conf/solrconfig.xml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
<double name="maxWriteMBPerSecDefault">1000000</double>
4343
<double name="maxWriteMBPerSecFlush">2000000</double>
4444
<double name="maxWriteMBPerSecMerge">3000000</double>
45-
<double name="maxWriteMBPerSecRead">4000000</double>
45+
<double name="maxWriteMBPerSecRead">4000000</double>
4646
</directoryFactory>
4747

4848
<schemaFactory class="ClassicIndexSchemaFactory"/>
@@ -380,6 +380,9 @@
380380
</arr>
381381
</requestHandler>
382382

383+
<searchComponent class="solr.CombinedQueryComponent" name="combined_query">
384+
</searchComponent>
385+
383386
<requestHandler name="/mltrh" class="org.apache.solr.handler.component.SearchHandler">
384387

385388
</requestHandler>

solr/core/src/test/org/apache/solr/handler/component/CombinedQuerySolrCloudTest.java

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ private synchronized void prepareIndexDocsColocated() throws Exception {
7373
del("*:*");
7474
List<SolrInputDocument> docs = getSolrDocuments();
7575
for (SolrInputDocument doc : docs) {
76-
doc.setField("id", doc.getFieldValue("mod3_idv") + "!" + doc.getField("id").getValue());
76+
doc.setField("id", "CO!" + doc.getField("id").getValue());
7777
}
7878
for (SolrInputDocument doc : docs) {
7979
indexDoc(doc);
@@ -284,16 +284,16 @@ public void testQueriesWithFacetAndHighlightsCollapse() throws Exception {
284284
"queries": {
285285
"lexical1": {
286286
"lucene": {
287-
"query": "id:(2!2^2 OR 0!3^1 OR 0!6^2 OR 2!5^1)"
287+
"query": "id:(CO!2^3 OR CO!3^1 OR CO!6^2 OR CO!5^1)"
288288
}
289289
},
290290
"lexical2": {
291291
"lucene": {
292-
"query": "id:(2!8^1 OR 2!5^2 OR 1!7^3 OR 1!10^2)"
292+
"query": "id:(CO!8^1 OR CO!5^2 OR CO!7^3 OR CO!10^2)"
293293
}
294294
}
295295
},
296-
"limit": 1,
296+
"limit": 3,
297297
"fields": [
298298
"id",
299299
"score",
@@ -304,7 +304,7 @@ public void testQueriesWithFacetAndHighlightsCollapse() throws Exception {
304304
"facet": true,
305305
"facet.field": "id",
306306
"fq": [
307-
"{!collapse field=mod3_idv sort='id asc'}"
307+
"{!collapse field=mod3_idv sort='id asc, score desc'}"
308308
],
309309
"expand": true,
310310
"expand.q": "*:*",
@@ -319,17 +319,17 @@ public void testQueriesWithFacetAndHighlightsCollapse() throws Exception {
319319
}""";
320320
handle.put("expanded", UNORDERED);
321321
QueryResponse rsp = query(CommonParams.JSON, jsonQuery, CommonParams.QT, "/search");
322-
assertEquals(1, rsp.getResults().size());
323-
assertFieldValues(rsp.getResults(), id, "2!2");
322+
assertEquals(3, rsp.getResults().size());
323+
assertFieldValues(rsp.getResults(), id, "CO!2", "CO!10", "CO!3");
324324
assertEquals("id", rsp.getFacetFields().getFirst().getName());
325325
assertEquals(
326-
"[0!3 (1), 1!10 (1), 2!2 (1), 0!6 (0), 0!9 (0), 1!1 (0), 1!4 (0), 1!7 (0), 2!5 (0), 2!8 (0)]",
326+
"[CO!10 (1), CO!2 (1), CO!3 (1), CO!1 (0), CO!4 (0), CO!5 (0), CO!6 (0), CO!7 (0), CO!8 (0), CO!9 (0)]",
327327
rsp.getFacetFields().getFirst().getValues().toString());
328-
assertEquals(1, rsp.getHighlighting().size());
328+
assertEquals(3, rsp.getHighlighting().size());
329329
assertEquals(
330330
"title <em>test</em> for <em>doc</em> 2",
331-
rsp.getHighlighting().get("2!2").get("title").getFirst());
332-
assertEquals(1, rsp.getExpandedResults().size());
331+
rsp.getHighlighting().get("CO!2").get("title").getFirst());
332+
assertEquals(3, rsp.getExpandedResults().size());
333333
}
334334

335335
/** To test that we can force distrib */

solr/server/solr/configsets/sample_techproducts_configs/conf/solrconfig.xml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -640,7 +640,14 @@
640640
</lst>
641641
</requestHandler>
642642

643-
<initParams path="/update/**,/query,/select,/tvrh,/elevate,/spell,update">
643+
<requestHandler name="/rrf" class="solr.CombinedQuerySearchHandler">
644+
</requestHandler>
645+
646+
<searchComponent class="solr.CombinedQueryComponent" name="combined_query">
647+
<int name="maxCombinerQueries">2</int>
648+
</searchComponent>
649+
650+
<initParams path="/update/**,/query,/search,/select,/tvrh,/elevate,/spell,update">
644651
<lst name="defaults">
645652
<str name="df">text</str>
646653
</lst>

solr/solr-ref-guide/modules/query-guide/examples/JsonRequestApiTest.java

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,76 @@ public void testSimpleJsonQuery() throws Exception {
8383
assertResponseFoundNumDocs(queryResponse, expectedResults);
8484
}
8585

86+
/**
87+
* Test json query behaviour in case of multiple query to be executed using Combined Query.
88+
*
89+
* @throws Exception the exception
90+
*/
91+
@Test
92+
public void testSimpleJsonQueryWithQueriesParams() throws Exception {
93+
SolrClient solrClient = cluster.getSolrClient();
94+
final int expectedResults = 2;
95+
final Map<String, Object> queriesMap = new HashMap<>();
96+
queriesMap.put(
97+
"query1",
98+
Map.of(
99+
"lucene",
100+
Map.of(
101+
"query", "apache",
102+
"df", "manu")));
103+
queriesMap.put(
104+
"query2",
105+
Map.of(
106+
"edismax",
107+
Map.of(
108+
"query", "solr",
109+
"df", "name")));
110+
final JsonQueryRequest query =
111+
new JsonQueryRequest()
112+
.setQueries(queriesMap)
113+
.withFilter("inStock:true")
114+
.withParam("fl", "name")
115+
.withParam("combiner", "true")
116+
.withParam("combiner.query", List.of("query1", "query2"));
117+
query.setPath("/rrf");
118+
QueryResponse queryResponse = query.process(solrClient, COLLECTION_NAME);
119+
assertResponseFoundNumDocs(queryResponse, expectedResults);
120+
}
121+
122+
/**
123+
* Test json query behaviour in case of multiple query to be executed using Additional Queries.
124+
*
125+
* @throws Exception the exception
126+
*/
127+
@Test
128+
public void testAdditionalJsonQueries() throws Exception {
129+
SolrClient solrClient = cluster.getSolrClient();
130+
final int expectedResults = 12;
131+
// tag::solrj-json-query-with-queries[]
132+
final Map<String, Object> queriesMap = new HashMap<>();
133+
queriesMap.put(
134+
"electronic",
135+
Map.of(
136+
"field",
137+
Map.of(
138+
"query", "electronics",
139+
"f", "cat")));
140+
queriesMap.put(
141+
"manufacturers",
142+
List.of(
143+
"manu: apple",
144+
Map.of(
145+
"field",
146+
Map.of(
147+
"query", "belkin",
148+
"f", "manu"))));
149+
final JsonQueryRequest query =
150+
new JsonQueryRequest().setQueries(queriesMap).setQuery(Map.of("param", "electronic"));
151+
QueryResponse queryResponse = query.process(solrClient, COLLECTION_NAME);
152+
// end::solrj-json-query-with-queries[]
153+
assertEquals(expectedResults, queryResponse.getResults().getNumFound());
154+
}
155+
86156
@Test
87157
public void testJsonQueryWithJsonQueryParamOverrides() throws Exception {
88158
SolrClient solrClient = cluster.getSolrClient();

solr/solr-ref-guide/modules/query-guide/pages/json-combined-query-dsl.adoc

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -78,14 +78,21 @@ The query structure is similar to JSON Query DSL except for how multiple queries
7878
7979
=== Example
8080
81-
Below is a sample JSON query payload:
81+
Below is an example with sample JSON query payload:
8282
83-
```
83+
[tabs#json-query-with-queries]
84+
======
85+
curl::
86+
+
87+
====
88+
[source,bash]
89+
----
90+
curl -X POST http://localhost:8983/solr/techproducts/query -d '
8491
{
8592
"queries": {
8693
"lexical1": {
8794
"lucene": {
88-
"query": "title:sales"
95+
"query": "name:apache"
8996
}
9097
},
9198
"vector": {
@@ -97,15 +104,37 @@ Below is a sample JSON query payload:
97104
}
98105
},
99106
"limit": 5,
100-
"fields": ["id", "score", "title"],
107+
"fields": ["id", "score", "name"],
101108
"params": {
102109
"combiner": true,
103110
"combiner.query": ["lexical1", "vector"],
104111
"combiner.algorithm": "rrf",
105112
"combiner.rrf.k": "15"
106113
}
107-
}
108-
```
114+
}'
115+
----
116+
====
117+
SolrJ::
118+
+
119+
====
120+
[source,java,indent=0]
121+
----
122+
final Map<String, Object> queriesMap = new HashMap<>();
123+
queriesMap.put("lexical1", Map.of("lucene", Map.of("query", "apache", "df", "name")));
124+
queriesMap.put("vector", Map.of("knn", Map.of("query", [0.1,-0.34,0.89,0.02], "f", "vector", "topK", 5)));
125+
final JsonQueryRequest query =
126+
new JsonQueryRequest()
127+
.setQueries(queriesMap)
128+
.withParam("id", "score", "name")
129+
.setLimit(5)
130+
.withParam("combiner", "true")
131+
.withParam("combiner.query", List.of("lexical1", "vector"))
132+
.withParam("combiner.algorithm", "rrf")
133+
.withParam("combiner.rrf.k", "15");
134+
QueryResponse queryResponse = query.process(solrClient, COLLECTION_NAME);
135+
----
136+
====
137+
======
109138
110139
== Combiner Algorithm Plugin
111140

solr/solr-ref-guide/modules/query-guide/pages/json-query-dsl.adoc

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,11 @@ Beware of arity for these references.
388388
Depending on the context, a reference might be resolved into the first element in the array ignoring the later elements, e.g., if one changes the reference below from `{"param":"electronic"}` to `{"param":"manufacturers"}`, it's equivalent to querying for `manu:apple`, ignoring the later query.
389389
These queries don't impact the query result until explicit referencing.
390390

391+
[tabs#json-additional-queries]
392+
======
393+
curl::
394+
+
395+
====
391396
[source,bash]
392397
----
393398
curl -X POST http://localhost:8983/solr/techproducts/query -d '
@@ -402,6 +407,16 @@ curl -X POST http://localhost:8983/solr/techproducts/query -d '
402407
"query":{"param":"electronic"}
403408
}'
404409
----
410+
====
411+
SolrJ::
412+
+
413+
====
414+
[source,java,indent=0]
415+
----
416+
include::example$JsonRequestApiTest.java[tag=solrj-json-query-with-queries]
417+
----
418+
====
419+
======
405420

406421
Overall this example doesn't make much sense, but just demonstrates the syntax.
407422
This feature is useful in xref:json-faceting-domain-changes.adoc#adding-domain-filters[filtering domain] in JSON Facet API xref:json-facet-api.adoc#changing-the-domain[domain changes].

solr/solrj/src/java/org/apache/solr/client/solrj/request/json/JsonQueryRequest.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,26 @@ public JsonQueryRequest setQuery(Map<String, Object> queryJson) {
101101
return this;
102102
}
103103

104+
/**
105+
* Specify the queries parameter sent as a part of JSON request.
106+
*
107+
* <p>This method would be helpful in setting the queries parameter specially for Combined Query
108+
* Component {@code CombinedQueryComponent} use case.
109+
*
110+
* <p><b>Example:</b> You wish to send the JSON request:
111+
*
112+
* <pre>{@code {'limit': 5, 'queries': {'query1': {'lucene':
113+
* {'df':'genre_s', 'query': 'scifi'}}}, 'query2': {'knn': {'f': 'vector', 'query': [0.1, 0.43]}}}}
114+
* </pre>
115+
*
116+
* @param queriesJson a Map of values representing the query subtree of the JSON request you wish
117+
* to send.
118+
*/
119+
public JsonQueryRequest setQueries(Map<String, Object> queriesJson) {
120+
jsonRequestMap.put("queries", queriesJson);
121+
return this;
122+
}
123+
104124
/**
105125
* Specify the query sent as a part of this JSON request.
106126
*

solr/solrj/src/test/org/apache/solr/client/solrj/request/json/JsonQueryRequestIntegrationTest.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.io.IOException;
2121
import java.util.Collection;
2222
import java.util.HashMap;
23+
import java.util.List;
2324
import java.util.Map;
2425
import org.apache.solr.client.solrj.request.AbstractUpdateRequest;
2526
import org.apache.solr.client.solrj.request.CollectionAdminRequest;
@@ -45,6 +46,7 @@ public class JsonQueryRequestIntegrationTest extends SolrCloudTestCase {
4546
private static final int NUM_SCIFI_BOOKS = 2;
4647
private static final int NUM_IN_STOCK = 8;
4748
private static final int NUM_IN_STOCK_AND_FIRST_IN_SERIES = 5;
49+
private static final int NUM_MARTIN_BOOKS = 3;
4850

4951
@BeforeClass
5052
public static void setupCluster() throws Exception {
@@ -88,6 +90,29 @@ public void testQueriesCanUseLocalParamsSyntax() throws Exception {
8890
assertEquals(NUM_SCIFI_BOOKS, queryResponse.getResults().getNumFound());
8991
}
9092

93+
/**
94+
* Test multiple queries can use local params syntax with Combined Query Component.
95+
*
96+
* @throws Exception the exception
97+
*/
98+
@Test
99+
public void testMultipleQueriesCanUseLocalParamsSyntax() throws Exception {
100+
final Map<String, Object> queriesMap = new HashMap<>();
101+
queriesMap.put("query1", "{!lucene df=genre_s v='scifi'}");
102+
queriesMap.put("query2", "{!edismax df=author_t v='martin'}");
103+
final JsonQueryRequest query =
104+
new JsonQueryRequest()
105+
.setQueries(queriesMap)
106+
.withFilter("inStock:true")
107+
.withParam("fl", "name")
108+
.withParam("combiner", "true")
109+
.withParam("combiner.query", List.of("query1", "query2"));
110+
query.setPath("/rrf");
111+
QueryResponse queryResponse = query.process(cluster.getSolrClient(), COLLECTION_NAME);
112+
assertEquals(0, queryResponse.getStatus());
113+
assertEquals(NUM_SCIFI_BOOKS + NUM_MARTIN_BOOKS, queryResponse.getResults().size());
114+
}
115+
91116
@Test
92117
public void testQueriesCanUseExpandedSyntax() throws Exception {
93118
// Construct a tree representing the JSON: {lucene: {df:'genre_s', 'query': 'scifi'}}

0 commit comments

Comments
 (0)