Add MultiSearchTemplate support to High Level Rest client (#30836)
Add MultiSearchTemplate support to High Level Rest client. Addresses part of #27205
This commit is contained in:
parent
48cfb9b0db
commit
09dd19a403
|
@ -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;
|
||||
|
@ -605,6 +606,21 @@ final class RequestConverters {
|
|||
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) &&
|
||||
(getAliasesRequest.aliases() == null || getAliasesRequest.aliases().length == 0)) {
|
||||
|
|
|
@ -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 <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/multi-search-template.html">Multi Search Template API
|
||||
* on elastic.co</a>.
|
||||
*/
|
||||
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 <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/multi-search-template.html">Multi Search Template API
|
||||
* on elastic.co</a>.
|
||||
*/
|
||||
public final void multiSearchTemplateAsync(MultiSearchTemplateRequest multiSearchTemplateRequest,
|
||||
RequestOptions options,
|
||||
ActionListener<MultiSearchTemplateResponse> listener) {
|
||||
performRequestAsyncAndParseEntity(multiSearchTemplateRequest, RequestConverters::multiSearchTemplate,
|
||||
options, MultiSearchTemplateResponse::fromXContext, listener, emptySet());
|
||||
}
|
||||
|
||||
/**
|
||||
* Asynchronously executes a request using the Ranking Evaluation API.
|
||||
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-rank-eval.html">Ranking Evaluation API
|
||||
|
|
|
@ -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;
|
||||
|
@ -1374,6 +1375,52 @@ public class RequestConvertersTests extends ESTestCase {
|
|||
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<String, String> 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<String, Object> 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<SearchTemplateRequest> 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()) {
|
||||
|
|
|
@ -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;
|
||||
|
@ -876,6 +879,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<String, Object> 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<String, Object> 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 {
|
||||
{
|
||||
ExplainRequest explainRequest = new ExplainRequest("index1", "doc", "1");
|
||||
|
|
|
@ -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();
|
||||
|
@ -841,6 +830,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<String, Object> 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<String, Object> 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<MultiSearchTemplateResponse> listener = new ActionListener<MultiSearchTemplateResponse>() {
|
||||
@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();
|
||||
RestHighLevelClient client = highLevelClient();
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
[[java-rest-high-multi-search-template]]
|
||||
=== Multi-Search-Template API
|
||||
|
||||
The `multiSearchTemplate` API executes multiple <<java-rest-high-search-template,`search template`>>
|
||||
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 <<java-rest-high-search-template,`search template`>>.
|
||||
<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
|
||||
<<java-rest-high-search-response,`SearchResponse`>> 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 <<java-rest-high-search-response,`SearchResponse`>> in
|
||||
`getResponse`.
|
|
@ -32,6 +32,7 @@ The Java High Level REST Client supports the following Search APIs:
|
|||
* <<java-rest-high-search-scroll>>
|
||||
* <<java-rest-high-clear-scroll>>
|
||||
* <<java-rest-high-search-template>>
|
||||
* <<java-rest-high-multi-search-template>>
|
||||
* <<java-rest-high-multi-search>>
|
||||
* <<java-rest-high-field-caps>>
|
||||
* <<java-rest-high-rank-eval>>
|
||||
|
@ -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[]
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
@ -185,6 +194,23 @@ public class MultiSearchTemplateResponse extends ActionResponse implements Itera
|
|||
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() {
|
||||
return Strings.toString(this);
|
||||
|
|
|
@ -68,6 +68,11 @@ public class SearchTemplateResponse extends ActionResponse implements StatusToXC
|
|||
return response != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SearchTemplateResponse [source=" + source + ", response=" + response + "]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTo(StreamOutput out) throws IOException {
|
||||
super.writeTo(out);
|
||||
|
|
|
@ -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;
|
||||
|
@ -98,4 +106,56 @@ public class MultiSearchTemplateRequestTests extends ESTestCase {
|
|||
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<String, Object> 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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<MultiSearchTemplateResponse> {
|
||||
|
||||
@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<String> 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<MultiSearchTemplateResponse> 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);
|
||||
}
|
||||
|
||||
}
|
|
@ -270,6 +270,25 @@ public class MultiSearchRequest extends ActionRequest implements CompositeIndice
|
|||
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||
for (SearchRequest request : multiSearchRequest.requests()) {
|
||||
try (XContentBuilder xContentBuilder = XContentBuilder.builder(xContent)) {
|
||||
writeSearchRequestParams(request, xContentBuilder);
|
||||
BytesReference.bytes(xContentBuilder).writeTo(output);
|
||||
}
|
||||
output.write(xContent.streamSeparator());
|
||||
try (XContentBuilder xContentBuilder = XContentBuilder.builder(xContent)) {
|
||||
if (request.source() != null) {
|
||||
request.source().toXContent(xContentBuilder, ToXContent.EMPTY_PARAMS);
|
||||
} else {
|
||||
xContentBuilder.startObject();
|
||||
xContentBuilder.endObject();
|
||||
}
|
||||
BytesReference.bytes(xContentBuilder).writeTo(output);
|
||||
}
|
||||
output.write(xContent.streamSeparator());
|
||||
}
|
||||
return output.toByteArray();
|
||||
}
|
||||
|
||||
public static void writeSearchRequestParams(SearchRequest request, XContentBuilder xContentBuilder) throws IOException {
|
||||
xContentBuilder.startObject();
|
||||
if (request.indices() != null) {
|
||||
xContentBuilder.field("index", request.indices());
|
||||
|
@ -306,21 +325,6 @@ public class MultiSearchRequest extends ActionRequest implements CompositeIndice
|
|||
xContentBuilder.field("allow_partial_search_results", request.allowPartialSearchResults());
|
||||
}
|
||||
xContentBuilder.endObject();
|
||||
BytesReference.bytes(xContentBuilder).writeTo(output);
|
||||
}
|
||||
output.write(xContent.streamSeparator());
|
||||
try (XContentBuilder xContentBuilder = XContentBuilder.builder(xContent)) {
|
||||
if (request.source() != null) {
|
||||
request.source().toXContent(xContentBuilder, ToXContent.EMPTY_PARAMS);
|
||||
} else {
|
||||
xContentBuilder.startObject();
|
||||
xContentBuilder.endObject();
|
||||
}
|
||||
BytesReference.bytes(xContentBuilder).writeTo(output);
|
||||
}
|
||||
output.write(xContent.streamSeparator());
|
||||
}
|
||||
return output.toByteArray();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 + "]");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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<MultiSearchResponse> {
|
||||
|
||||
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());
|
||||
@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());
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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<String> 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<MultiSearchResponse> 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue