diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/RequestConverters.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/RequestConverters.java
index 6e96ecd9c5c..839d86bf9f1 100644
--- a/client/rest-high-level/src/main/java/org/elasticsearch/client/RequestConverters.java
+++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/RequestConverters.java
@@ -103,6 +103,7 @@ import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.VersionType;
import org.elasticsearch.index.rankeval.RankEvalRequest;
import org.elasticsearch.rest.action.search.RestSearchAction;
+import org.elasticsearch.script.mustache.MultiSearchTemplateRequest;
import org.elasticsearch.script.mustache.SearchTemplateRequest;
import org.elasticsearch.search.fetch.subphase.FetchSourceContext;
import org.elasticsearch.tasks.TaskId;
@@ -604,6 +605,21 @@ final class RequestConverters {
request.setEntity(createEntity(searchTemplateRequest, REQUEST_BODY_CONTENT_TYPE));
return request;
}
+
+ static Request multiSearchTemplate(MultiSearchTemplateRequest multiSearchTemplateRequest) throws IOException {
+ Request request = new Request(HttpPost.METHOD_NAME, "/_msearch/template");
+
+ Params params = new Params(request);
+ params.putParam(RestSearchAction.TYPED_KEYS_PARAM, "true");
+ if (multiSearchTemplateRequest.maxConcurrentSearchRequests() != MultiSearchRequest.MAX_CONCURRENT_SEARCH_REQUESTS_DEFAULT) {
+ params.putParam("max_concurrent_searches", Integer.toString(multiSearchTemplateRequest.maxConcurrentSearchRequests()));
+ }
+
+ XContent xContent = REQUEST_BODY_CONTENT_TYPE.xContent();
+ byte[] source = MultiSearchTemplateRequest.writeMultiLineFormat(multiSearchTemplateRequest, xContent);
+ request.setEntity(new ByteArrayEntity(source, createContentType(xContent.type())));
+ return request;
+ }
static Request existsAlias(GetAliasesRequest getAliasesRequest) {
if ((getAliasesRequest.indices() == null || getAliasesRequest.indices().length == 0) &&
diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/RestHighLevelClient.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/RestHighLevelClient.java
index 7d9b02b06a1..48277d67e6d 100644
--- a/client/rest-high-level/src/main/java/org/elasticsearch/client/RestHighLevelClient.java
+++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/RestHighLevelClient.java
@@ -68,6 +68,8 @@ import org.elasticsearch.index.rankeval.RankEvalResponse;
import org.elasticsearch.plugins.spi.NamedXContentProvider;
import org.elasticsearch.rest.BytesRestResponse;
import org.elasticsearch.rest.RestStatus;
+import org.elasticsearch.script.mustache.MultiSearchTemplateRequest;
+import org.elasticsearch.script.mustache.MultiSearchTemplateResponse;
import org.elasticsearch.script.mustache.SearchTemplateRequest;
import org.elasticsearch.script.mustache.SearchTemplateResponse;
import org.elasticsearch.search.aggregations.Aggregation;
@@ -666,6 +668,32 @@ public class RestHighLevelClient implements Closeable {
emptySet());
}
+
+ /**
+ * Executes a request using the Multi Search Template API.
+ *
+ * See Multi Search Template API
+ * on elastic.co.
+ */
+ public final MultiSearchTemplateResponse multiSearchTemplate(MultiSearchTemplateRequest multiSearchTemplateRequest,
+ RequestOptions options) throws IOException {
+ return performRequestAndParseEntity(multiSearchTemplateRequest, RequestConverters::multiSearchTemplate,
+ options, MultiSearchTemplateResponse::fromXContext, emptySet());
+ }
+
+ /**
+ * Asynchronously executes a request using the Multi Search Template API
+ *
+ * See Multi Search Template API
+ * on elastic.co.
+ */
+ public final void multiSearchTemplateAsync(MultiSearchTemplateRequest multiSearchTemplateRequest,
+ RequestOptions options,
+ ActionListener listener) {
+ performRequestAsyncAndParseEntity(multiSearchTemplateRequest, RequestConverters::multiSearchTemplate,
+ options, MultiSearchTemplateResponse::fromXContext, listener, emptySet());
+ }
+
/**
* Asynchronously executes a request using the Ranking Evaluation API.
* See Ranking Evaluation API
diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/RequestConvertersTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/RequestConvertersTests.java
index 5887feefa63..e838989a0c8 100644
--- a/client/rest-high-level/src/test/java/org/elasticsearch/client/RequestConvertersTests.java
+++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/RequestConvertersTests.java
@@ -124,6 +124,7 @@ import org.elasticsearch.index.rankeval.RestRankEvalAction;
import org.elasticsearch.repositories.fs.FsRepository;
import org.elasticsearch.rest.action.search.RestSearchAction;
import org.elasticsearch.script.ScriptType;
+import org.elasticsearch.script.mustache.MultiSearchTemplateRequest;
import org.elasticsearch.script.mustache.SearchTemplateRequest;
import org.elasticsearch.search.Scroll;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
@@ -1373,7 +1374,53 @@ public class RequestConvertersTests extends ESTestCase {
assertEquals(Collections.emptyMap(), request.getParameters());
assertToXContentBody(searchTemplateRequest, request.getEntity());
}
+
+ public void testMultiSearchTemplate() throws Exception {
+ final int numSearchRequests = randomIntBetween(1, 10);
+ MultiSearchTemplateRequest multiSearchTemplateRequest = new MultiSearchTemplateRequest();
+
+ for (int i = 0; i < numSearchRequests; i++) {
+ // Create a random request.
+ String[] indices = randomIndicesNames(0, 5);
+ SearchRequest searchRequest = new SearchRequest(indices);
+
+ Map expectedParams = new HashMap<>();
+ setRandomSearchParams(searchRequest, expectedParams);
+
+ // scroll is not supported in the current msearch or msearchtemplate api, so unset it:
+ searchRequest.scroll((Scroll) null);
+ // batched reduce size is currently not set-able on a per-request basis as it is a query string parameter only
+ searchRequest.setBatchedReduceSize(SearchRequest.DEFAULT_BATCHED_REDUCE_SIZE);
+
+ setRandomIndicesOptions(searchRequest::indicesOptions, searchRequest::indicesOptions, expectedParams);
+
+ SearchTemplateRequest searchTemplateRequest = new SearchTemplateRequest(searchRequest);
+
+ searchTemplateRequest.setScript("{\"query\": { \"match\" : { \"{{field}}\" : \"{{value}}\" }}}");
+ searchTemplateRequest.setScriptType(ScriptType.INLINE);
+ searchTemplateRequest.setProfile(randomBoolean());
+
+ Map scriptParams = new HashMap<>();
+ scriptParams.put("field", "name");
+ scriptParams.put("value", randomAlphaOfLengthBetween(2, 5));
+ searchTemplateRequest.setScriptParams(scriptParams);
+
+ multiSearchTemplateRequest.add(searchTemplateRequest);
+ }
+ Request multiRequest = RequestConverters.multiSearchTemplate(multiSearchTemplateRequest);
+
+ assertEquals(HttpPost.METHOD_NAME, multiRequest.getMethod());
+ assertEquals("/_msearch/template", multiRequest.getEndpoint());
+ List searchRequests = multiSearchTemplateRequest.requests();
+ assertEquals(numSearchRequests, searchRequests.size());
+
+ HttpEntity actualEntity = multiRequest.getEntity();
+ byte[] expectedBytes = MultiSearchTemplateRequest.writeMultiLineFormat(multiSearchTemplateRequest, XContentType.JSON.xContent());
+ assertEquals(XContentType.JSON.mediaTypeWithoutParameters(), actualEntity.getContentType().getValue());
+ assertEquals(new BytesArray(expectedBytes), new BytesArray(EntityUtils.toByteArray(actualEntity)));
+ }
+
public void testExistsAlias() {
GetAliasesRequest getAliasesRequest = new GetAliasesRequest();
String[] indices = randomBoolean() ? null : randomIndicesNames(0, 5);
@@ -2385,7 +2432,7 @@ public class RequestConvertersTests extends ESTestCase {
expectedParams.put("preference", searchRequest.preference());
}
if (randomBoolean()) {
- searchRequest.searchType(randomFrom(SearchType.values()));
+ searchRequest.searchType(randomFrom(SearchType.CURRENTLY_SUPPORTED));
}
expectedParams.put("search_type", searchRequest.searchType().name().toLowerCase(Locale.ROOT));
if (randomBoolean()) {
diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/SearchIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/SearchIT.java
index b83cc263be9..18a43ffa8d4 100644
--- a/client/rest-high-level/src/test/java/org/elasticsearch/client/SearchIT.java
+++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/SearchIT.java
@@ -54,6 +54,9 @@ import org.elasticsearch.join.aggregations.ChildrenAggregationBuilder;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.script.Script;
import org.elasticsearch.script.ScriptType;
+import org.elasticsearch.script.mustache.MultiSearchTemplateRequest;
+import org.elasticsearch.script.mustache.MultiSearchTemplateResponse;
+import org.elasticsearch.script.mustache.MultiSearchTemplateResponse.Item;
import org.elasticsearch.script.mustache.SearchTemplateRequest;
import org.elasticsearch.script.mustache.SearchTemplateResponse;
import org.elasticsearch.search.SearchHit;
@@ -875,6 +878,105 @@ public class SearchIT extends ESRestHighLevelClientTestCase {
assertToXContentEquivalent(expectedSource, actualSource, XContentType.JSON);
}
+
+
+ public void testMultiSearchTemplate() throws Exception {
+ MultiSearchTemplateRequest multiSearchTemplateRequest = new MultiSearchTemplateRequest();
+
+ SearchTemplateRequest goodRequest = new SearchTemplateRequest();
+ goodRequest.setRequest(new SearchRequest("index"));
+ goodRequest.setScriptType(ScriptType.INLINE);
+ goodRequest.setScript(
+ "{" +
+ " \"query\": {" +
+ " \"match\": {" +
+ " \"num\": {{number}}" +
+ " }" +
+ " }" +
+ "}");
+ Map scriptParams = new HashMap<>();
+ scriptParams.put("number", 10);
+ goodRequest.setScriptParams(scriptParams);
+ goodRequest.setExplain(true);
+ goodRequest.setProfile(true);
+ multiSearchTemplateRequest.add(goodRequest);
+
+
+ SearchTemplateRequest badRequest = new SearchTemplateRequest();
+ badRequest.setRequest(new SearchRequest("index"));
+ badRequest.setScriptType(ScriptType.INLINE);
+ badRequest.setScript("{ NOT VALID JSON {{number}} }");
+ scriptParams = new HashMap<>();
+ scriptParams.put("number", 10);
+ badRequest.setScriptParams(scriptParams);
+
+ multiSearchTemplateRequest.add(badRequest);
+
+ MultiSearchTemplateResponse multiSearchTemplateResponse =
+ execute(multiSearchTemplateRequest, highLevelClient()::multiSearchTemplate,
+ highLevelClient()::multiSearchTemplateAsync);
+
+ Item[] responses = multiSearchTemplateResponse.getResponses();
+
+ assertEquals(2, responses.length);
+
+
+ assertNull(responses[0].getResponse().getSource());
+ SearchResponse goodResponse =responses[0].getResponse().getResponse();
+ assertNotNull(goodResponse);
+ assertThat(responses[0].isFailure(), Matchers.is(false));
+ assertEquals(1, goodResponse.getHits().totalHits);
+ assertEquals(1, goodResponse.getHits().getHits().length);
+ assertThat(goodResponse.getHits().getMaxScore(), greaterThan(0f));
+ SearchHit hit = goodResponse.getHits().getHits()[0];
+ assertNotNull(hit.getExplanation());
+ assertFalse(goodResponse.getProfileResults().isEmpty());
+
+
+ assertNull(responses[0].getResponse().getSource());
+ assertThat(responses[1].isFailure(), Matchers.is(true));
+ assertNotNull(responses[1].getFailureMessage());
+ assertThat(responses[1].getFailureMessage(), containsString("json_parse_exception"));
+ }
+
+ public void testMultiSearchTemplateAllBad() throws Exception {
+ MultiSearchTemplateRequest multiSearchTemplateRequest = new MultiSearchTemplateRequest();
+
+ SearchTemplateRequest badRequest1 = new SearchTemplateRequest();
+ badRequest1.setRequest(new SearchRequest("index"));
+ badRequest1.setScriptType(ScriptType.INLINE);
+ badRequest1.setScript(
+ "{" +
+ " \"query\": {" +
+ " \"match\": {" +
+ " \"num\": {{number}}" +
+ " }" +
+ " }" +
+ "}");
+ Map scriptParams = new HashMap<>();
+ scriptParams.put("number", "BAD NUMBER");
+ badRequest1.setScriptParams(scriptParams);
+ multiSearchTemplateRequest.add(badRequest1);
+
+
+ SearchTemplateRequest badRequest2 = new SearchTemplateRequest();
+ badRequest2.setRequest(new SearchRequest("index"));
+ badRequest2.setScriptType(ScriptType.INLINE);
+ badRequest2.setScript("BAD QUERY TEMPLATE");
+ scriptParams = new HashMap<>();
+ scriptParams.put("number", "BAD NUMBER");
+ badRequest2.setScriptParams(scriptParams);
+
+ multiSearchTemplateRequest.add(badRequest2);
+
+ // The whole HTTP request should fail if no nested search requests are valid
+ ElasticsearchStatusException exception = expectThrows(ElasticsearchStatusException.class,
+ () -> execute(multiSearchTemplateRequest, highLevelClient()::multiSearchTemplate,
+ highLevelClient()::multiSearchTemplateAsync));
+
+ assertEquals(RestStatus.BAD_REQUEST, exception.status());
+ assertThat(exception.getMessage(), containsString("no requests added"));
+ }
public void testExplain() throws IOException {
{
diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/SearchDocumentationIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/SearchDocumentationIT.java
index 3e484b0c86d..308d9ba5699 100644
--- a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/SearchDocumentationIT.java
+++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/SearchDocumentationIT.java
@@ -71,6 +71,9 @@ import org.elasticsearch.index.rankeval.RatedRequest;
import org.elasticsearch.index.rankeval.RatedSearchHit;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.script.ScriptType;
+import org.elasticsearch.script.mustache.MultiSearchTemplateRequest;
+import org.elasticsearch.script.mustache.MultiSearchTemplateResponse;
+import org.elasticsearch.script.mustache.MultiSearchTemplateResponse.Item;
import org.elasticsearch.script.mustache.SearchTemplateRequest;
import org.elasticsearch.script.mustache.SearchTemplateResponse;
import org.elasticsearch.search.Scroll;
@@ -773,21 +776,7 @@ public class SearchDocumentationIT extends ESRestHighLevelClientTestCase {
RestHighLevelClient client = highLevelClient();
RestClient restClient = client();
- // tag::register-script
- Request scriptRequest = new Request("POST", "_scripts/title_search");
- scriptRequest.setJsonEntity(
- "{" +
- " \"script\": {" +
- " \"lang\": \"mustache\"," +
- " \"source\": {" +
- " \"query\": { \"match\" : { \"{{field}}\" : \"{{value}}\" } }," +
- " \"size\" : \"{{size}}\"" +
- " }" +
- " }" +
- "}");
- Response scriptResponse = restClient.performRequest(scriptRequest);
- // end::register-script
- assertEquals(RestStatus.OK.getStatus(), scriptResponse.getStatusLine().getStatusCode());
+ registerQueryScript(restClient);
// tag::search-template-request-stored
SearchTemplateRequest request = new SearchTemplateRequest();
@@ -840,6 +829,144 @@ public class SearchDocumentationIT extends ESRestHighLevelClientTestCase {
assertTrue(latch.await(30L, TimeUnit.SECONDS));
}
+
+ public void testMultiSearchTemplateWithInlineScript() throws Exception {
+ indexSearchTestData();
+ RestHighLevelClient client = highLevelClient();
+
+ // tag::multi-search-template-request-inline
+ String [] searchTerms = {"elasticsearch", "logstash", "kibana"};
+
+ MultiSearchTemplateRequest multiRequest = new MultiSearchTemplateRequest(); // <1>
+ for (String searchTerm : searchTerms) {
+ SearchTemplateRequest request = new SearchTemplateRequest(); // <2>
+ request.setRequest(new SearchRequest("posts"));
+
+ request.setScriptType(ScriptType.INLINE);
+ request.setScript(
+ "{" +
+ " \"query\": { \"match\" : { \"{{field}}\" : \"{{value}}\" } }," +
+ " \"size\" : \"{{size}}\"" +
+ "}");
+
+ Map scriptParams = new HashMap<>();
+ scriptParams.put("field", "title");
+ scriptParams.put("value", searchTerm);
+ scriptParams.put("size", 5);
+ request.setScriptParams(scriptParams);
+
+ multiRequest.add(request); // <3>
+ }
+ // end::multi-search-template-request-inline
+
+ // tag::multi-search-template-request-sync
+ MultiSearchTemplateResponse multiResponse = client.multiSearchTemplate(multiRequest, RequestOptions.DEFAULT);
+ // end::multi-search-template-request-sync
+
+ // tag::multi-search-template-response
+ for (Item item : multiResponse.getResponses()) { // <1>
+ if (item.isFailure()) {
+ String error = item.getFailureMessage(); // <2>
+ } else {
+ SearchTemplateResponse searchTemplateResponse = item.getResponse(); // <3>
+ SearchResponse searchResponse = searchTemplateResponse.getResponse();
+ searchResponse.getHits();
+ }
+ }
+ // end::multi-search-template-response
+
+ assertNotNull(multiResponse);
+ assertEquals(searchTerms.length, multiResponse.getResponses().length);
+ assertNotNull(multiResponse.getResponses()[0]);
+ SearchResponse searchResponse = multiResponse.getResponses()[0].getResponse().getResponse();
+ assertTrue(searchResponse.getHits().totalHits > 0);
+
+ }
+
+ public void testMultiSearchTemplateWithStoredScript() throws Exception {
+ indexSearchTestData();
+ RestHighLevelClient client = highLevelClient();
+ RestClient restClient = client();
+
+ registerQueryScript(restClient);
+
+ // tag::multi-search-template-request-stored
+ MultiSearchTemplateRequest multiRequest = new MultiSearchTemplateRequest();
+
+ String [] searchTerms = {"elasticsearch", "logstash", "kibana"};
+ for (String searchTerm : searchTerms) {
+
+ SearchTemplateRequest request = new SearchTemplateRequest();
+ request.setRequest(new SearchRequest("posts"));
+
+ request.setScriptType(ScriptType.STORED);
+ request.setScript("title_search");
+
+ Map params = new HashMap<>();
+ params.put("field", "title");
+ params.put("value", searchTerm);
+ params.put("size", 5);
+ request.setScriptParams(params);
+ multiRequest.add(request);
+ }
+ // end::multi-search-template-request-stored
+
+
+
+
+ // tag::multi-search-template-execute
+ MultiSearchTemplateResponse multiResponse = client.multiSearchTemplate(multiRequest, RequestOptions.DEFAULT);
+ // end::multi-search-template-execute
+
+ assertNotNull(multiResponse);
+ assertEquals(searchTerms.length, multiResponse.getResponses().length);
+ assertNotNull(multiResponse.getResponses()[0]);
+ SearchResponse searchResponse = multiResponse.getResponses()[0].getResponse().getResponse();
+ assertTrue(searchResponse.getHits().totalHits > 0);
+
+ // tag::multi-search-template-execute-listener
+ ActionListener listener = new ActionListener() {
+ @Override
+ public void onResponse(MultiSearchTemplateResponse response) {
+ // <1>
+ }
+
+ @Override
+ public void onFailure(Exception e) {
+ // <2>
+ }
+ };
+ // end::multi-search-template-execute-listener
+
+ // Replace the empty listener by a blocking listener for tests.
+ CountDownLatch latch = new CountDownLatch(1);
+ listener = new LatchedActionListener<>(listener, latch);
+
+ // tag::multi-search-template-execute-async
+ client.multiSearchTemplateAsync(multiRequest, RequestOptions.DEFAULT, listener);
+ // end::multi-search-template-execute-async
+
+ assertTrue(latch.await(30L, TimeUnit.SECONDS));
+ }
+
+ protected void registerQueryScript(RestClient restClient) throws IOException {
+ // tag::register-script
+ Request scriptRequest = new Request("POST", "_scripts/title_search");
+ scriptRequest.setJsonEntity(
+ "{" +
+ " \"script\": {" +
+ " \"lang\": \"mustache\"," +
+ " \"source\": {" +
+ " \"query\": { \"match\" : { \"{{field}}\" : \"{{value}}\" } }," +
+ " \"size\" : \"{{size}}\"" +
+ " }" +
+ " }" +
+ "}");
+ Response scriptResponse = restClient.performRequest(scriptRequest);
+ // end::register-script
+ assertEquals(RestStatus.OK.getStatus(), scriptResponse.getStatusLine().getStatusCode());
+ }
+
public void testExplain() throws Exception {
indexSearchTestData();
diff --git a/docs/java-rest/high-level/search/multi-search-template.asciidoc b/docs/java-rest/high-level/search/multi-search-template.asciidoc
new file mode 100644
index 00000000000..c5133f6614e
--- /dev/null
+++ b/docs/java-rest/high-level/search/multi-search-template.asciidoc
@@ -0,0 +1,81 @@
+[[java-rest-high-multi-search-template]]
+=== Multi-Search-Template API
+
+The `multiSearchTemplate` API executes multiple <>
+requests in a single http request in parallel.
+
+[[java-rest-high-multi-search-template-request]]
+==== Multi-Search-Template Request
+
+The `MultiSearchTemplateRequest` is built empty and you add all of the searches that
+you wish to execute to it:
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/SearchDocumentationIT.java[multi-search-template-request-inline]
+--------------------------------------------------
+<1> Create an empty `MultiSearchTemplateRequest`.
+<2> Create one or more `SearchTemplateRequest` objects and populate them just like you
+would for a regular <>.
+<3> Add the `SearchTemplateRequest` to the `MultiSearchTemplateRequest`.
+
+===== Optional arguments
+
+The multiSearchTemplate's `max_concurrent_searches` request parameter can be used to control
+the maximum number of concurrent searches the multi search api will execute.
+This default is based on the number of data nodes and the default search thread pool size.
+
+[[java-rest-high-multi-search-template-sync]]
+==== Synchronous Execution
+
+The `multiSearchTemplate` method executes `MultiSearchTemplateRequest`s synchronously:
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/SearchDocumentationIT.java[multi-search-template-request-sync]
+--------------------------------------------------
+
+[[java-rest-high-multi-search-template-async]]
+==== Asynchronous Execution
+
+The `multiSearchTemplateAsync` method executes `MultiSearchTemplateRequest`s asynchronously,
+calling the provided `ActionListener` when the response is ready.
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/SearchDocumentationIT.java[multi-search-template-execute-async]
+--------------------------------------------------
+The parameters are the `MultiSearchTemplateRequest` to execute and the `ActionListener` to use when
+the execution completes
+
+The asynchronous method does not block and returns immediately. Once it is
+completed the `ActionListener` is called back using the `onResponse` method
+if the execution successfully completed or using the `onFailure` method if
+it failed.
+
+A typical listener for `MultiSearchTemplateResponse` looks like:
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/SearchDocumentationIT.java[multi-search-template-execute-listener]
+--------------------------------------------------
+<1> Called when the execution is successfully completed.
+<2> Called when the whole `MultiSearchTemplateRequest` fails.
+
+==== MultiSearchTemplateResponse
+
+The `MultiSearchTemplateResponse` that is returned by executing the `multiSearchTemplate` method contains
+a `MultiSearchTemplateResponse.Item` for each `SearchTemplateRequest` in the
+`MultiSearchTemplateRequest`. Each `MultiSearchTemplateResponse.Item` contains an
+exception in `getFailure` if the request failed or a
+<> in `getResponse` if
+the request succeeded:
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/SearchDocumentationIT.java[multi-search-template-response]
+--------------------------------------------------
+<1> An array of responses is returned - one response for each request
+<2> Failed search template requests have error messages
+<3> Successful requests contain a <> in
+`getResponse`.
diff --git a/docs/java-rest/high-level/supported-apis.asciidoc b/docs/java-rest/high-level/supported-apis.asciidoc
index 1a126d82cbb..d2484db1d78 100644
--- a/docs/java-rest/high-level/supported-apis.asciidoc
+++ b/docs/java-rest/high-level/supported-apis.asciidoc
@@ -32,6 +32,7 @@ The Java High Level REST Client supports the following Search APIs:
* <>
* <>
* <>
+* <>
* <>
* <>
* <>
@@ -41,6 +42,7 @@ include::search/search.asciidoc[]
include::search/scroll.asciidoc[]
include::search/multi-search.asciidoc[]
include::search/search-template.asciidoc[]
+include::search/multi-search-template.asciidoc[]
include::search/field-caps.asciidoc[]
include::search/rank-eval.asciidoc[]
include::search/explain.asciidoc[]
diff --git a/modules/lang-mustache/src/main/java/org/elasticsearch/script/mustache/MultiSearchTemplateRequest.java b/modules/lang-mustache/src/main/java/org/elasticsearch/script/mustache/MultiSearchTemplateRequest.java
index bd294db6d29..caa9fa4831a 100644
--- a/modules/lang-mustache/src/main/java/org/elasticsearch/script/mustache/MultiSearchTemplateRequest.java
+++ b/modules/lang-mustache/src/main/java/org/elasticsearch/script/mustache/MultiSearchTemplateRequest.java
@@ -23,13 +23,21 @@ import org.elasticsearch.Version;
import org.elasticsearch.action.ActionRequest;
import org.elasticsearch.action.ActionRequestValidationException;
import org.elasticsearch.action.CompositeIndicesRequest;
+import org.elasticsearch.action.search.MultiSearchRequest;
+import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.support.IndicesOptions;
+import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
+import org.elasticsearch.common.xcontent.ToXContent;
+import org.elasticsearch.common.xcontent.XContent;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
import static org.elasticsearch.action.ValidateActions.addValidationError;
@@ -126,4 +134,39 @@ public class MultiSearchTemplateRequest extends ActionRequest implements Composi
}
out.writeStreamableList(requests);
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ MultiSearchTemplateRequest that = (MultiSearchTemplateRequest) o;
+ return maxConcurrentSearchRequests == that.maxConcurrentSearchRequests &&
+ Objects.equals(requests, that.requests) &&
+ Objects.equals(indicesOptions, that.indicesOptions);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(maxConcurrentSearchRequests, requests, indicesOptions);
+ }
+
+ public static byte[] writeMultiLineFormat(MultiSearchTemplateRequest multiSearchTemplateRequest,
+ XContent xContent) throws IOException {
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+ for (SearchTemplateRequest templateRequest : multiSearchTemplateRequest.requests()) {
+ final SearchRequest searchRequest = templateRequest.getRequest();
+ try (XContentBuilder xContentBuilder = XContentBuilder.builder(xContent)) {
+ MultiSearchRequest.writeSearchRequestParams(searchRequest, xContentBuilder);
+ BytesReference.bytes(xContentBuilder).writeTo(output);
+ }
+ output.write(xContent.streamSeparator());
+ try (XContentBuilder xContentBuilder = XContentBuilder.builder(xContent)) {
+ templateRequest.toXContent(xContentBuilder, ToXContent.EMPTY_PARAMS);
+ BytesReference.bytes(xContentBuilder).writeTo(output);
+ }
+ output.write(xContent.streamSeparator());
+ }
+ return output.toByteArray();
+ }
+
}
diff --git a/modules/lang-mustache/src/main/java/org/elasticsearch/script/mustache/MultiSearchTemplateResponse.java b/modules/lang-mustache/src/main/java/org/elasticsearch/script/mustache/MultiSearchTemplateResponse.java
index b5bc86679ae..74f3bc743ae 100644
--- a/modules/lang-mustache/src/main/java/org/elasticsearch/script/mustache/MultiSearchTemplateResponse.java
+++ b/modules/lang-mustache/src/main/java/org/elasticsearch/script/mustache/MultiSearchTemplateResponse.java
@@ -22,6 +22,7 @@ package org.elasticsearch.script.mustache;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.Version;
import org.elasticsearch.action.ActionResponse;
+import org.elasticsearch.action.search.MultiSearchResponse;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.io.stream.StreamInput;
@@ -31,6 +32,7 @@ import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.ToXContentObject;
import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.XContentParser;
import java.io.IOException;
import java.util.Arrays;
@@ -106,6 +108,13 @@ public class MultiSearchTemplateResponse extends ActionResponse implements Itera
public Exception getFailure() {
return exception;
}
+
+ @Override
+ public String toString() {
+ return "Item [response=" + response + ", exception=" + exception + "]";
+ }
+
+
}
private Item[] items;
@@ -117,7 +126,7 @@ public class MultiSearchTemplateResponse extends ActionResponse implements Itera
public MultiSearchTemplateResponse(Item[] items, long tookInMillis) {
this.items = items;
this.tookInMillis = tookInMillis;
- }
+ }
@Override
public Iterator- iterator() {
@@ -184,6 +193,23 @@ public class MultiSearchTemplateResponse extends ActionResponse implements Itera
static final class Fields {
static final String RESPONSES = "responses";
}
+
+ public static MultiSearchTemplateResponse fromXContext(XContentParser parser) {
+ //The MultiSearchTemplateResponse is identical to the multi search response so we reuse the parsing logic in multi search response
+ MultiSearchResponse mSearchResponse = MultiSearchResponse.fromXContext(parser);
+ org.elasticsearch.action.search.MultiSearchResponse.Item[] responses = mSearchResponse.getResponses();
+ Item[] templateResponses = new Item[responses.length];
+ int i = 0;
+ for (org.elasticsearch.action.search.MultiSearchResponse.Item item : responses) {
+ SearchTemplateResponse stResponse = null;
+ if(item.getResponse() != null){
+ stResponse = new SearchTemplateResponse();
+ stResponse.setResponse(item.getResponse());
+ }
+ templateResponses[i++] = new Item(stResponse, item.getFailure());
+ }
+ return new MultiSearchTemplateResponse(templateResponses, mSearchResponse.getTook().millis());
+ }
@Override
public String toString() {
diff --git a/modules/lang-mustache/src/main/java/org/elasticsearch/script/mustache/SearchTemplateResponse.java b/modules/lang-mustache/src/main/java/org/elasticsearch/script/mustache/SearchTemplateResponse.java
index 500a5a399ef..6d19afbfd6f 100644
--- a/modules/lang-mustache/src/main/java/org/elasticsearch/script/mustache/SearchTemplateResponse.java
+++ b/modules/lang-mustache/src/main/java/org/elasticsearch/script/mustache/SearchTemplateResponse.java
@@ -66,6 +66,11 @@ public class SearchTemplateResponse extends ActionResponse implements StatusToXC
public boolean hasResponse() {
return response != null;
+ }
+
+ @Override
+ public String toString() {
+ return "SearchTemplateResponse [source=" + source + ", response=" + response + "]";
}
@Override
diff --git a/modules/lang-mustache/src/test/java/org/elasticsearch/script/mustache/MultiSearchTemplateRequestTests.java b/modules/lang-mustache/src/test/java/org/elasticsearch/script/mustache/MultiSearchTemplateRequestTests.java
index 1ff54796237..ee967237d3c 100644
--- a/modules/lang-mustache/src/test/java/org/elasticsearch/script/mustache/MultiSearchTemplateRequestTests.java
+++ b/modules/lang-mustache/src/test/java/org/elasticsearch/script/mustache/MultiSearchTemplateRequestTests.java
@@ -19,14 +19,22 @@
package org.elasticsearch.script.mustache;
+import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.common.bytes.BytesArray;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.script.ScriptType;
+import org.elasticsearch.search.Scroll;
+import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.test.StreamsUtils;
import org.elasticsearch.test.rest.FakeRestRequest;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+
import static org.hamcrest.Matchers.arrayContaining;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.nullValue;
@@ -97,5 +105,57 @@ public class MultiSearchTemplateRequestTests extends ESTestCase {
expectThrows(IllegalArgumentException.class, () ->
request.maxConcurrentSearchRequests(randomIntBetween(Integer.MIN_VALUE, 0)));
}
+
+ public void testMultiSearchTemplateToJson() throws Exception {
+ final int numSearchRequests = randomIntBetween(1, 10);
+ MultiSearchTemplateRequest multiSearchTemplateRequest = new MultiSearchTemplateRequest();
+ for (int i = 0; i < numSearchRequests; i++) {
+ // Create a random request.
+ String[] indices = {"test"};
+ SearchRequest searchRequest = new SearchRequest(indices);
+ // scroll is not supported in the current msearch or msearchtemplate api, so unset it:
+ searchRequest.scroll((Scroll) null);
+ // batched reduce size is currently not set-able on a per-request basis as it is a query string parameter only
+ searchRequest.setBatchedReduceSize(SearchRequest.DEFAULT_BATCHED_REDUCE_SIZE);
+ SearchTemplateRequest searchTemplateRequest = new SearchTemplateRequest(searchRequest);
+
+ searchTemplateRequest.setScript("{\"query\": { \"match\" : { \"{{field}}\" : \"{{value}}\" }}}");
+ searchTemplateRequest.setScriptType(ScriptType.INLINE);
+ searchTemplateRequest.setProfile(randomBoolean());
+
+ Map scriptParams = new HashMap<>();
+ scriptParams.put("field", "name");
+ scriptParams.put("value", randomAlphaOfLengthBetween(2, 5));
+ searchTemplateRequest.setScriptParams(scriptParams);
+
+ multiSearchTemplateRequest.add(searchTemplateRequest);
+ }
+
+ //Serialize the request
+ String serialized = toJsonString(multiSearchTemplateRequest);
+
+ //Deserialize the request
+ RestRequest restRequest = new FakeRestRequest.Builder(xContentRegistry())
+ .withContent(new BytesArray(serialized), XContentType.JSON).build();
+ MultiSearchTemplateRequest deser = RestMultiSearchTemplateAction.parseRequest(restRequest, true);
+
+ // For object equality purposes need to set the search requests' source to non-null
+ for (SearchTemplateRequest str : deser.requests()) {
+ SearchRequest sr = str.getRequest();
+ if (sr.source() == null) {
+ sr.source(new SearchSourceBuilder());
+ }
+ }
+ // Compare the deserialized request object with the original request object
+ assertEquals(multiSearchTemplateRequest, deser);
+
+ // Finally, serialize the deserialized request to compare JSON equivalence (in case Object.equals() fails to reveal a discrepancy)
+ assertEquals(serialized, toJsonString(deser));
+ }
+
+ protected String toJsonString(MultiSearchTemplateRequest multiSearchTemplateRequest) throws IOException {
+ byte[] bytes = MultiSearchTemplateRequest.writeMultiLineFormat(multiSearchTemplateRequest, XContentType.JSON.xContent());
+ return new String(bytes, StandardCharsets.UTF_8);
+ }
}
diff --git a/modules/lang-mustache/src/test/java/org/elasticsearch/script/mustache/MultiSearchTemplateResponseTests.java b/modules/lang-mustache/src/test/java/org/elasticsearch/script/mustache/MultiSearchTemplateResponseTests.java
new file mode 100644
index 00000000000..2c67dd4709b
--- /dev/null
+++ b/modules/lang-mustache/src/test/java/org/elasticsearch/script/mustache/MultiSearchTemplateResponseTests.java
@@ -0,0 +1,138 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch 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.elasticsearch.script.mustache;
+
+import org.elasticsearch.ElasticsearchException;
+import org.elasticsearch.action.search.SearchResponse;
+import org.elasticsearch.action.search.ShardSearchFailure;
+import org.elasticsearch.common.Strings;
+import org.elasticsearch.common.xcontent.ToXContent;
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.search.internal.InternalSearchResponse;
+import org.elasticsearch.test.AbstractXContentTestCase;
+
+import java.io.IOException;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.nullValue;
+
+public class MultiSearchTemplateResponseTests extends AbstractXContentTestCase {
+
+ @Override
+ protected MultiSearchTemplateResponse createTestInstance() {
+ int numItems = randomIntBetween(0, 128);
+ long overallTookInMillis = randomNonNegativeLong();
+ MultiSearchTemplateResponse.Item[] items = new MultiSearchTemplateResponse.Item[numItems];
+ for (int i = 0; i < numItems; i++) {
+ // Creating a minimal response is OK, because SearchResponse self
+ // is tested elsewhere.
+ long tookInMillis = randomNonNegativeLong();
+ int totalShards = randomIntBetween(1, Integer.MAX_VALUE);
+ int successfulShards = randomIntBetween(0, totalShards);
+ int skippedShards = totalShards - successfulShards;
+ InternalSearchResponse internalSearchResponse = InternalSearchResponse.empty();
+ SearchResponse.Clusters clusters = new SearchResponse.Clusters(totalShards, successfulShards, skippedShards);
+ SearchTemplateResponse searchTemplateResponse = new SearchTemplateResponse();
+ SearchResponse searchResponse = new SearchResponse(internalSearchResponse, null, totalShards,
+ successfulShards, skippedShards, tookInMillis, ShardSearchFailure.EMPTY_ARRAY, clusters);
+ searchTemplateResponse.setResponse(searchResponse);
+ items[i] = new MultiSearchTemplateResponse.Item(searchTemplateResponse, null);
+ }
+ return new MultiSearchTemplateResponse(items, overallTookInMillis);
+ }
+
+
+ private static MultiSearchTemplateResponse createTestInstanceWithFailures() {
+ int numItems = randomIntBetween(0, 128);
+ long overallTookInMillis = randomNonNegativeLong();
+ MultiSearchTemplateResponse.Item[] items = new MultiSearchTemplateResponse.Item[numItems];
+ for (int i = 0; i < numItems; i++) {
+ if (randomBoolean()) {
+ // Creating a minimal response is OK, because SearchResponse self
+ // is tested elsewhere.
+ long tookInMillis = randomNonNegativeLong();
+ int totalShards = randomIntBetween(1, Integer.MAX_VALUE);
+ int successfulShards = randomIntBetween(0, totalShards);
+ int skippedShards = totalShards - successfulShards;
+ InternalSearchResponse internalSearchResponse = InternalSearchResponse.empty();
+ SearchResponse.Clusters clusters = new SearchResponse.Clusters(totalShards, successfulShards, skippedShards);
+ SearchTemplateResponse searchTemplateResponse = new SearchTemplateResponse();
+ SearchResponse searchResponse = new SearchResponse(internalSearchResponse, null, totalShards,
+ successfulShards, skippedShards, tookInMillis, ShardSearchFailure.EMPTY_ARRAY, clusters);
+ searchTemplateResponse.setResponse(searchResponse);
+ items[i] = new MultiSearchTemplateResponse.Item(searchTemplateResponse, null);
+ } else {
+ items[i] = new MultiSearchTemplateResponse.Item(null, new ElasticsearchException("an error"));
+ }
+ }
+ return new MultiSearchTemplateResponse(items, overallTookInMillis);
+ }
+
+ @Override
+ protected MultiSearchTemplateResponse doParseInstance(XContentParser parser) throws IOException {
+ return MultiSearchTemplateResponse.fromXContext(parser);
+ }
+
+ @Override
+ protected boolean supportsUnknownFields() {
+ return true;
+ }
+
+ protected Predicate getRandomFieldsExcludeFilterWhenResultHasErrors() {
+ return field -> field.startsWith("responses");
+ }
+
+ @Override
+ protected void assertEqualInstances(MultiSearchTemplateResponse expectedInstance, MultiSearchTemplateResponse newInstance) {
+ assertThat(newInstance.getTook(), equalTo(expectedInstance.getTook()));
+ assertThat(newInstance.getResponses().length, equalTo(expectedInstance.getResponses().length));
+ for (int i = 0; i < expectedInstance.getResponses().length; i++) {
+ MultiSearchTemplateResponse.Item expectedItem = expectedInstance.getResponses()[i];
+ MultiSearchTemplateResponse.Item actualItem = newInstance.getResponses()[i];
+ if (expectedItem.isFailure()) {
+ assertThat(actualItem.getResponse(), nullValue());
+ assertThat(actualItem.getFailureMessage(), containsString(expectedItem.getFailureMessage()));
+ } else {
+ assertThat(actualItem.getResponse().toString(), equalTo(expectedItem.getResponse().toString()));
+ assertThat(actualItem.getFailure(), nullValue());
+ }
+ }
+ }
+
+ /**
+ * Test parsing {@link MultiSearchTemplateResponse} with inner failures as they don't support asserting on xcontent equivalence, given
+ * exceptions are not parsed back as the same original class. We run the usual {@link AbstractXContentTestCase#testFromXContent()}
+ * without failures, and this other test with failures where we disable asserting on xcontent equivalence at the end.
+ */
+ public void testFromXContentWithFailures() throws IOException {
+ Supplier instanceSupplier = MultiSearchTemplateResponseTests::createTestInstanceWithFailures;
+ //with random fields insertion in the inner exceptions, some random stuff may be parsed back as metadata,
+ //but that does not bother our assertions, as we only want to test that we don't break.
+ boolean supportsUnknownFields = true;
+ //exceptions are not of the same type whenever parsed back
+ boolean assertToXContentEquivalence = false;
+ AbstractXContentTestCase.testFromXContent(NUMBER_OF_TEST_RUNS, instanceSupplier, supportsUnknownFields, Strings.EMPTY_ARRAY,
+ getRandomFieldsExcludeFilterWhenResultHasErrors(), this::createParser, this::doParseInstance,
+ this::assertEqualInstances, assertToXContentEquivalence, ToXContent.EMPTY_PARAMS);
+ }
+
+}
diff --git a/server/src/main/java/org/elasticsearch/action/search/MultiSearchRequest.java b/server/src/main/java/org/elasticsearch/action/search/MultiSearchRequest.java
index c7c711253ba..056c4c29c7a 100644
--- a/server/src/main/java/org/elasticsearch/action/search/MultiSearchRequest.java
+++ b/server/src/main/java/org/elasticsearch/action/search/MultiSearchRequest.java
@@ -270,42 +270,7 @@ public class MultiSearchRequest extends ActionRequest implements CompositeIndice
ByteArrayOutputStream output = new ByteArrayOutputStream();
for (SearchRequest request : multiSearchRequest.requests()) {
try (XContentBuilder xContentBuilder = XContentBuilder.builder(xContent)) {
- xContentBuilder.startObject();
- if (request.indices() != null) {
- xContentBuilder.field("index", request.indices());
- }
- if (request.indicesOptions() != null && request.indicesOptions() != SearchRequest.DEFAULT_INDICES_OPTIONS) {
- if (request.indicesOptions().expandWildcardsOpen() && request.indicesOptions().expandWildcardsClosed()) {
- xContentBuilder.field("expand_wildcards", "all");
- } else if (request.indicesOptions().expandWildcardsOpen()) {
- xContentBuilder.field("expand_wildcards", "open");
- } else if (request.indicesOptions().expandWildcardsClosed()) {
- xContentBuilder.field("expand_wildcards", "closed");
- } else {
- xContentBuilder.field("expand_wildcards", "none");
- }
- xContentBuilder.field("ignore_unavailable", request.indicesOptions().ignoreUnavailable());
- xContentBuilder.field("allow_no_indices", request.indicesOptions().allowNoIndices());
- }
- if (request.types() != null) {
- xContentBuilder.field("types", request.types());
- }
- if (request.searchType() != null) {
- xContentBuilder.field("search_type", request.searchType().name().toLowerCase(Locale.ROOT));
- }
- if (request.requestCache() != null) {
- xContentBuilder.field("request_cache", request.requestCache());
- }
- if (request.preference() != null) {
- xContentBuilder.field("preference", request.preference());
- }
- if (request.routing() != null) {
- xContentBuilder.field("routing", request.routing());
- }
- if (request.allowPartialSearchResults() != null) {
- xContentBuilder.field("allow_partial_search_results", request.allowPartialSearchResults());
- }
- xContentBuilder.endObject();
+ writeSearchRequestParams(request, xContentBuilder);
BytesReference.bytes(xContentBuilder).writeTo(output);
}
output.write(xContent.streamSeparator());
@@ -322,5 +287,44 @@ public class MultiSearchRequest extends ActionRequest implements CompositeIndice
}
return output.toByteArray();
}
+
+ public static void writeSearchRequestParams(SearchRequest request, XContentBuilder xContentBuilder) throws IOException {
+ xContentBuilder.startObject();
+ if (request.indices() != null) {
+ xContentBuilder.field("index", request.indices());
+ }
+ if (request.indicesOptions() != null && request.indicesOptions() != SearchRequest.DEFAULT_INDICES_OPTIONS) {
+ if (request.indicesOptions().expandWildcardsOpen() && request.indicesOptions().expandWildcardsClosed()) {
+ xContentBuilder.field("expand_wildcards", "all");
+ } else if (request.indicesOptions().expandWildcardsOpen()) {
+ xContentBuilder.field("expand_wildcards", "open");
+ } else if (request.indicesOptions().expandWildcardsClosed()) {
+ xContentBuilder.field("expand_wildcards", "closed");
+ } else {
+ xContentBuilder.field("expand_wildcards", "none");
+ }
+ xContentBuilder.field("ignore_unavailable", request.indicesOptions().ignoreUnavailable());
+ xContentBuilder.field("allow_no_indices", request.indicesOptions().allowNoIndices());
+ }
+ if (request.types() != null) {
+ xContentBuilder.field("types", request.types());
+ }
+ if (request.searchType() != null) {
+ xContentBuilder.field("search_type", request.searchType().name().toLowerCase(Locale.ROOT));
+ }
+ if (request.requestCache() != null) {
+ xContentBuilder.field("request_cache", request.requestCache());
+ }
+ if (request.preference() != null) {
+ xContentBuilder.field("preference", request.preference());
+ }
+ if (request.routing() != null) {
+ xContentBuilder.field("routing", request.routing());
+ }
+ if (request.allowPartialSearchResults() != null) {
+ xContentBuilder.field("allow_partial_search_results", request.allowPartialSearchResults());
+ }
+ xContentBuilder.endObject();
+ }
}
diff --git a/server/src/main/java/org/elasticsearch/action/search/SearchRequest.java b/server/src/main/java/org/elasticsearch/action/search/SearchRequest.java
index a390538ec29..e67517c4852 100644
--- a/server/src/main/java/org/elasticsearch/action/search/SearchRequest.java
+++ b/server/src/main/java/org/elasticsearch/action/search/SearchRequest.java
@@ -60,6 +60,7 @@ public final class SearchRequest extends ActionRequest implements IndicesRequest
private static final ToXContent.Params FORMAT_PARAMS = new ToXContent.MapParams(Collections.singletonMap("pretty", "false"));
public static final int DEFAULT_PRE_FILTER_SHARD_SIZE = 128;
+ public static final int DEFAULT_BATCHED_REDUCE_SIZE = 512;
private SearchType searchType = SearchType.DEFAULT;
@@ -79,7 +80,7 @@ public final class SearchRequest extends ActionRequest implements IndicesRequest
private Scroll scroll;
- private int batchedReduceSize = 512;
+ private int batchedReduceSize = DEFAULT_BATCHED_REDUCE_SIZE;
private int maxConcurrentShardRequests = 0;
diff --git a/server/src/main/java/org/elasticsearch/action/search/SearchResponse.java b/server/src/main/java/org/elasticsearch/action/search/SearchResponse.java
index 9ad8a20cb17..2a97798764e 100644
--- a/server/src/main/java/org/elasticsearch/action/search/SearchResponse.java
+++ b/server/src/main/java/org/elasticsearch/action/search/SearchResponse.java
@@ -422,7 +422,7 @@ public class SearchResponse extends ActionResponse implements StatusToXContentOb
private final int successful;
private final int skipped;
- Clusters(int total, int successful, int skipped) {
+ public Clusters(int total, int successful, int skipped) {
assert total >= 0 && successful >= 0 && skipped >= 0
: "total: " + total + " successful: " + successful + " skipped: " + skipped;
assert successful <= total && skipped == total - successful
diff --git a/server/src/main/java/org/elasticsearch/action/search/SearchType.java b/server/src/main/java/org/elasticsearch/action/search/SearchType.java
index b8001204087..910bc3d676a 100644
--- a/server/src/main/java/org/elasticsearch/action/search/SearchType.java
+++ b/server/src/main/java/org/elasticsearch/action/search/SearchType.java
@@ -50,6 +50,11 @@ public enum SearchType {
*/
public static final SearchType DEFAULT = QUERY_THEN_FETCH;
+ /**
+ * Non-deprecated types
+ */
+ public static final SearchType [] CURRENTLY_SUPPORTED = {QUERY_THEN_FETCH, DFS_QUERY_THEN_FETCH};
+
private byte id;
SearchType(byte id) {
@@ -94,4 +99,5 @@ public enum SearchType {
throw new IllegalArgumentException("No search type for [" + searchType + "]");
}
}
+
}
diff --git a/server/src/test/java/org/elasticsearch/action/search/MultiSearchResponseTests.java b/server/src/test/java/org/elasticsearch/action/search/MultiSearchResponseTests.java
index 4f1fa4cf061..4bd4406d81c 100644
--- a/server/src/test/java/org/elasticsearch/action/search/MultiSearchResponseTests.java
+++ b/server/src/test/java/org/elasticsearch/action/search/MultiSearchResponseTests.java
@@ -19,50 +19,43 @@
package org.elasticsearch.action.search;
import org.elasticsearch.ElasticsearchException;
-import org.elasticsearch.common.bytes.BytesReference;
+import org.elasticsearch.common.Strings;
import org.elasticsearch.common.xcontent.ToXContent;
-import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentParser;
-import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.search.internal.InternalSearchResponse;
-import org.elasticsearch.test.ESTestCase;
+import org.elasticsearch.test.AbstractXContentTestCase;
import java.io.IOException;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.nullValue;
-public class MultiSearchResponseTests extends ESTestCase {
+public class MultiSearchResponseTests extends AbstractXContentTestCase {
- public void testFromXContent() throws IOException {
- for (int runs = 0; runs < 20; runs++) {
- MultiSearchResponse expected = createTestInstance();
- XContentType xContentType = randomFrom(XContentType.values());
- BytesReference shuffled = toShuffledXContent(expected, xContentType, ToXContent.EMPTY_PARAMS, false);
- MultiSearchResponse actual;
- try (XContentParser parser = createParser(XContentFactory.xContent(xContentType), shuffled)) {
- actual = MultiSearchResponse.fromXContext(parser);
- assertThat(parser.nextToken(), nullValue());
- }
-
- assertThat(actual.getTook(), equalTo(expected.getTook()));
- assertThat(actual.getResponses().length, equalTo(expected.getResponses().length));
- for (int i = 0; i < expected.getResponses().length; i++) {
- MultiSearchResponse.Item expectedItem = expected.getResponses()[i];
- MultiSearchResponse.Item actualItem = actual.getResponses()[i];
- if (expectedItem.isFailure()) {
- assertThat(actualItem.getResponse(), nullValue());
- assertThat(actualItem.getFailureMessage(), containsString(expectedItem.getFailureMessage()));
- } else {
- assertThat(actualItem.getResponse().toString(), equalTo(expectedItem.getResponse().toString()));
- assertThat(actualItem.getFailure(), nullValue());
- }
- }
+ @Override
+ protected MultiSearchResponse createTestInstance() {
+ int numItems = randomIntBetween(0, 128);
+ MultiSearchResponse.Item[] items = new MultiSearchResponse.Item[numItems];
+ for (int i = 0; i < numItems; i++) {
+ // Creating a minimal response is OK, because SearchResponse self
+ // is tested elsewhere.
+ long tookInMillis = randomNonNegativeLong();
+ int totalShards = randomIntBetween(1, Integer.MAX_VALUE);
+ int successfulShards = randomIntBetween(0, totalShards);
+ int skippedShards = totalShards - successfulShards;
+ InternalSearchResponse internalSearchResponse = InternalSearchResponse.empty();
+ SearchResponse.Clusters clusters = new SearchResponse.Clusters(totalShards, successfulShards, skippedShards);
+ SearchResponse searchResponse = new SearchResponse(internalSearchResponse, null, totalShards,
+ successfulShards, skippedShards, tookInMillis, ShardSearchFailure.EMPTY_ARRAY, clusters);
+ items[i] = new MultiSearchResponse.Item(searchResponse, null);
}
+ return new MultiSearchResponse(items, randomNonNegativeLong());
}
- private static MultiSearchResponse createTestInstance() {
+ private static MultiSearchResponse createTestInstanceWithFailures() {
int numItems = randomIntBetween(0, 128);
MultiSearchResponse.Item[] items = new MultiSearchResponse.Item[numItems];
for (int i = 0; i < numItems; i++) {
@@ -85,4 +78,52 @@ public class MultiSearchResponseTests extends ESTestCase {
return new MultiSearchResponse(items, randomNonNegativeLong());
}
+ @Override
+ protected MultiSearchResponse doParseInstance(XContentParser parser) throws IOException {
+ return MultiSearchResponse.fromXContext(parser);
+ }
+
+ @Override
+ protected void assertEqualInstances(MultiSearchResponse expected, MultiSearchResponse actual) {
+ assertThat(actual.getTook(), equalTo(expected.getTook()));
+ assertThat(actual.getResponses().length, equalTo(expected.getResponses().length));
+ for (int i = 0; i < expected.getResponses().length; i++) {
+ MultiSearchResponse.Item expectedItem = expected.getResponses()[i];
+ MultiSearchResponse.Item actualItem = actual.getResponses()[i];
+ if (expectedItem.isFailure()) {
+ assertThat(actualItem.getResponse(), nullValue());
+ assertThat(actualItem.getFailureMessage(), containsString(expectedItem.getFailureMessage()));
+ } else {
+ assertThat(actualItem.getResponse().toString(), equalTo(expectedItem.getResponse().toString()));
+ assertThat(actualItem.getFailure(), nullValue());
+ }
+ }
+ }
+
+ @Override
+ protected boolean supportsUnknownFields() {
+ return true;
+ }
+
+ protected Predicate getRandomFieldsExcludeFilterWhenResultHasErrors() {
+ return field -> field.startsWith("responses");
+ }
+
+ /**
+ * Test parsing {@link MultiSearchResponse} with inner failures as they don't support asserting on xcontent equivalence, given that
+ * exceptions are not parsed back as the same original class. We run the usual {@link AbstractXContentTestCase#testFromXContent()}
+ * without failures, and this other test with failures where we disable asserting on xcontent equivalence at the end.
+ */
+ public void testFromXContentWithFailures() throws IOException {
+ Supplier instanceSupplier = MultiSearchResponseTests::createTestInstanceWithFailures;
+ //with random fields insertion in the inner exceptions, some random stuff may be parsed back as metadata,
+ //but that does not bother our assertions, as we only want to test that we don't break.
+ boolean supportsUnknownFields = true;
+ //exceptions are not of the same type whenever parsed back
+ boolean assertToXContentEquivalence = false;
+ AbstractXContentTestCase.testFromXContent(NUMBER_OF_TEST_RUNS, instanceSupplier, supportsUnknownFields, Strings.EMPTY_ARRAY,
+ getRandomFieldsExcludeFilterWhenResultHasErrors(), this::createParser, this::doParseInstance,
+ this::assertEqualInstances, assertToXContentEquivalence, ToXContent.EMPTY_PARAMS);
+ }
+
}