Skip to content

Commit 09dd19a

Browse files
authored
Add MultiSearchTemplate support to High Level Rest client (#30836)
Add MultiSearchTemplate support to High Level Rest client. Addresses part of #27205
1 parent 48cfb9b commit 09dd19a

File tree

17 files changed

+813
-86
lines changed

17 files changed

+813
-86
lines changed

client/rest-high-level/src/main/java/org/elasticsearch/client/RequestConverters.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@
103103
import org.elasticsearch.index.VersionType;
104104
import org.elasticsearch.index.rankeval.RankEvalRequest;
105105
import org.elasticsearch.rest.action.search.RestSearchAction;
106+
import org.elasticsearch.script.mustache.MultiSearchTemplateRequest;
106107
import org.elasticsearch.script.mustache.SearchTemplateRequest;
107108
import org.elasticsearch.search.fetch.subphase.FetchSourceContext;
108109
import org.elasticsearch.tasks.TaskId;
@@ -604,6 +605,21 @@ static Request searchTemplate(SearchTemplateRequest searchTemplateRequest) throw
604605
request.setEntity(createEntity(searchTemplateRequest, REQUEST_BODY_CONTENT_TYPE));
605606
return request;
606607
}
608+
609+
static Request multiSearchTemplate(MultiSearchTemplateRequest multiSearchTemplateRequest) throws IOException {
610+
Request request = new Request(HttpPost.METHOD_NAME, "/_msearch/template");
611+
612+
Params params = new Params(request);
613+
params.putParam(RestSearchAction.TYPED_KEYS_PARAM, "true");
614+
if (multiSearchTemplateRequest.maxConcurrentSearchRequests() != MultiSearchRequest.MAX_CONCURRENT_SEARCH_REQUESTS_DEFAULT) {
615+
params.putParam("max_concurrent_searches", Integer.toString(multiSearchTemplateRequest.maxConcurrentSearchRequests()));
616+
}
617+
618+
XContent xContent = REQUEST_BODY_CONTENT_TYPE.xContent();
619+
byte[] source = MultiSearchTemplateRequest.writeMultiLineFormat(multiSearchTemplateRequest, xContent);
620+
request.setEntity(new ByteArrayEntity(source, createContentType(xContent.type())));
621+
return request;
622+
}
607623

