Skip to content

Commit 4f8451f

Browse files
committed
Add MultiSearchTemplate support to High Level Rest client
Note some changes to core classes were required to support tests: * Added SearchType.currentlySupported to list only the usable options * Changed SearchResponse.Clusters constructor to public * Added SearchRequest.DEFAULT_BATCHED_REDUCE_SIZE constant * Added missing "tookInMillis" to MultiSearchTemplateResponse * Added SearchTemplateResponse toString() for comparisons in tests
1 parent b2e48c9 commit 4f8451f

File tree

17 files changed

+734
-84
lines changed

17 files changed

+734
-84
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
@@ -91,6 +91,7 @@
9191
import org.elasticsearch.index.VersionType;
9292
import org.elasticsearch.index.rankeval.RankEvalRequest;
9393
import org.elasticsearch.rest.action.search.RestSearchAction;
94+
import org.elasticsearch.script.mustache.MultiSearchTemplateRequest;
9495
import org.elasticsearch.script.mustache.SearchTemplateRequest;
9596
import org.elasticsearch.search.fetch.subphase.FetchSourceContext;
9697
import org.elasticsearch.tasks.TaskId;
@@ -573,6 +574,21 @@ static Request searchTemplate(SearchTemplateRequest searchTemplateRequest) throw
573574
request.setEntity(createEntity(searchTemplateRequest, REQUEST_BODY_CONTENT_TYPE));
574575
return request;
575576
}
577+
578+
static Request multiSearchTemplate(MultiSearchTemplateRequest multiSearchTemplateRequest) throws IOException {
579+
Request request = new Request(HttpPost.METHOD_NAME, "/_msearch/template");
580+
581+
Params params = new Params(request);
582+
params.putParam(RestSearchAction.TYPED_KEYS_PARAM, "true");
583+
if (multiSearchTemplateRequest.maxConcurrentSearchRequests() != MultiSearchRequest.MAX_CONCURRENT_SEARCH_REQUESTS_DEFAULT) {
584+
params.putParam("max_concurrent_searches", Integer.toString(multiSearchTemplateRequest.maxConcurrentSearchRequests()));
585+
}
586+
587+
XContent xContent = REQUEST_BODY_CONTENT_TYPE.xContent();
588+
byte[] source = MultiSearchTemplateRequest.writeMultiLineFormat(multiSearchTemplateRequest, xContent);
589+
request.setEntity(new ByteArrayEntity(source, createContentType(xContent.type())));
590+
return request;
591+
}
576592

577593
static Request existsAlias(GetAliasesRequest getAliasesRequest) {
578594
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
@@ -62,6 +62,8 @@
6262
import org.elasticsearch.plugins.spi.NamedXContentProvider;
6363
import org.elasticsearch.rest.BytesRestResponse;
6464
import org.elasticsearch.rest.RestStatus;
65+
import org.elasticsearch.script.mustache.MultiSearchTemplateRequest;
66+
import org.elasticsearch.script.mustache.MultiSearchTemplateResponse;
6567
import org.elasticsearch.script.mustache.SearchTemplateRequest;
6668
import org.elasticsearch.script.mustache.SearchTemplateResponse;
6769
import org.elasticsearch.search.aggregations.Aggregation;
@@ -909,6 +911,32 @@ public final RankEvalResponse rankEval(RankEvalRequest rankEvalRequest, RequestO
909911
emptySet());
910912
}
911913