608624
static Request existsAlias(GetAliasesRequest getAliasesRequest) {
609625
if ((getAliasesRequest.indices() == null || getAliasesRequest.indices().length == 0) &&

client/rest-high-level/src/main/java/org/elasticsearch/client/RestHighLevelClient.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@
6868
import org.elasticsearch.plugins.spi.NamedXContentProvider;
6969
import org.elasticsearch.rest.BytesRestResponse;
7070
import org.elasticsearch.rest.RestStatus;
71+
import org.elasticsearch.script.mustache.MultiSearchTemplateRequest;
72+
import org.elasticsearch.script.mustache.MultiSearchTemplateResponse;
7173
import org.elasticsearch.script.mustache.SearchTemplateRequest;
7274
import org.elasticsearch.script.mustache.SearchTemplateResponse;
7375
import org.elasticsearch.search.aggregations.Aggregation;
@@ -666,6 +668,32 @@ public final RankEvalResponse rankEval(RankEvalRequest rankEvalRequest, RequestO
666668
emptySet());
667669
}
668670

671+
672+
/**
673+
* Executes a request using the Multi Search Template API.
674+
*
675+
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/multi-search-template.html">Multi Search Template API
676+
* on elastic.co</a>.
677+
*/
678+
public final MultiSearchTemplateResponse multiSearchTemplate(MultiSearchTemplateRequest multiSearchTemplateRequest,
679+
RequestOptions options) throws IOException {
680+
return performRequestAndParseEntity(multiSearchTemplateRequest, RequestConverters::multiSearchTemplate,
681+
options, MultiSearchTemplateResponse::fromXContext, emptySet());
682+
}
683+
684+
/**
685+
* Asynchronously executes a request using the Multi Search Template API
686+
*
687+
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/multi-search-template.html">Multi Search Template API
688+
* on elastic.co</a>.
689+
*/
690+
public final void multiSearchTemplateAsync(MultiSearchTemplateRequest multiSearchTemplateRequest,
691+
RequestOptions options,
692+
ActionListener<MultiSearchTemplateResponse> listener) {
693+
performRequestAsyncAndParseEntity(multiSearchTemplateRequest, RequestConverters::multiSearchTemplate,
694+
options, MultiSearchTemplateResponse::fromXContext, listener, emptySet());
695+
}
696+
669697
/**
670698
* Asynchronously executes a request using the Ranking Evaluation API.
671699
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-rank-eval.html">Ranking Evaluation API

client/rest-high-level/src/test/java/org/elasticsearch/client/RequestConvertersTests.java

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@
124124
import org.elasticsearch.repositories.fs.FsRepository;
125125
import org.elasticsearch.rest.action.search.RestSearchAction;
126126
import org.elasticsearch.script.ScriptType;
127+
import org.elasticsearch.script.mustache.MultiSearchTemplateRequest;
127128
import org.elasticsearch.script.mustache.SearchTemplateRequest;
128129
import org.elasticsearch.search.Scroll;
129130
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
@@ -1373,7 +1374,53 @@ public void testRenderSearchTemplate() throws Exception {
13731374
assertEquals(Collections.emptyMap(), request.getParameters());
13741375
assertToXContentBody(searchTemplateRequest, request.getEntity());
13751376
}
1376-
1377+
1378+
public void testMultiSearchTemplate() throws Exception {
1379+
final int numSearchRequests = randomIntBetween(1, 10);
1380+
MultiSearchTemplateRequest multiSearchTemplateRequest = new MultiSearchTemplateRequest();
1381+
1382+
for (int i = 0; i < numSearchRequests; i++) {
1383+
// Create a random request.
1384+
String[] indices = randomIndicesNames(0, 5);
1385+
SearchRequest searchRequest = new SearchRequest(indices);
1386+
1387+
Map<String, String> expectedParams = new HashMap<>();
1388+
setRandomSearchParams(searchRequest, expectedParams);
1389+
1390+
// scroll is not supported in the current msearch or msearchtemplate api, so unset it:
1391+
searchRequest.scroll((Scroll) null);
1392+
// batched reduce size is currently not set-able on a per-request basis as it is a query string parameter only
1393+
searchRequest.setBatchedReduceSize(SearchRequest.DEFAULT_BATCHED_REDUCE_SIZE);
1394+
1395+
setRandomIndicesOptions(searchRequest::indicesOptions, searchRequest::indicesOptions, expectedParams);
1396+
1397+
SearchTemplateRequest searchTemplateRequest = new SearchTemplateRequest(searchRequest);
1398+
1399+
searchTemplateRequest.setScript("{\"query\": { \"match\" : { \"{{field}}\" : \"{{value}}\" }}}");
1400+
searchTemplateRequest.setScriptType(ScriptType.INLINE);
1401+
searchTemplateRequest.setProfile(randomBoolean());
1402+
1403+
Map<String, Object> scriptParams = new HashMap<>();
1404+
scriptParams.put("field", "name");
1405+
scriptParams.put("value", randomAlphaOfLengthBetween(2, 5));
1406+
searchTemplateRequest.setScriptParams(scriptParams);
1407+
1408+
multiSearchTemplateRequest.add(searchTemplateRequest);
1409+
}
1410+
1411+
Request multiRequest = RequestConverters.multiSearchTemplate(multiSearchTemplateRequest);
1412+
1413+
assertEquals(HttpPost.METHOD_NAME, multiRequest.getMethod());
1414+
assertEquals("/_msearch/template", multiRequest.getEndpoint());
1415+
List<SearchTemplateRequest> searchRequests = multiSearchTemplateRequest.requests();
1416+
assertEquals(numSearchRequests, searchRequests.size());
1417+
1418+
HttpEntity actualEntity = multiRequest.getEntity();
1419+
byte[] expectedBytes = MultiSearchTemplateRequest.writeMultiLineFormat(multiSearchTemplateRequest, XContentType.JSON.xContent());
1420+
assertEquals(XContentType.JSON.mediaTypeWithoutParameters(), actualEntity.getContentType().getValue());
1421+
assertEquals(new BytesArray(expectedBytes), new BytesArray(EntityUtils.toByteArray(actualEntity)));
1422+
}
1423+
13771424
public void testExistsAlias() {
13781425
GetAliasesRequest getAliasesRequest = new GetAliasesRequest();
13791426
String[] indices = randomBoolean() ? null : randomIndicesNames(0, 5);
@@ -2385,7 +2432,7 @@ private static void setRandomSearchParams(SearchRequest searchRequest,
23852432
expectedParams.put("preference", searchRequest.preference());
23862433
}
23872434
if (randomBoolean()) {
2388-
searchRequest.searchType(randomFrom(SearchType.values()));
2435+
searchRequest.searchType(randomFrom(SearchType.CURRENTLY_SUPPORTED));
23892436
}
23902437
expectedParams.put("search_type", searchRequest.searchType().name().toLowerCase(Locale.ROOT));
23912438
if (randomBoolean()) {

client/rest-high-level/src/test/java/org/elasticsearch/client/SearchIT.java

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@
5454
import org.elasticsearch.rest.RestStatus;
5555
import org.elasticsearch.script.Script;
5656
import org.elasticsearch.script.ScriptType;
57+
import org.elasticsearch.script.mustache.MultiSearchTemplateRequest;
58+
import org.elasticsearch.script.mustache.MultiSearchTemplateResponse;
59+
import org.elasticsearch.script.mustache.MultiSearchTemplateResponse.Item;
5760
import org.elasticsearch.script.mustache.SearchTemplateRequest;
5861
import org.elasticsearch.script.mustache.SearchTemplateResponse;
5962
import org.elasticsearch.search.SearchHit;
@@ -875,6 +878,105 @@ public void testRenderSearchTemplate() throws IOException {
875878

876879
assertToXContentEquivalent(expectedSource, actualSource, XContentType.JSON);
877880
}
881+
882+
883+
public void testMultiSearchTemplate() throws Exception {
884+
MultiSearchTemplateRequest multiSearchTemplateRequest = new MultiSearchTemplateRequest();
885+
886+
SearchTemplateRequest goodRequest = new SearchTemplateRequest();
887+
goodRequest.setRequest(new SearchRequest("index"));
888+
goodRequest.setScriptType(ScriptType.INLINE);
889+
goodRequest.setScript(
890+
"{" +
891+
" \"query\": {" +
892+
" \"match\": {" +
893+
" \"num\": {{number}}" +
894+
" }" +
895+
" }" +
896+
"}");
897+
Map<String, Object> scriptParams = new HashMap<>();
898+
scriptParams.put("number", 10);
899+
goodRequest.setScriptParams(scriptParams);
900+
goodRequest.setExplain(true);
901+
goodRequest.setProfile(true);
902+
multiSearchTemplateRequest.add(goodRequest);
903+
904+
905+
SearchTemplateRequest badRequest = new SearchTemplateRequest();
906+
badRequest.setRequest(new SearchRequest("index"));
907+
badRequest.setScriptType(ScriptType.INLINE);
908+
badRequest.setScript("{ NOT VALID JSON {{number}} }");
909+
scriptParams = new HashMap<>();
910+
scriptParams.put("number", 10);
911+
badRequest.setScriptParams(scriptParams);
912+
913+
multiSearchTemplateRequest.add(badRequest);
914+
915+
MultiSearchTemplateResponse multiSearchTemplateResponse =
916+
execute(multiSearchTemplateRequest, highLevelClient()::multiSearchTemplate,
917+
highLevelClient()::multiSearchTemplateAsync);
918+
919+
Item[] responses = multiSearchTemplateResponse.getResponses();
920+
921+
assertEquals(2, responses.length);
922+
923+
924+
assertNull(responses[0].getResponse().getSource());
925+
SearchResponse goodResponse =responses[0].getResponse().getResponse();
926+
assertNotNull(goodResponse);
927+
assertThat(responses[0].isFailure(), Matchers.is(false));
928+
assertEquals(1, goodResponse.getHits().totalHits);
929+
assertEquals(1, goodResponse.getHits().getHits().length);
930+
assertThat(goodResponse.getHits().getMaxScore(), greaterThan(0f));
931+
SearchHit hit = goodResponse.getHits().getHits()[0];
932+
assertNotNull(hit.getExplanation());
933+
assertFalse(goodResponse.getProfileResults().isEmpty());
934+
935+
936+
assertNull(responses[0].getResponse().getSource());
937+
assertThat(responses[1].isFailure(), Matchers.is(true));
938+
assertNotNull(responses[1].getFailureMessage());
939+
assertThat(responses[1].getFailureMessage(), containsString("json_parse_exception"));
940+
}
941+
942+
public void testMultiSearchTemplateAllBad() throws Exception {
943+
MultiSearchTemplateRequest multiSearchTemplateRequest = new MultiSearchTemplateRequest();
944+
945+
SearchTemplateRequest badRequest1 = new SearchTemplateRequest();
946+
badRequest1.setRequest(new SearchRequest("index"));
947+
badRequest1.setScriptType(ScriptType.INLINE);
948+
badRequest1.setScript(
949+
"{" +
950+
" \"query\": {" +
951+
" \"match\": {" +
952+
" \"num\": {{number}}" +
953+
" }" +
954+
" }" +
955+
"}");
956+
Map<String, Object> scriptParams = new HashMap<>();
957+
scriptParams.put("number", "BAD NUMBER");
958+
badRequest1.setScriptParams(scriptParams);
959+
multiSearchTemplateRequest.add(badRequest1);
960+
961+
962+
SearchTemplateRequest badRequest2 = new SearchTemplateRequest();
963+
badRequest2.setRequest(new SearchRequest("index"));
964+
badRequest2.setScriptType(ScriptType.INLINE);
965+
badRequest2.setScript("BAD QUERY TEMPLATE");
966+
scriptParams = new HashMap<>();
967+
scriptParams.put("number", "BAD NUMBER");
968+
badRequest2.setScriptParams(scriptParams);
969+
970+
multiSearchTemplateRequest.add(badRequest2);
971+
972+
// The whole HTTP request should fail if no nested search requests are valid
973+
ElasticsearchStatusException exception = expectThrows(ElasticsearchStatusException.class,
974+
() -> execute(multiSearchTemplateRequest, highLevelClient()::multiSearchTemplate,
975+
highLevelClient()::multiSearchTemplateAsync));
976+
977+
assertEquals(RestStatus.BAD_REQUEST, exception.status());
978+
assertThat(exception.getMessage(), containsString("no requests added"));
979+
}
878980

879981
public void testExplain() throws IOException {
880982
{

0 commit comments

Comments
 (0)