914+
915+
/**
916+
* Executes a request using the Multi Search Template API.
917+
*
918+
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/multi-search-template.html">Multi Search Template API
919+
* on elastic.co</a>.
920+
*/
921+
public final MultiSearchTemplateResponse multiSearchTemplate(MultiSearchTemplateRequest multiSearchTemplateRequest,
922+
RequestOptions options) throws IOException {
923+
return performRequestAndParseEntity(multiSearchTemplateRequest, RequestConverters::multiSearchTemplate,
924+
options, MultiSearchTemplateResponse::fromXContext, emptySet());
925+
}
926+
927+
/**
928+
* Asynchronously executes a request using the Multi Search Template API
929+
*
930+
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/multi-search-template.html">Multi Search Template API
931+
* on elastic.co</a>.
932+
*/
933+
public final void multiSearchTemplateAsync(MultiSearchTemplateRequest multiSearchTemplateRequest,
934+
RequestOptions options,
935+
ActionListener<MultiSearchTemplateResponse> listener) {
936+
performRequestAsyncAndParseEntity(multiSearchTemplateRequest, RequestConverters::multiSearchTemplate,
937+
options, MultiSearchTemplateResponse::fromXContext, listener, emptySet());
938+
}
939+
912940
/**
913941
* Executes a request using the Ranking Evaluation API.
914942
*

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

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,8 +109,12 @@
109109
import org.elasticsearch.index.rankeval.RatedRequest;
110110
import org.elasticsearch.index.rankeval.RestRankEvalAction;
111111
import org.elasticsearch.repositories.fs.FsRepository;
112+
import org.elasticsearch.rest.RestRequest;
113+
import org.elasticsearch.rest.RestRequest.Method;
112114
import org.elasticsearch.rest.action.search.RestSearchAction;
113115
import org.elasticsearch.script.ScriptType;
116+
import org.elasticsearch.script.mustache.MultiSearchTemplateRequest;
117+
import org.elasticsearch.script.mustache.RestMultiSearchTemplateAction;
114118
import org.elasticsearch.script.mustache.SearchTemplateRequest;
115119
import org.elasticsearch.search.Scroll;
116120
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
@@ -1304,7 +1308,53 @@ public void testRenderSearchTemplate() throws Exception {
13041308
assertEquals(Collections.emptyMap(), request.getParameters());
13051309
assertToXContentBody(searchTemplateRequest, request.getEntity());
13061310
}
1307-
1311+
1312+
public void testMultiSearchTemplate() throws Exception {
1313+
final int numSearchRequests = randomIntBetween(1, 10);
1314+
MultiSearchTemplateRequest multiSearchTemplateRequest = new MultiSearchTemplateRequest();
1315+
1316+
for (int i = 0; i < numSearchRequests; i++) {
1317+
// Create a random request.
1318+
String[] indices = randomIndicesNames(0, 5);
1319+
SearchRequest searchRequest = new SearchRequest(indices);
1320+
1321+
Map<String, String> expectedParams = new HashMap<>();
1322+
setRandomSearchParams(searchRequest, expectedParams);
1323+
1324+
// scroll is not supported in the current msearch or msearchtemplate api, so unset it:
1325+
searchRequest.scroll((Scroll) null);
1326+
// batched reduce size is currently not set-able on a per-request basis as it is a query string parameter only
1327+
searchRequest.setBatchedReduceSize(SearchRequest.DEFAULT_BATCHED_REDUCE_SIZE);
1328+
1329+
setRandomIndicesOptions(searchRequest::indicesOptions, searchRequest::indicesOptions, expectedParams);
1330+
1331+
SearchTemplateRequest searchTemplateRequest = new SearchTemplateRequest(searchRequest);
1332+
1333+
searchTemplateRequest.setScript("{\"query\": { \"match\" : { \"{{field}}\" : \"{{value}}\" }}}");
1334+
searchTemplateRequest.setScriptType(ScriptType.INLINE);
1335+
searchTemplateRequest.setProfile(randomBoolean());
1336+
1337+
Map<String, Object> scriptParams = new HashMap<>();
1338+
scriptParams.put("field", "name");
1339+
scriptParams.put("value", randomAlphaOfLengthBetween(2, 5));
1340+
searchTemplateRequest.setScriptParams(scriptParams);
1341+
1342+
multiSearchTemplateRequest.add(searchTemplateRequest);
1343+
}
1344+
1345+
Request multiRequest = RequestConverters.multiSearchTemplate(multiSearchTemplateRequest);
1346+
1347+
assertEquals(HttpPost.METHOD_NAME, multiRequest.getMethod());
1348+
assertEquals("/_msearch/template", multiRequest.getEndpoint());
1349+
List<SearchTemplateRequest> searchRequests = multiSearchTemplateRequest.requests();
1350+
assertEquals(numSearchRequests, searchRequests.size());
1351+
1352+
HttpEntity actualEntity = multiRequest.getEntity();
1353+
byte[] expectedBytes = MultiSearchTemplateRequest.writeMultiLineFormat(multiSearchTemplateRequest, XContentType.JSON.xContent());
1354+
assertEquals(XContentType.JSON.mediaTypeWithoutParameters(), actualEntity.getContentType().getValue());
1355+
assertEquals(new BytesArray(expectedBytes), new BytesArray(EntityUtils.toByteArray(actualEntity)));
1356+
}
1357+
13081358
public void testExistsAlias() {
13091359
GetAliasesRequest getAliasesRequest = new GetAliasesRequest();
13101360
String[] indices = randomBoolean() ? null : randomIndicesNames(0, 5);
@@ -2013,7 +2063,7 @@ private static void setRandomSearchParams(SearchRequest searchRequest,
20132063
expectedParams.put("preference", searchRequest.preference());
20142064
}
20152065
if (randomBoolean()) {
2016-
searchRequest.searchType(randomFrom(SearchType.values()));
2066+
searchRequest.searchType(randomFrom(SearchType.CURRENTLY_SUPPORTED));
20172067
}
20182068
expectedParams.put("search_type", searchRequest.searchType().name().toLowerCase(Locale.ROOT));
20192069
if (randomBoolean()) {
@@ -2038,7 +2088,7 @@ private static void setRandomIndicesOptions(Consumer<IndicesOptions> setter, Sup
20382088
Map<String, String> expectedParams) {
20392089

20402090
if (randomBoolean()) {
2041-
setter.accept(IndicesOptions.fromOptions(randomBoolean(), randomBoolean(), randomBoolean(), randomBoolean()));
2091+
setter.accept(IndicesOptions.fromOptions(randomBoolean(), randomBoolean(), randomBoolean(), randomBoolean()));
20422092
}
20432093
expectedParams.put("ignore_unavailable", Boolean.toString(getter.get().ignoreUnavailable()));
20442094
expectedParams.put("allow_no_indices", Boolean.toString(getter.get().allowNoIndices()));

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
@@ -51,6 +51,9 @@
5151
import org.elasticsearch.rest.RestStatus;
5252
import org.elasticsearch.script.Script;
5353
import org.elasticsearch.script.ScriptType;
54+
import org.elasticsearch.script.mustache.MultiSearchTemplateRequest;
55+
import org.elasticsearch.script.mustache.MultiSearchTemplateResponse;
56+
import org.elasticsearch.script.mustache.MultiSearchTemplateResponse.Item;
5457
import org.elasticsearch.script.mustache.SearchTemplateRequest;
5558
import org.elasticsearch.script.mustache.SearchTemplateResponse;
5659
import org.elasticsearch.search.SearchHit;
@@ -854,6 +857,105 @@ public void testRenderSearchTemplate() throws IOException {
854857

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

858960
public void testFieldCaps() throws IOException {
859961
FieldCapabilitiesRequest request = new FieldCapabilitiesRequest()

0 commit comments

Comments
 (0)