mirror of
https://github.com/honeymoose/OpenSearch.git
synced 2025-03-25 01:19:02 +00:00
Add support for search templates to the high-level REST client. (#30473)
This commit is contained in:
parent
03dd2ab499
commit
4f9dd37169
@ -40,6 +40,7 @@ dependencies {
|
||||
compile "org.elasticsearch.plugin:parent-join-client:${version}"
|
||||
compile "org.elasticsearch.plugin:aggs-matrix-stats-client:${version}"
|
||||
compile "org.elasticsearch.plugin:rank-eval-client:${version}"
|
||||
compile "org.elasticsearch.plugin:lang-mustache-client:${version}"
|
||||
|
||||
testCompile "org.elasticsearch.client:test:${version}"
|
||||
testCompile "org.elasticsearch.test:framework:${version}"
|
||||
|
@ -80,6 +80,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.SearchTemplateRequest;
|
||||
import org.elasticsearch.search.fetch.subphase.FetchSourceContext;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
@ -458,6 +459,15 @@ final class RequestConverters {
|
||||
Request request = new Request(HttpPost.METHOD_NAME, endpoint(searchRequest.indices(), searchRequest.types(), "_search"));
|
||||
|
||||
Params params = new Params(request);
|
||||
addSearchRequestParams(params, searchRequest);
|
||||
|
||||
if (searchRequest.source() != null) {
|
||||
request.setEntity(createEntity(searchRequest.source(), REQUEST_BODY_CONTENT_TYPE));
|
||||
}
|
||||
return request;
|
||||
}
|
||||
|
||||
private static void addSearchRequestParams(Params params, SearchRequest searchRequest) {
|
||||
params.putParam(RestSearchAction.TYPED_KEYS_PARAM, "true");
|
||||
params.withRouting(searchRequest.routing());
|
||||
params.withPreference(searchRequest.preference());
|
||||
@ -473,11 +483,6 @@ final class RequestConverters {
|
||||
if (searchRequest.scroll() != null) {
|
||||
params.putParam("scroll", searchRequest.scroll().keepAlive());
|
||||
}
|
||||
|
||||
if (searchRequest.source() != null) {
|
||||
request.setEntity(createEntity(searchRequest.source(), REQUEST_BODY_CONTENT_TYPE));
|
||||
}
|
||||
return request;
|
||||
}
|
||||
|
||||
static Request searchScroll(SearchScrollRequest searchScrollRequest) throws IOException {
|
||||
@ -507,6 +512,24 @@ final class RequestConverters {
|
||||
return request;
|
||||
}
|
||||
|
||||
static Request searchTemplate(SearchTemplateRequest searchTemplateRequest) throws IOException {
|
||||
Request request;
|
||||
|
||||
if (searchTemplateRequest.isSimulate()) {
|
||||
request = new Request(HttpGet.METHOD_NAME, "_render/template");
|
||||
} else {
|
||||
SearchRequest searchRequest = searchTemplateRequest.getRequest();
|
||||
String endpoint = endpoint(searchRequest.indices(), searchRequest.types(), "_search/template");
|
||||
request = new Request(HttpGet.METHOD_NAME, endpoint);
|
||||
|
||||
Params params = new Params(request);
|
||||
addSearchRequestParams(params, searchRequest);
|
||||
}
|
||||
|
||||
request.setEntity(createEntity(searchTemplateRequest, REQUEST_BODY_CONTENT_TYPE));
|
||||
return request;
|
||||
}
|
||||
|
||||
static Request existsAlias(GetAliasesRequest getAliasesRequest) {
|
||||
if ((getAliasesRequest.indices() == null || getAliasesRequest.indices().length == 0) &&
|
||||
(getAliasesRequest.aliases() == null || getAliasesRequest.aliases().length == 0)) {
|
||||
|
@ -64,6 +64,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.SearchTemplateRequest;
|
||||
import org.elasticsearch.script.mustache.SearchTemplateResponse;
|
||||
import org.elasticsearch.search.aggregations.Aggregation;
|
||||
import org.elasticsearch.search.aggregations.bucket.adjacency.AdjacencyMatrixAggregationBuilder;
|
||||
import org.elasticsearch.search.aggregations.bucket.adjacency.ParsedAdjacencyMatrix;
|
||||
@ -501,6 +503,32 @@ public class RestHighLevelClient implements Closeable {
|
||||
listener, emptySet(), headers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a request using the Search Template API.
|
||||
*
|
||||
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-template.html">Search Template API
|
||||
* on elastic.co</a>.
|
||||
*/
|
||||
public final SearchTemplateResponse searchTemplate(SearchTemplateRequest searchTemplateRequest,
|
||||
Header... headers) throws IOException {
|
||||
return performRequestAndParseEntity(searchTemplateRequest, RequestConverters::searchTemplate,
|
||||
SearchTemplateResponse::fromXContent, emptySet(), headers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asynchronously executes a request using the Search Template API
|
||||
*
|
||||
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-template.html">Search Template API
|
||||
* on elastic.co</a>.
|
||||
*/
|
||||
public final void searchTemplateAsync(SearchTemplateRequest searchTemplateRequest,
|
||||
ActionListener<SearchTemplateResponse> listener,
|
||||
Header... headers) {
|
||||
performRequestAsyncAndParseEntity(searchTemplateRequest, RequestConverters::searchTemplate,
|
||||
SearchTemplateResponse::fromXContent, listener, emptySet(), headers);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Executes a request using the Ranking Evaluation API.
|
||||
*
|
||||
|
@ -95,6 +95,8 @@ import org.elasticsearch.index.rankeval.RankEvalSpec;
|
||||
import org.elasticsearch.index.rankeval.RatedRequest;
|
||||
import org.elasticsearch.index.rankeval.RestRankEvalAction;
|
||||
import org.elasticsearch.rest.action.search.RestSearchAction;
|
||||
import org.elasticsearch.script.ScriptType;
|
||||
import org.elasticsearch.script.mustache.SearchTemplateRequest;
|
||||
import org.elasticsearch.search.Scroll;
|
||||
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
|
||||
import org.elasticsearch.search.aggregations.support.ValueType;
|
||||
@ -1011,36 +1013,7 @@ public class RequestConvertersTests extends ESTestCase {
|
||||
searchRequest.types(types);
|
||||
|
||||
Map<String, String> expectedParams = new HashMap<>();
|
||||
expectedParams.put(RestSearchAction.TYPED_KEYS_PARAM, "true");
|
||||
if (randomBoolean()) {
|
||||
searchRequest.routing(randomAlphaOfLengthBetween(3, 10));
|
||||
expectedParams.put("routing", searchRequest.routing());
|
||||
}
|
||||
if (randomBoolean()) {
|
||||
searchRequest.preference(randomAlphaOfLengthBetween(3, 10));
|
||||
expectedParams.put("preference", searchRequest.preference());
|
||||
}
|
||||
if (randomBoolean()) {
|
||||
searchRequest.searchType(randomFrom(SearchType.values()));
|
||||
}
|
||||
expectedParams.put("search_type", searchRequest.searchType().name().toLowerCase(Locale.ROOT));
|
||||
if (randomBoolean()) {
|
||||
searchRequest.requestCache(randomBoolean());
|
||||
expectedParams.put("request_cache", Boolean.toString(searchRequest.requestCache()));
|
||||
}
|
||||
if (randomBoolean()) {
|
||||
searchRequest.allowPartialSearchResults(randomBoolean());
|
||||
expectedParams.put("allow_partial_search_results", Boolean.toString(searchRequest.allowPartialSearchResults()));
|
||||
}
|
||||
if (randomBoolean()) {
|
||||
searchRequest.setBatchedReduceSize(randomIntBetween(2, Integer.MAX_VALUE));
|
||||
}
|
||||
expectedParams.put("batched_reduce_size", Integer.toString(searchRequest.getBatchedReduceSize()));
|
||||
if (randomBoolean()) {
|
||||
searchRequest.scroll(randomTimeValue());
|
||||
expectedParams.put("scroll", searchRequest.scroll().keepAlive().getStringRep());
|
||||
}
|
||||
|
||||
setRandomSearchParams(searchRequest, expectedParams);
|
||||
setRandomIndicesOptions(searchRequest::indicesOptions, searchRequest::indicesOptions, expectedParams);
|
||||
|
||||
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
|
||||
@ -1189,6 +1162,65 @@ public class RequestConvertersTests extends ESTestCase {
|
||||
assertEquals(REQUEST_BODY_CONTENT_TYPE.mediaTypeWithoutParameters(), request.getEntity().getContentType().getValue());
|
||||
}
|
||||
|
||||
public void testSearchTemplate() throws Exception {
|
||||
// Create a random request.
|
||||
String[] indices = randomIndicesNames(0, 5);
|
||||
SearchRequest searchRequest = new SearchRequest(indices);
|
||||
|
||||
Map<String, String> expectedParams = new HashMap<>();
|
||||
setRandomSearchParams(searchRequest, expectedParams);
|
||||
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", "soren");
|
||||
searchTemplateRequest.setScriptParams(scriptParams);
|
||||
|
||||
// Verify that the resulting REST request looks as expected.
|
||||
Request request = RequestConverters.searchTemplate(searchTemplateRequest);
|
||||
StringJoiner endpoint = new StringJoiner("/", "/", "");
|
||||
String index = String.join(",", indices);
|
||||
if (Strings.hasLength(index)) {
|
||||
endpoint.add(index);
|
||||
}
|
||||
endpoint.add("_search/template");
|
||||
|
||||
assertEquals(HttpGet.METHOD_NAME, request.getMethod());
|
||||
assertEquals(endpoint.toString(), request.getEndpoint());
|
||||
assertEquals(expectedParams, request.getParameters());
|
||||
assertToXContentBody(searchTemplateRequest, request.getEntity());
|
||||
}
|
||||
|
||||
public void testRenderSearchTemplate() throws Exception {
|
||||
// Create a simple request.
|
||||
SearchTemplateRequest searchTemplateRequest = new SearchTemplateRequest();
|
||||
searchTemplateRequest.setSimulate(true); // Setting simulate true means the template should only be rendered.
|
||||
|
||||
searchTemplateRequest.setScript("template1");
|
||||
searchTemplateRequest.setScriptType(ScriptType.STORED);
|
||||
searchTemplateRequest.setProfile(randomBoolean());
|
||||
|
||||
Map<String, Object> scriptParams = new HashMap<>();
|
||||
scriptParams.put("field", "name");
|
||||
scriptParams.put("value", "soren");
|
||||
searchTemplateRequest.setScriptParams(scriptParams);
|
||||
|
||||
// Verify that the resulting REST request looks as expected.
|
||||
Request request = RequestConverters.searchTemplate(searchTemplateRequest);
|
||||
String endpoint = "_render/template";
|
||||
|
||||
assertEquals(HttpGet.METHOD_NAME, request.getMethod());
|
||||
assertEquals(endpoint, request.getEndpoint());
|
||||
assertEquals(Collections.emptyMap(), request.getParameters());
|
||||
assertToXContentBody(searchTemplateRequest, request.getEntity());
|
||||
}
|
||||
|
||||
public void testExistsAlias() {
|
||||
GetAliasesRequest getAliasesRequest = new GetAliasesRequest();
|
||||
String[] indices = randomBoolean() ? null : randomIndicesNames(0, 5);
|
||||
@ -1662,6 +1694,39 @@ public class RequestConvertersTests extends ESTestCase {
|
||||
}
|
||||
}
|
||||
|
||||
private static void setRandomSearchParams(SearchRequest searchRequest,
|
||||
Map<String, String> expectedParams) {
|
||||
expectedParams.put(RestSearchAction.TYPED_KEYS_PARAM, "true");
|
||||
if (randomBoolean()) {
|
||||
searchRequest.routing(randomAlphaOfLengthBetween(3, 10));
|
||||
expectedParams.put("routing", searchRequest.routing());
|
||||
}
|
||||
if (randomBoolean()) {
|
||||
searchRequest.preference(randomAlphaOfLengthBetween(3, 10));
|
||||
expectedParams.put("preference", searchRequest.preference());
|
||||
}
|
||||
if (randomBoolean()) {
|
||||
searchRequest.searchType(randomFrom(SearchType.values()));
|
||||
}
|
||||
expectedParams.put("search_type", searchRequest.searchType().name().toLowerCase(Locale.ROOT));
|
||||
if (randomBoolean()) {
|
||||
searchRequest.requestCache(randomBoolean());
|
||||
expectedParams.put("request_cache", Boolean.toString(searchRequest.requestCache()));
|
||||
}
|
||||
if (randomBoolean()) {
|
||||
searchRequest.allowPartialSearchResults(randomBoolean());
|
||||
expectedParams.put("allow_partial_search_results", Boolean.toString(searchRequest.allowPartialSearchResults()));
|
||||
}
|
||||
if (randomBoolean()) {
|
||||
searchRequest.setBatchedReduceSize(randomIntBetween(2, Integer.MAX_VALUE));
|
||||
}
|
||||
expectedParams.put("batched_reduce_size", Integer.toString(searchRequest.getBatchedReduceSize()));
|
||||
if (randomBoolean()) {
|
||||
searchRequest.scroll(randomTimeValue());
|
||||
expectedParams.put("scroll", searchRequest.scroll().keepAlive().getStringRep());
|
||||
}
|
||||
}
|
||||
|
||||
private static void setRandomIndicesOptions(Consumer<IndicesOptions> setter, Supplier<IndicesOptions> getter,
|
||||
Map<String, String> expectedParams) {
|
||||
|
||||
|
@ -38,8 +38,11 @@ import org.elasticsearch.action.search.SearchRequest;
|
||||
import org.elasticsearch.action.search.SearchResponse;
|
||||
import org.elasticsearch.action.search.SearchScrollRequest;
|
||||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.common.bytes.BytesReference;
|
||||
import org.elasticsearch.common.unit.TimeValue;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.common.xcontent.XContentFactory;
|
||||
import org.elasticsearch.common.xcontent.XContentType;
|
||||
import org.elasticsearch.index.query.MatchQueryBuilder;
|
||||
import org.elasticsearch.index.query.ScriptQueryBuilder;
|
||||
import org.elasticsearch.index.query.TermsQueryBuilder;
|
||||
@ -48,6 +51,8 @@ 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.SearchTemplateRequest;
|
||||
import org.elasticsearch.script.mustache.SearchTemplateResponse;
|
||||
import org.elasticsearch.search.SearchHit;
|
||||
import org.elasticsearch.search.aggregations.BucketOrder;
|
||||
import org.elasticsearch.search.aggregations.bucket.range.Range;
|
||||
@ -69,10 +74,12 @@ import org.junit.Before;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
|
||||
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertToXContentEquivalent;
|
||||
import static org.hamcrest.Matchers.both;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.either;
|
||||
@ -733,6 +740,103 @@ public class SearchIT extends ESRestHighLevelClientTestCase {
|
||||
assertThat(multiSearchResponse.getResponses()[1].getResponse(), nullValue());
|
||||
}
|
||||
|
||||
public void testSearchTemplate() throws IOException {
|
||||
SearchTemplateRequest searchTemplateRequest = new SearchTemplateRequest();
|
||||
searchTemplateRequest.setRequest(new SearchRequest("index"));
|
||||
|
||||
searchTemplateRequest.setScriptType(ScriptType.INLINE);
|
||||
searchTemplateRequest.setScript(
|
||||
"{" +
|
||||
" \"query\": {" +
|
||||
" \"match\": {" +
|
||||
" \"num\": {{number}}" +
|
||||
" }" +
|
||||
" }" +
|
||||
"}");
|
||||
|
||||
Map<String, Object> scriptParams = new HashMap<>();
|
||||
scriptParams.put("number", 10);
|
||||
searchTemplateRequest.setScriptParams(scriptParams);
|
||||
|
||||
searchTemplateRequest.setExplain(true);
|
||||
searchTemplateRequest.setProfile(true);
|
||||
|
||||
SearchTemplateResponse searchTemplateResponse = execute(searchTemplateRequest,
|
||||
highLevelClient()::searchTemplate,
|
||||
highLevelClient()::searchTemplateAsync);
|
||||
|
||||
assertNull(searchTemplateResponse.getSource());
|
||||
|
||||
SearchResponse searchResponse = searchTemplateResponse.getResponse();
|
||||
assertNotNull(searchResponse);
|
||||
|
||||
assertEquals(1, searchResponse.getHits().totalHits);
|
||||
assertEquals(1, searchResponse.getHits().getHits().length);
|
||||
assertThat(searchResponse.getHits().getMaxScore(), greaterThan(0f));
|
||||
|
||||
SearchHit hit = searchResponse.getHits().getHits()[0];
|
||||
assertNotNull(hit.getExplanation());
|
||||
|
||||
assertFalse(searchResponse.getProfileResults().isEmpty());
|
||||
}
|
||||
|
||||
public void testNonExistentSearchTemplate() {
|
||||
SearchTemplateRequest searchTemplateRequest = new SearchTemplateRequest();
|
||||
searchTemplateRequest.setRequest(new SearchRequest("index"));
|
||||
|
||||
searchTemplateRequest.setScriptType(ScriptType.STORED);
|
||||
searchTemplateRequest.setScript("non-existent");
|
||||
searchTemplateRequest.setScriptParams(Collections.emptyMap());
|
||||
|
||||
ElasticsearchStatusException exception = expectThrows(ElasticsearchStatusException.class,
|
||||
() -> execute(searchTemplateRequest,
|
||||
highLevelClient()::searchTemplate,
|
||||
highLevelClient()::searchTemplateAsync));
|
||||
|
||||
assertEquals(RestStatus.NOT_FOUND, exception.status());
|
||||
}
|
||||
|
||||
public void testRenderSearchTemplate() throws IOException {
|
||||
SearchTemplateRequest searchTemplateRequest = new SearchTemplateRequest();
|
||||
|
||||
searchTemplateRequest.setScriptType(ScriptType.INLINE);
|
||||
searchTemplateRequest.setScript(
|
||||
"{" +
|
||||
" \"query\": {" +
|
||||
" \"match\": {" +
|
||||
" \"num\": {{number}}" +
|
||||
" }" +
|
||||
" }" +
|
||||
"}");
|
||||
|
||||
Map<String, Object> scriptParams = new HashMap<>();
|
||||
scriptParams.put("number", 10);
|
||||
searchTemplateRequest.setScriptParams(scriptParams);
|
||||
|
||||
// Setting simulate true causes the template to only be rendered.
|
||||
searchTemplateRequest.setSimulate(true);
|
||||
|
||||
SearchTemplateResponse searchTemplateResponse = execute(searchTemplateRequest,
|
||||
highLevelClient()::searchTemplate,
|
||||
highLevelClient()::searchTemplateAsync);
|
||||
assertNull(searchTemplateResponse.getResponse());
|
||||
|
||||
BytesReference expectedSource = BytesReference.bytes(
|
||||
XContentFactory.jsonBuilder()
|
||||
.startObject()
|
||||
.startObject("query")
|
||||
.startObject("match")
|
||||
.field("num", 10)
|
||||
.endObject()
|
||||
.endObject()
|
||||
.endObject());
|
||||
|
||||
BytesReference actualSource = searchTemplateResponse.getSource();
|
||||
assertNotNull(actualSource);
|
||||
|
||||
assertToXContentEquivalent(expectedSource, actualSource, XContentType.JSON);
|
||||
}
|
||||
|
||||
public void testFieldCaps() throws IOException {
|
||||
FieldCapabilitiesRequest request = new FieldCapabilitiesRequest()
|
||||
.indices("index1", "index2")
|
||||
|
@ -41,7 +41,11 @@ import org.elasticsearch.action.search.ShardSearchFailure;
|
||||
import org.elasticsearch.action.support.IndicesOptions;
|
||||
import org.elasticsearch.action.support.WriteRequest;
|
||||
import org.elasticsearch.client.ESRestHighLevelClientTestCase;
|
||||
import org.elasticsearch.client.Request;
|
||||
import org.elasticsearch.client.Response;
|
||||
import org.elasticsearch.client.RestClient;
|
||||
import org.elasticsearch.client.RestHighLevelClient;
|
||||
import org.elasticsearch.common.bytes.BytesReference;
|
||||
import org.elasticsearch.common.text.Text;
|
||||
import org.elasticsearch.common.unit.Fuzziness;
|
||||
import org.elasticsearch.common.unit.TimeValue;
|
||||
@ -60,6 +64,9 @@ import org.elasticsearch.index.rankeval.RatedDocument;
|
||||
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.SearchTemplateRequest;
|
||||
import org.elasticsearch.script.mustache.SearchTemplateResponse;
|
||||
import org.elasticsearch.search.Scroll;
|
||||
import org.elasticsearch.search.SearchHit;
|
||||
import org.elasticsearch.search.SearchHits;
|
||||
@ -92,6 +99,7 @@ import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
@ -706,9 +714,130 @@ public class SearchDocumentationIT extends ESRestHighLevelClientTestCase {
|
||||
}
|
||||
}
|
||||
|
||||
public void testSearchTemplateWithInlineScript() throws Exception {
|
||||
indexSearchTestData();
|
||||
RestHighLevelClient client = highLevelClient();
|
||||
|
||||
// tag::search-template-request-inline
|
||||
SearchTemplateRequest request = new SearchTemplateRequest();
|
||||
request.setRequest(new SearchRequest("posts")); // <1>
|
||||
|
||||
request.setScriptType(ScriptType.INLINE);
|
||||
request.setScript( // <2>
|
||||
"{" +
|
||||
" \"query\": { \"match\" : { \"{{field}}\" : \"{{value}}\" } }," +
|
||||
" \"size\" : \"{{size}}\"" +
|
||||
"}");
|
||||
|
||||
Map<String, Object> scriptParams = new HashMap<>();
|
||||
scriptParams.put("field", "title");
|
||||
scriptParams.put("value", "elasticsearch");
|
||||
scriptParams.put("size", 5);
|
||||
request.setScriptParams(scriptParams); // <3>
|
||||
// end::search-template-request-inline
|
||||
|
||||
// tag::search-template-response
|
||||
SearchTemplateResponse response = client.searchTemplate(request);
|
||||
SearchResponse searchResponse = response.getResponse();
|
||||
// end::search-template-response
|
||||
|
||||
assertNotNull(searchResponse);
|
||||
assertTrue(searchResponse.getHits().totalHits > 0);
|
||||
|
||||
// tag::render-search-template-request
|
||||
request.setSimulate(true); // <1>
|
||||
// end::render-search-template-request
|
||||
|
||||
// tag::render-search-template-response
|
||||
SearchTemplateResponse renderResponse = client.searchTemplate(request);
|
||||
BytesReference source = renderResponse.getSource(); // <1>
|
||||
// end::render-search-template-response
|
||||
|
||||
assertNotNull(source);
|
||||
assertEquals((
|
||||
"{" +
|
||||
" \"size\" : \"5\"," +
|
||||
" \"query\": { \"match\" : { \"title\" : \"elasticsearch\" } }" +
|
||||
"}").replaceAll("\\s+", ""), source.utf8ToString());
|
||||
}
|
||||
|
||||
public void testSearchTemplateWithStoredScript() throws Exception {
|
||||
indexSearchTestData();
|
||||
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());
|
||||
|
||||
// tag::search-template-request-stored
|
||||
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", "elasticsearch");
|
||||
params.put("size", 5);
|
||||
request.setScriptParams(params);
|
||||
// end::search-template-request-stored
|
||||
|
||||
// tag::search-template-request-options
|
||||
request.setExplain(true);
|
||||
request.setProfile(true);
|
||||
// end::search-template-request-options
|
||||
|
||||
// tag::search-template-execute
|
||||
SearchTemplateResponse response = client.searchTemplate(request);
|
||||
// end::search-template-execute
|
||||
|
||||
SearchResponse searchResponse = response.getResponse();
|
||||
assertNotNull(searchResponse);
|
||||
assertTrue(searchResponse.getHits().totalHits > 0);
|
||||
|
||||
// tag::search-template-execute-listener
|
||||
ActionListener<SearchTemplateResponse> listener = new ActionListener<SearchTemplateResponse>() {
|
||||
@Override
|
||||
public void onResponse(SearchTemplateResponse response) {
|
||||
// <1>
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Exception e) {
|
||||
// <2>
|
||||
}
|
||||
};
|
||||
// end::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::search-template-execute-async
|
||||
client.searchTemplateAsync(request, listener); // <1>
|
||||
// end::search-template-execute-async
|
||||
|
||||
assertTrue(latch.await(30L, TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
public void testFieldCaps() throws Exception {
|
||||
indexSearchTestData();
|
||||
RestHighLevelClient client = highLevelClient();
|
||||
|
||||
// tag::field-caps-request
|
||||
FieldCapabilitiesRequest request = new FieldCapabilitiesRequest()
|
||||
.fields("user")
|
||||
|
117
docs/java-rest/high-level/search/search-template.asciidoc
Normal file
117
docs/java-rest/high-level/search/search-template.asciidoc
Normal file
@ -0,0 +1,117 @@
|
||||
[[java-rest-high-search-template]]
|
||||
=== Search Template API
|
||||
|
||||
The search template API allows for searches to be executed from a template based
|
||||
on the mustache language, and also for previewing rendered templates.
|
||||
|
||||
[[java-rest-high-search-template-request]]
|
||||
==== Search Template Request
|
||||
|
||||
===== Inline Templates
|
||||
|
||||
In the most basic form of request, the search template is specified inline:
|
||||
|
||||
["source","java",subs="attributes,callouts,macros"]
|
||||
--------------------------------------------------
|
||||
include-tagged::{doc-tests}/SearchDocumentationIT.java[search-template-request-inline]
|
||||
--------------------------------------------------
|
||||
<1> The search is executed against the `posts` index.
|
||||
<2> The template defines the structure of the search source. It is passed
|
||||
as a string because mustache templates are not always valid JSON.
|
||||
<3> Before running the search, the template is rendered with the provided parameters.
|
||||
|
||||
===== Registered Templates
|
||||
|
||||
Search templates can be registered in advance through stored scripts API. Note that
|
||||
the stored scripts API is not yet available in the high-level REST client, so in this
|
||||
example we use the low-level REST client.
|
||||
|
||||
["source","java",subs="attributes,callouts,macros"]
|
||||
--------------------------------------------------
|
||||
include-tagged::{doc-tests}/SearchDocumentationIT.java[register-script]
|
||||
--------------------------------------------------
|
||||
|
||||
Instead of providing an inline script, we can refer to this registered template in the request:
|
||||
|
||||
["source","java",subs="attributes,callouts,macros"]
|
||||
--------------------------------------------------
|
||||
include-tagged::{doc-tests}/SearchDocumentationIT.java[search-template-request-stored]
|
||||
--------------------------------------------------
|
||||
|
||||
===== Rendering Templates
|
||||
|
||||
Given parameter values, a template can be rendered without executing a search:
|
||||
|
||||
["source","java",subs="attributes,callouts,macros"]
|
||||
--------------------------------------------------
|
||||
include-tagged::{doc-tests}/SearchDocumentationIT.java[render-search-template-request]
|
||||
--------------------------------------------------
|
||||
<1> Setting `simulate` to `true` causes the search template to only be rendered.
|
||||
|
||||
Both inline and pre-registered templates can be rendered.
|
||||
|
||||
===== Optional Arguments
|
||||
|
||||
As in standard search requests, the `explain` and `profile` options are supported:
|
||||
|
||||
["source","java",subs="attributes,callouts,macros"]
|
||||
--------------------------------------------------
|
||||
include-tagged::{doc-tests}/SearchDocumentationIT.java[search-template-request-options]
|
||||
--------------------------------------------------
|
||||
|
||||
===== Additional References
|
||||
|
||||
The {ref}/search-template.html[Search Template documentation] contains further examples of how search requests can be templated.
|
||||
|
||||
[[java-rest-high-search-template-sync]]
|
||||
==== Synchronous Execution
|
||||
|
||||
The `searchTemplate` method executes the request synchronously:
|
||||
|
||||
["source","java",subs="attributes,callouts,macros"]
|
||||
--------------------------------------------------
|
||||
include-tagged::{doc-tests}/SearchDocumentationIT.java[search-template-execute]
|
||||
--------------------------------------------------
|
||||
|
||||
==== Asynchronous Execution
|
||||
|
||||
A search template request can be executed asynchronously through the `searchTemplateAsync`
|
||||
method:
|
||||
|
||||
["source","java",subs="attributes,callouts,macros"]
|
||||
--------------------------------------------------
|
||||
include-tagged::{doc-tests}/SearchDocumentationIT.java[search-template-execute-async]
|
||||
--------------------------------------------------
|
||||
<1> The `SearchTemplateRequest` to execute and the `ActionListener` to call when the execution completes.
|
||||
|
||||
The asynchronous method does not block and returns immediately. Once the request completes, the
|
||||
`ActionListener` is called back using the `onResponse` method if the execution completed successfully,
|
||||
or using the `onFailure` method if it failed.
|
||||
|
||||
A typical listener for `SearchTemplateResponse` is constructed as follows:
|
||||
|
||||
["source","java",subs="attributes,callouts,macros"]
|
||||
--------------------------------------------------
|
||||
include-tagged::{doc-tests}/SearchDocumentationIT.java[search-template-execute-listener]
|
||||
--------------------------------------------------
|
||||
<1> Called when the execution is successfully completed.
|
||||
<2> Called when the whole `SearchTemplateRequest` fails.
|
||||
|
||||
==== Search Template Response
|
||||
|
||||
For a standard search template request, the response contains a `SearchResponse` object
|
||||
with the result of executing the search:
|
||||
|
||||
["source","java",subs="attributes,callouts,macros"]
|
||||
--------------------------------------------------
|
||||
include-tagged::{doc-tests}/SearchDocumentationIT.java[search-template-response]
|
||||
--------------------------------------------------
|
||||
|
||||
If `simulate` was set to `true` in the request, then the response
|
||||
will contain the rendered search source instead of a `SearchResponse`:
|
||||
|
||||
["source","java",subs="attributes,callouts,macros"]
|
||||
--------------------------------------------------
|
||||
include-tagged::{doc-tests}/SearchDocumentationIT.java[render-search-template-response]
|
||||
--------------------------------------------------
|
||||
<1> The rendered source in bytes, in our example `{"query": { "match" : { "title" : "elasticsearch" }}, "size" : 5}`.
|
@ -31,6 +31,7 @@ The Java High Level REST Client supports the following Search APIs:
|
||||
* <<java-rest-high-search>>
|
||||
* <<java-rest-high-search-scroll>>
|
||||
* <<java-rest-high-clear-scroll>>
|
||||
* <<java-rest-high-search-template>>
|
||||
* <<java-rest-high-multi-search>>
|
||||
* <<java-rest-high-field-caps>>
|
||||
* <<java-rest-high-rank-eval>>
|
||||
@ -38,6 +39,7 @@ The Java High Level REST Client supports the following Search APIs:
|
||||
include::search/search.asciidoc[]
|
||||
include::search/scroll.asciidoc[]
|
||||
include::search/multi-search.asciidoc[]
|
||||
include::search/search-template.asciidoc[]
|
||||
include::search/field-caps.asciidoc[]
|
||||
include::search/rank-eval.asciidoc[]
|
||||
|
||||
|
@ -77,7 +77,7 @@ public class RestMultiSearchTemplateAction extends BaseRestHandler {
|
||||
|
||||
RestMultiSearchAction.parseMultiLineRequest(restRequest, multiRequest.indicesOptions(), allowExplicitIndex,
|
||||
(searchRequest, bytes) -> {
|
||||
SearchTemplateRequest searchTemplateRequest = RestSearchTemplateAction.parse(bytes);
|
||||
SearchTemplateRequest searchTemplateRequest = SearchTemplateRequest.fromXContent(bytes);
|
||||
if (searchTemplateRequest.getScript() != null) {
|
||||
searchTemplateRequest.setRequest(searchRequest);
|
||||
multiRequest.add(searchTemplateRequest);
|
||||
|
@ -52,7 +52,7 @@ public class RestRenderSearchTemplateAction extends BaseRestHandler {
|
||||
// Creates the render template request
|
||||
SearchTemplateRequest renderRequest;
|
||||
try (XContentParser parser = request.contentOrSourceParamParser()) {
|
||||
renderRequest = RestSearchTemplateAction.parse(parser);
|
||||
renderRequest = SearchTemplateRequest.fromXContent(parser);
|
||||
}
|
||||
renderRequest.setSimulate(true);
|
||||
|
||||
|
@ -47,33 +47,6 @@ public class RestSearchTemplateAction extends BaseRestHandler {
|
||||
|
||||
private static final Set<String> RESPONSE_PARAMS = Collections.singleton(RestSearchAction.TYPED_KEYS_PARAM);
|
||||
|
||||
private static final ObjectParser<SearchTemplateRequest, Void> PARSER;
|
||||
static {
|
||||
PARSER = new ObjectParser<>("search_template");
|
||||
PARSER.declareField((parser, request, s) ->
|
||||
request.setScriptParams(parser.map())
|
||||
, new ParseField("params"), ObjectParser.ValueType.OBJECT);
|
||||
PARSER.declareString((request, s) -> {
|
||||
request.setScriptType(ScriptType.STORED);
|
||||
request.setScript(s);
|
||||
}, new ParseField("id"));
|
||||
PARSER.declareBoolean(SearchTemplateRequest::setExplain, new ParseField("explain"));
|
||||
PARSER.declareBoolean(SearchTemplateRequest::setProfile, new ParseField("profile"));
|
||||
PARSER.declareField((parser, request, value) -> {
|
||||
request.setScriptType(ScriptType.INLINE);
|
||||
if (parser.currentToken() == XContentParser.Token.START_OBJECT) {
|
||||
//convert the template to json which is the only supported XContentType (see CustomMustacheFactory#createEncoder)
|
||||
try (XContentBuilder builder = XContentFactory.jsonBuilder()) {
|
||||
request.setScript(Strings.toString(builder.copyCurrentStructure(parser)));
|
||||
} catch (IOException e) {
|
||||
throw new ParsingException(parser.getTokenLocation(), "Could not parse inline template", e);
|
||||
}
|
||||
} else {
|
||||
request.setScript(parser.text());
|
||||
}
|
||||
}, new ParseField("source", "inline", "template"), ObjectParser.ValueType.OBJECT_OR_STRING);
|
||||
}
|
||||
|
||||
public RestSearchTemplateAction(Settings settings, RestController controller) {
|
||||
super(settings);
|
||||
|
||||
@ -99,17 +72,13 @@ public class RestSearchTemplateAction extends BaseRestHandler {
|
||||
// Creates the search template request
|
||||
SearchTemplateRequest searchTemplateRequest;
|
||||
try (XContentParser parser = request.contentOrSourceParamParser()) {
|
||||
searchTemplateRequest = PARSER.parse(parser, new SearchTemplateRequest(), null);
|
||||
searchTemplateRequest = SearchTemplateRequest.fromXContent(parser);
|
||||
}
|
||||
searchTemplateRequest.setRequest(searchRequest);
|
||||
|
||||
return channel -> client.execute(SearchTemplateAction.INSTANCE, searchTemplateRequest, new RestStatusToXContentListener<>(channel));
|
||||
}
|
||||
|
||||
public static SearchTemplateRequest parse(XContentParser parser) throws IOException {
|
||||
return PARSER.parse(parser, new SearchTemplateRequest(), null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Set<String> responseParams() {
|
||||
return RESPONSE_PARAMS;
|
||||
|
@ -23,19 +23,28 @@ import org.elasticsearch.action.ActionRequest;
|
||||
import org.elasticsearch.action.ActionRequestValidationException;
|
||||
import org.elasticsearch.action.CompositeIndicesRequest;
|
||||
import org.elasticsearch.action.search.SearchRequest;
|
||||
import org.elasticsearch.common.ParseField;
|
||||
import org.elasticsearch.common.ParsingException;
|
||||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.common.io.stream.StreamInput;
|
||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||
import org.elasticsearch.common.xcontent.ObjectParser;
|
||||
import org.elasticsearch.common.xcontent.ToXContentObject;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.common.xcontent.XContentFactory;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.script.ScriptType;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import static org.elasticsearch.action.ValidateActions.addValidationError;
|
||||
|
||||
/**
|
||||
* A request to execute a search based on a search template.
|
||||
*/
|
||||
public class SearchTemplateRequest extends ActionRequest implements CompositeIndicesRequest {
|
||||
public class SearchTemplateRequest extends ActionRequest implements CompositeIndicesRequest, ToXContentObject {
|
||||
|
||||
private SearchRequest request;
|
||||
private boolean simulate = false;
|
||||
@ -60,6 +69,24 @@ public class SearchTemplateRequest extends ActionRequest implements CompositeInd
|
||||
return request;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
SearchTemplateRequest request1 = (SearchTemplateRequest) o;
|
||||
return simulate == request1.simulate &&
|
||||
explain == request1.explain &&
|
||||
profile == request1.profile &&
|
||||
Objects.equals(request, request1.request) &&
|
||||
scriptType == request1.scriptType &&
|
||||
Objects.equals(script, request1.script) &&
|
||||
Objects.equals(scriptParams, request1.scriptParams);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(request, simulate, explain, profile, scriptType, script, scriptParams);
|
||||
}
|
||||
|
||||
public boolean isSimulate() {
|
||||
return simulate;
|
||||
@ -134,6 +161,62 @@ public class SearchTemplateRequest extends ActionRequest implements CompositeInd
|
||||
return validationException;
|
||||
}
|
||||
|
||||
private static ParseField ID_FIELD = new ParseField("id");
|
||||
private static ParseField SOURCE_FIELD = new ParseField("source", "inline", "template");
|
||||
|
||||
private static ParseField PARAMS_FIELD = new ParseField("params");
|
||||
private static ParseField EXPLAIN_FIELD = new ParseField("explain");
|
||||
private static ParseField PROFILE_FIELD = new ParseField("profile");
|
||||
|
||||
private static final ObjectParser<SearchTemplateRequest, Void> PARSER;
|
||||
static {
|
||||
PARSER = new ObjectParser<>("search_template");
|
||||
PARSER.declareField((parser, request, s) ->
|
||||
request.setScriptParams(parser.map())
|
||||
, PARAMS_FIELD, ObjectParser.ValueType.OBJECT);
|
||||
PARSER.declareString((request, s) -> {
|
||||
request.setScriptType(ScriptType.STORED);
|
||||
request.setScript(s);
|
||||
}, ID_FIELD);
|
||||
PARSER.declareBoolean(SearchTemplateRequest::setExplain, EXPLAIN_FIELD);
|
||||
PARSER.declareBoolean(SearchTemplateRequest::setProfile, PROFILE_FIELD);
|
||||
PARSER.declareField((parser, request, value) -> {
|
||||
request.setScriptType(ScriptType.INLINE);
|
||||
if (parser.currentToken() == XContentParser.Token.START_OBJECT) {
|
||||
//convert the template to json which is the only supported XContentType (see CustomMustacheFactory#createEncoder)
|
||||
try (XContentBuilder builder = XContentFactory.jsonBuilder()) {
|
||||
request.setScript(Strings.toString(builder.copyCurrentStructure(parser)));
|
||||
} catch (IOException e) {
|
||||
throw new ParsingException(parser.getTokenLocation(), "Could not parse inline template", e);
|
||||
}
|
||||
} else {
|
||||
request.setScript(parser.text());
|
||||
}
|
||||
}, SOURCE_FIELD, ObjectParser.ValueType.OBJECT_OR_STRING);
|
||||
}
|
||||
|
||||
public static SearchTemplateRequest fromXContent(XContentParser parser) throws IOException {
|
||||
return PARSER.parse(parser, new SearchTemplateRequest(), null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
||||
builder.startObject();
|
||||
|
||||
if (scriptType == ScriptType.STORED) {
|
||||
builder.field(ID_FIELD.getPreferredName(), script);
|
||||
} else if (scriptType == ScriptType.INLINE) {
|
||||
builder.field(SOURCE_FIELD.getPreferredName(), script);
|
||||
} else {
|
||||
throw new UnsupportedOperationException("Unrecognized script type [" + scriptType + "].");
|
||||
}
|
||||
|
||||
return builder.field(PARAMS_FIELD.getPreferredName(), scriptParams)
|
||||
.field(EXPLAIN_FIELD.getPreferredName(), explain)
|
||||
.field(PROFILE_FIELD.getPreferredName(), profile)
|
||||
.endObject();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readFrom(StreamInput in) throws IOException {
|
||||
super.readFrom(in);
|
||||
|
@ -21,18 +21,23 @@ package org.elasticsearch.script.mustache;
|
||||
|
||||
import org.elasticsearch.action.ActionResponse;
|
||||
import org.elasticsearch.action.search.SearchResponse;
|
||||
import org.elasticsearch.common.ParseField;
|
||||
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.StatusToXContentObject;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.common.xcontent.XContentFactory;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.common.xcontent.XContentType;
|
||||
import org.elasticsearch.rest.RestStatus;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Map;
|
||||
|
||||
public class SearchTemplateResponse extends ActionResponse implements StatusToXContentObject {
|
||||
public class SearchTemplateResponse extends ActionResponse implements StatusToXContentObject {
|
||||
public static ParseField TEMPLATE_OUTPUT_FIELD = new ParseField("template_output");
|
||||
|
||||
/** Contains the source of the rendered template **/
|
||||
private BytesReference source;
|
||||
@ -77,6 +82,30 @@ public class SearchTemplateResponse extends ActionResponse implements StatusToX
|
||||
response = in.readOptionalStreamable(SearchResponse::new);
|
||||
}
|
||||
|
||||
public static SearchTemplateResponse fromXContent(XContentParser parser) throws IOException {
|
||||
SearchTemplateResponse searchTemplateResponse = new SearchTemplateResponse();
|
||||
Map<String, Object> contentAsMap = parser.map();
|
||||
|
||||
if (contentAsMap.containsKey(TEMPLATE_OUTPUT_FIELD.getPreferredName())) {
|
||||
Object source = contentAsMap.get(TEMPLATE_OUTPUT_FIELD.getPreferredName());
|
||||
XContentBuilder builder = XContentFactory.contentBuilder(XContentType.JSON)
|
||||
.value(source);
|
||||
searchTemplateResponse.setSource(BytesReference.bytes(builder));
|
||||
} else {
|
||||
XContentType contentType = parser.contentType();
|
||||
XContentBuilder builder = XContentFactory.contentBuilder(contentType)
|
||||
.map(contentAsMap);
|
||||
XContentParser searchResponseParser = contentType.xContent().createParser(
|
||||
parser.getXContentRegistry(),
|
||||
parser.getDeprecationHandler(),
|
||||
BytesReference.bytes(builder).streamInput());
|
||||
|
||||
SearchResponse searchResponse = SearchResponse.fromXContent(searchResponseParser);
|
||||
searchTemplateResponse.setResponse(searchResponse);
|
||||
}
|
||||
return searchTemplateResponse;
|
||||
}
|
||||
|
||||
@Override
|
||||
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
||||
if (hasResponse()) {
|
||||
@ -85,7 +114,7 @@ public class SearchTemplateResponse extends ActionResponse implements StatusToX
|
||||
builder.startObject();
|
||||
//we can assume the template is always json as we convert it before compiling it
|
||||
try (InputStream stream = source.streamInput()) {
|
||||
builder.rawField("template_output", stream, XContentType.JSON);
|
||||
builder.rawField(TEMPLATE_OUTPUT_FIELD.getPreferredName(), stream, XContentType.JSON);
|
||||
}
|
||||
builder.endObject();
|
||||
}
|
||||
|
@ -101,7 +101,7 @@ public class SearchTemplateIT extends ESSingleNodeTestCase {
|
||||
+ " \"size\": 1"
|
||||
+ " }"
|
||||
+ "}";
|
||||
SearchTemplateRequest request = RestSearchTemplateAction.parse(createParser(JsonXContent.jsonXContent, query));
|
||||
SearchTemplateRequest request = SearchTemplateRequest.fromXContent(createParser(JsonXContent.jsonXContent, query));
|
||||
request.setRequest(searchRequest);
|
||||
SearchTemplateResponse searchResponse = client().execute(SearchTemplateAction.INSTANCE, request).get();
|
||||
assertThat(searchResponse.getResponse().getHits().getHits().length, equalTo(1));
|
||||
@ -122,7 +122,7 @@ public class SearchTemplateIT extends ESSingleNodeTestCase {
|
||||
+ " \"use_size\": true"
|
||||
+ " }"
|
||||
+ "}";
|
||||
SearchTemplateRequest request = RestSearchTemplateAction.parse(createParser(JsonXContent.jsonXContent, templateString));
|
||||
SearchTemplateRequest request = SearchTemplateRequest.fromXContent(createParser(JsonXContent.jsonXContent, templateString));
|
||||
request.setRequest(searchRequest);
|
||||
SearchTemplateResponse searchResponse = client().execute(SearchTemplateAction.INSTANCE, request).get();
|
||||
assertThat(searchResponse.getResponse().getHits().getHits().length, equalTo(1));
|
||||
@ -143,7 +143,7 @@ public class SearchTemplateIT extends ESSingleNodeTestCase {
|
||||
+ " \"use_size\": true"
|
||||
+ " }"
|
||||
+ "}";
|
||||
SearchTemplateRequest request = RestSearchTemplateAction.parse(createParser(JsonXContent.jsonXContent, templateString));
|
||||
SearchTemplateRequest request = SearchTemplateRequest.fromXContent(createParser(JsonXContent.jsonXContent, templateString));
|
||||
request.setRequest(searchRequest);
|
||||
SearchTemplateResponse searchResponse = client().execute(SearchTemplateAction.INSTANCE, request).get();
|
||||
assertThat(searchResponse.getResponse().getHits().getHits().length, equalTo(1));
|
||||
|
@ -19,117 +19,77 @@
|
||||
|
||||
package org.elasticsearch.script.mustache;
|
||||
|
||||
import org.elasticsearch.common.xcontent.XContentParseException;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.common.xcontent.json.JsonXContent;
|
||||
import org.elasticsearch.script.ScriptType;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
import org.elasticsearch.search.RandomSearchRequestGenerator;
|
||||
import org.elasticsearch.search.builder.SearchSourceBuilder;
|
||||
import org.elasticsearch.test.AbstractStreamableTestCase;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.hasEntry;
|
||||
import static org.hamcrest.Matchers.hasItems;
|
||||
import static org.hamcrest.Matchers.hasKey;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
public class SearchTemplateRequestTests extends AbstractStreamableTestCase<SearchTemplateRequest> {
|
||||
|
||||
public class SearchTemplateRequestTests extends ESTestCase {
|
||||
|
||||
public void testParseInlineTemplate() throws Exception {
|
||||
String source = "{" +
|
||||
" 'source' : {\n" +
|
||||
" 'query': {\n" +
|
||||
" 'terms': {\n" +
|
||||
" 'status': [\n" +
|
||||
" '{{#status}}',\n" +
|
||||
" '{{.}}',\n" +
|
||||
" '{{/status}}'\n" +
|
||||
" ]\n" +
|
||||
" }\n" +
|
||||
" }\n" +
|
||||
" }" +
|
||||
"}";
|
||||
|
||||
SearchTemplateRequest request = RestSearchTemplateAction.parse(newParser(source));
|
||||
assertThat(request.getScript(), equalTo("{\"query\":{\"terms\":{\"status\":[\"{{#status}}\",\"{{.}}\",\"{{/status}}\"]}}}"));
|
||||
assertThat(request.getScriptType(), equalTo(ScriptType.INLINE));
|
||||
assertThat(request.getScriptParams(), nullValue());
|
||||
@Override
|
||||
protected SearchTemplateRequest createBlankInstance() {
|
||||
return new SearchTemplateRequest();
|
||||
}
|
||||
|
||||
public void testParseInlineTemplateWithParams() throws Exception {
|
||||
String source = "{" +
|
||||
" 'source' : {" +
|
||||
" 'query': { 'match' : { '{{my_field}}' : '{{my_value}}' } }," +
|
||||
" 'size' : '{{my_size}}'" +
|
||||
" }," +
|
||||
" 'params' : {" +
|
||||
" 'my_field' : 'foo'," +
|
||||
" 'my_value' : 'bar'," +
|
||||
" 'my_size' : 5" +
|
||||
" }" +
|
||||
"}";
|
||||
|
||||
SearchTemplateRequest request = RestSearchTemplateAction.parse(newParser(source));
|
||||
assertThat(request.getScript(), equalTo("{\"query\":{\"match\":{\"{{my_field}}\":\"{{my_value}}\"}},\"size\":\"{{my_size}}\"}"));
|
||||
assertThat(request.getScriptType(), equalTo(ScriptType.INLINE));
|
||||
assertThat(request.getScriptParams().size(), equalTo(3));
|
||||
assertThat(request.getScriptParams(), hasEntry("my_field", "foo"));
|
||||
assertThat(request.getScriptParams(), hasEntry("my_value", "bar"));
|
||||
assertThat(request.getScriptParams(), hasEntry("my_size", 5));
|
||||
@Override
|
||||
protected SearchTemplateRequest createTestInstance() {
|
||||
return createRandomRequest();
|
||||
}
|
||||
|
||||
public void testParseInlineTemplateAsString() throws Exception {
|
||||
String source = "{'source' : '{\\\"query\\\":{\\\"bool\\\":{\\\"must\\\":{\\\"match\\\":{\\\"foo\\\":\\\"{{text}}\\\"}}}}}'}";
|
||||
@Override
|
||||
protected SearchTemplateRequest mutateInstance(SearchTemplateRequest instance) throws IOException {
|
||||
List<Consumer<SearchTemplateRequest>> mutators = new ArrayList<>();
|
||||
|
||||
SearchTemplateRequest request = RestSearchTemplateAction.parse(newParser(source));
|
||||
assertThat(request.getScript(), equalTo("{\"query\":{\"bool\":{\"must\":{\"match\":{\"foo\":\"{{text}}\"}}}}}"));
|
||||
assertThat(request.getScriptType(), equalTo(ScriptType.INLINE));
|
||||
assertThat(request.getScriptParams(), nullValue());
|
||||
mutators.add(request -> request.setScriptType(
|
||||
randomValueOtherThan(request.getScriptType(), () -> randomFrom(ScriptType.values()))));
|
||||
mutators.add(request -> request.setScript(
|
||||
randomValueOtherThan(request.getScript(), () -> randomAlphaOfLength(50))));
|
||||
|
||||
mutators.add(request -> {
|
||||
Map<String, Object> mutatedScriptParams = new HashMap<>(request.getScriptParams());
|
||||
String newField = randomValueOtherThanMany(mutatedScriptParams::containsKey, () -> randomAlphaOfLength(5));
|
||||
mutatedScriptParams.put(newField, randomAlphaOfLength(10));
|
||||
request.setScriptParams(mutatedScriptParams);
|
||||
});
|
||||
|
||||
mutators.add(request -> request.setProfile(!request.isProfile()));
|
||||
mutators.add(request -> request.setExplain(!request.isExplain()));
|
||||
mutators.add(request -> request.setSimulate(!request.isSimulate()));
|
||||
|
||||
mutators.add(request -> request.setRequest(
|
||||
RandomSearchRequestGenerator.randomSearchRequest(SearchSourceBuilder::searchSource)));
|
||||
|
||||
SearchTemplateRequest mutatedInstance = copyInstance(instance);
|
||||
Consumer<SearchTemplateRequest> mutator = randomFrom(mutators);
|
||||
mutator.accept(mutatedInstance);
|
||||
return mutatedInstance;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testParseInlineTemplateAsStringWithParams() throws Exception {
|
||||
String source = "{'source' : '{\\\"query\\\":{\\\"match\\\":{\\\"{{field}}\\\":\\\"{{value}}\\\"}}}', " +
|
||||
"'params': {'status': ['pending', 'published']}}";
|
||||
|
||||
SearchTemplateRequest request = RestSearchTemplateAction.parse(newParser(source));
|
||||
assertThat(request.getScript(), equalTo("{\"query\":{\"match\":{\"{{field}}\":\"{{value}}\"}}}"));
|
||||
assertThat(request.getScriptType(), equalTo(ScriptType.INLINE));
|
||||
assertThat(request.getScriptParams().size(), equalTo(1));
|
||||
assertThat(request.getScriptParams(), hasKey("status"));
|
||||
assertThat((List<String>) request.getScriptParams().get("status"), hasItems("pending", "published"));
|
||||
}
|
||||
public static SearchTemplateRequest createRandomRequest() {
|
||||
SearchTemplateRequest request = new SearchTemplateRequest();
|
||||
request.setScriptType(randomFrom(ScriptType.values()));
|
||||
request.setScript(randomAlphaOfLength(50));
|
||||
|
||||
public void testParseStoredTemplate() throws Exception {
|
||||
String source = "{'id' : 'storedTemplate'}";
|
||||
Map<String, Object> scriptParams = new HashMap<>();
|
||||
for (int i = 0; i < randomInt(10); i++) {
|
||||
scriptParams.put(randomAlphaOfLength(5), randomAlphaOfLength(10));
|
||||
}
|
||||
request.setScriptParams(scriptParams);
|
||||
|
||||
SearchTemplateRequest request = RestSearchTemplateAction.parse(newParser(source));
|
||||
assertThat(request.getScript(), equalTo("storedTemplate"));
|
||||
assertThat(request.getScriptType(), equalTo(ScriptType.STORED));
|
||||
assertThat(request.getScriptParams(), nullValue());
|
||||
}
|
||||
request.setExplain(randomBoolean());
|
||||
request.setProfile(randomBoolean());
|
||||
request.setSimulate(randomBoolean());
|
||||
|
||||
public void testParseStoredTemplateWithParams() throws Exception {
|
||||
String source = "{'id' : 'another_template', 'params' : {'bar': 'foo'}}";
|
||||
|
||||
SearchTemplateRequest request = RestSearchTemplateAction.parse(newParser(source));
|
||||
assertThat(request.getScript(), equalTo("another_template"));
|
||||
assertThat(request.getScriptType(), equalTo(ScriptType.STORED));
|
||||
assertThat(request.getScriptParams().size(), equalTo(1));
|
||||
assertThat(request.getScriptParams(), hasEntry("bar", "foo"));
|
||||
}
|
||||
|
||||
public void testParseWrongTemplate() {
|
||||
// Unclosed template id
|
||||
expectThrows(XContentParseException.class, () -> RestSearchTemplateAction.parse(newParser("{'id' : 'another_temp }")));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link XContentParser} with the given String while replacing single quote to double quotes.
|
||||
*/
|
||||
private XContentParser newParser(String s) throws IOException {
|
||||
assertNotNull(s);
|
||||
return createParser(JsonXContent.jsonXContent, s.replace("'", "\""));
|
||||
request.setRequest(RandomSearchRequestGenerator.randomSearchRequest(
|
||||
SearchSourceBuilder::searchSource));
|
||||
return request;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,197 @@
|
||||
/*
|
||||
* 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.common.bytes.BytesReference;
|
||||
import org.elasticsearch.common.xcontent.ToXContent;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.common.xcontent.XContentFactory;
|
||||
import org.elasticsearch.common.xcontent.XContentParseException;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.common.xcontent.XContentType;
|
||||
import org.elasticsearch.common.xcontent.json.JsonXContent;
|
||||
import org.elasticsearch.script.ScriptType;
|
||||
import org.elasticsearch.test.AbstractXContentTestCase;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertToXContentEquivalent;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.hasEntry;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
|
||||
public class SearchTemplateRequestXContentTests extends AbstractXContentTestCase<SearchTemplateRequest> {
|
||||
|
||||
@Override
|
||||
public SearchTemplateRequest createTestInstance() {
|
||||
return SearchTemplateRequestTests.createRandomRequest();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SearchTemplateRequest doParseInstance(XContentParser parser) throws IOException {
|
||||
return SearchTemplateRequest.fromXContent(parser);
|
||||
}
|
||||
|
||||
/**
|
||||
* Note that when checking equality for xContent parsing, we omit two parts of the request:
|
||||
* - The 'simulate' option, since this parameter is not included in the
|
||||
* request's xContent (it's instead used to determine the request endpoint).
|
||||
* - The random SearchRequest, since this component only affects the request
|
||||
* parameters and also isn't captured in the request's xContent.
|
||||
*/
|
||||
@Override
|
||||
protected void assertEqualInstances(SearchTemplateRequest expectedInstance, SearchTemplateRequest newInstance) {
|
||||
assertTrue(
|
||||
expectedInstance.isExplain() == newInstance.isExplain() &&
|
||||
expectedInstance.isProfile() == newInstance.isProfile() &&
|
||||
expectedInstance.getScriptType() == newInstance.getScriptType() &&
|
||||
Objects.equals(expectedInstance.getScript(), newInstance.getScript()) &&
|
||||
Objects.equals(expectedInstance.getScriptParams(), newInstance.getScriptParams()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean supportsUnknownFields() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public void testToXContentWithInlineTemplate() throws IOException {
|
||||
SearchTemplateRequest request = new SearchTemplateRequest();
|
||||
|
||||
request.setScriptType(ScriptType.INLINE);
|
||||
request.setScript("{\"query\": { \"match\" : { \"{{my_field}}\" : \"{{my_value}}\" } } }");
|
||||
request.setProfile(true);
|
||||
|
||||
Map<String, Object> scriptParams = new HashMap<>();
|
||||
scriptParams.put("my_field", "foo");
|
||||
scriptParams.put("my_value", "bar");
|
||||
request.setScriptParams(scriptParams);
|
||||
|
||||
XContentType contentType = randomFrom(XContentType.values());
|
||||
XContentBuilder expectedRequest = XContentFactory.contentBuilder(contentType)
|
||||
.startObject()
|
||||
.field("source", "{\"query\": { \"match\" : { \"{{my_field}}\" : \"{{my_value}}\" } } }")
|
||||
.startObject("params")
|
||||
.field("my_field", "foo")
|
||||
.field("my_value", "bar")
|
||||
.endObject()
|
||||
.field("explain", false)
|
||||
.field("profile", true)
|
||||
.endObject();
|
||||
|
||||
XContentBuilder actualRequest = XContentFactory.contentBuilder(contentType);
|
||||
request.toXContent(actualRequest, ToXContent.EMPTY_PARAMS);
|
||||
|
||||
assertToXContentEquivalent(BytesReference.bytes(expectedRequest),
|
||||
BytesReference.bytes(actualRequest),
|
||||
contentType);
|
||||
}
|
||||
|
||||
public void testToXContentWithStoredTemplate() throws IOException {
|
||||
SearchTemplateRequest request = new SearchTemplateRequest();
|
||||
|
||||
request.setScriptType(ScriptType.STORED);
|
||||
request.setScript("match_template");
|
||||
request.setExplain(true);
|
||||
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
params.put("my_field", "foo");
|
||||
params.put("my_value", "bar");
|
||||
request.setScriptParams(params);
|
||||
|
||||
XContentType contentType = randomFrom(XContentType.values());
|
||||
XContentBuilder expectedRequest = XContentFactory.contentBuilder(contentType)
|
||||
.startObject()
|
||||
.field("id", "match_template")
|
||||
.startObject("params")
|
||||
.field("my_field", "foo")
|
||||
.field("my_value", "bar")
|
||||
.endObject()
|
||||
.field("explain", true)
|
||||
.field("profile", false)
|
||||
.endObject();
|
||||
|
||||
XContentBuilder actualRequest = XContentFactory.contentBuilder(contentType);
|
||||
request.toXContent(actualRequest, ToXContent.EMPTY_PARAMS);
|
||||
|
||||
assertToXContentEquivalent(
|
||||
BytesReference.bytes(expectedRequest),
|
||||
BytesReference.bytes(actualRequest),
|
||||
contentType);
|
||||
}
|
||||
|
||||
public void testFromXContentWithEmbeddedTemplate() throws Exception {
|
||||
String source = "{" +
|
||||
" 'source' : {\n" +
|
||||
" 'query': {\n" +
|
||||
" 'terms': {\n" +
|
||||
" 'status': [\n" +
|
||||
" '{{#status}}',\n" +
|
||||
" '{{.}}',\n" +
|
||||
" '{{/status}}'\n" +
|
||||
" ]\n" +
|
||||
" }\n" +
|
||||
" }\n" +
|
||||
" }" +
|
||||
"}";
|
||||
|
||||
SearchTemplateRequest request = SearchTemplateRequest.fromXContent(newParser(source));
|
||||
assertThat(request.getScript(), equalTo("{\"query\":{\"terms\":{\"status\":[\"{{#status}}\",\"{{.}}\",\"{{/status}}\"]}}}"));
|
||||
assertThat(request.getScriptType(), equalTo(ScriptType.INLINE));
|
||||
assertThat(request.getScriptParams(), nullValue());
|
||||
}
|
||||
|
||||
public void testFromXContentWithEmbeddedTemplateAndParams() throws Exception {
|
||||
String source = "{" +
|
||||
" 'source' : {" +
|
||||
" 'query': { 'match' : { '{{my_field}}' : '{{my_value}}' } }," +
|
||||
" 'size' : '{{my_size}}'" +
|
||||
" }," +
|
||||
" 'params' : {" +
|
||||
" 'my_field' : 'foo'," +
|
||||
" 'my_value' : 'bar'," +
|
||||
" 'my_size' : 5" +
|
||||
" }" +
|
||||
"}";
|
||||
|
||||
SearchTemplateRequest request = SearchTemplateRequest.fromXContent(newParser(source));
|
||||
assertThat(request.getScript(), equalTo("{\"query\":{\"match\":{\"{{my_field}}\":\"{{my_value}}\"}},\"size\":\"{{my_size}}\"}"));
|
||||
assertThat(request.getScriptType(), equalTo(ScriptType.INLINE));
|
||||
assertThat(request.getScriptParams().size(), equalTo(3));
|
||||
assertThat(request.getScriptParams(), hasEntry("my_field", "foo"));
|
||||
assertThat(request.getScriptParams(), hasEntry("my_value", "bar"));
|
||||
assertThat(request.getScriptParams(), hasEntry("my_size", 5));
|
||||
}
|
||||
|
||||
public void testFromXContentWithMalformedRequest() {
|
||||
// Unclosed template id
|
||||
expectThrows(XContentParseException.class, () -> SearchTemplateRequest.fromXContent(newParser("{'id' : 'another_temp }")));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link XContentParser} with the given String while replacing single quote to double quotes.
|
||||
*/
|
||||
private XContentParser newParser(String s) throws IOException {
|
||||
assertNotNull(s);
|
||||
return createParser(JsonXContent.jsonXContent, s.replace("'", "\""));
|
||||
}
|
||||
}
|
@ -0,0 +1,211 @@
|
||||
/*
|
||||
* 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.action.search.SearchResponse;
|
||||
import org.elasticsearch.action.search.ShardSearchFailure;
|
||||
import org.elasticsearch.common.bytes.BytesReference;
|
||||
import org.elasticsearch.common.text.Text;
|
||||
import org.elasticsearch.common.xcontent.ToXContent;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.common.xcontent.XContentFactory;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.common.xcontent.XContentType;
|
||||
import org.elasticsearch.search.SearchHit;
|
||||
import org.elasticsearch.search.SearchHits;
|
||||
import org.elasticsearch.search.internal.InternalSearchResponse;
|
||||
import org.elasticsearch.test.AbstractXContentTestCase;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertToXContentEquivalent;
|
||||
|
||||
public class SearchTemplateResponseTests extends AbstractXContentTestCase<SearchTemplateResponse> {
|
||||
|
||||
@Override
|
||||
protected SearchTemplateResponse createTestInstance() {
|
||||
SearchTemplateResponse response = new SearchTemplateResponse();
|
||||
if (randomBoolean()) {
|
||||
response.setResponse(createSearchResponse());
|
||||
} else {
|
||||
response.setSource(createSource());
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SearchTemplateResponse doParseInstance(XContentParser parser) throws IOException {
|
||||
return SearchTemplateResponse.fromXContent(parser);
|
||||
}
|
||||
|
||||
/**
|
||||
* For simplicity we create a minimal response, as there is already a dedicated
|
||||
* test class for search response parsing and serialization.
|
||||
*/
|
||||
private static SearchResponse createSearchResponse() {
|
||||
long tookInMillis = randomNonNegativeLong();
|
||||
int totalShards = randomIntBetween(1, Integer.MAX_VALUE);
|
||||
int successfulShards = randomIntBetween(0, totalShards);
|
||||
int skippedShards = randomIntBetween(0, totalShards);
|
||||
InternalSearchResponse internalSearchResponse = InternalSearchResponse.empty();
|
||||
|
||||
return new SearchResponse(internalSearchResponse, null, totalShards, successfulShards,
|
||||
skippedShards, tookInMillis, ShardSearchFailure.EMPTY_ARRAY, SearchResponse.Clusters.EMPTY);
|
||||
}
|
||||
|
||||
private static BytesReference createSource() {
|
||||
try {
|
||||
XContentBuilder source = XContentFactory.jsonBuilder()
|
||||
.startObject()
|
||||
.startObject("query")
|
||||
.startObject("match")
|
||||
.field(randomAlphaOfLength(5), randomAlphaOfLength(10))
|
||||
.endObject()
|
||||
.endObject()
|
||||
.endObject();
|
||||
return BytesReference.bytes(source);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Predicate<String> getRandomFieldsExcludeFilter() {
|
||||
String templateOutputField = SearchTemplateResponse.TEMPLATE_OUTPUT_FIELD.getPreferredName();
|
||||
return field -> field.equals(templateOutputField) || field.startsWith(templateOutputField + ".");
|
||||
}
|
||||
|
||||
/**
|
||||
* Note that we can't rely on normal equals and hashCode checks, since {@link SearchResponse} doesn't
|
||||
* currently implement equals and hashCode. Instead, we compare the template outputs for equality,
|
||||
* and perform some sanity checks on the search response instances.
|
||||
*/
|
||||
@Override
|
||||
protected void assertEqualInstances(SearchTemplateResponse expectedInstance, SearchTemplateResponse newInstance) {
|
||||
assertNotSame(newInstance, expectedInstance);
|
||||
|
||||
BytesReference expectedSource = expectedInstance.getSource();
|
||||
BytesReference newSource = newInstance.getSource();
|
||||
assertEquals(expectedSource == null, newSource == null);
|
||||
if (expectedSource != null) {
|
||||
try {
|
||||
assertToXContentEquivalent(expectedSource, newSource, XContentType.JSON);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
assertEquals(expectedInstance.hasResponse(), newInstance.hasResponse());
|
||||
if (expectedInstance.hasResponse()) {
|
||||
SearchResponse expectedResponse = expectedInstance.getResponse();
|
||||
SearchResponse newResponse = newInstance.getResponse();
|
||||
|
||||
assertEquals(expectedResponse.getHits().totalHits, newResponse.getHits().totalHits);
|
||||
assertEquals(expectedResponse.getHits().getMaxScore(), newResponse.getHits().getMaxScore(), 0.0001);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean supportsUnknownFields() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public void testSourceToXContent() throws IOException {
|
||||
SearchTemplateResponse response = new SearchTemplateResponse();
|
||||
|
||||
XContentBuilder source = XContentFactory.jsonBuilder()
|
||||
.startObject()
|
||||
.startObject("query")
|
||||
.startObject("terms")
|
||||
.field("status", new String[]{"pending", "published"})
|
||||
.endObject()
|
||||
.endObject()
|
||||
.endObject();
|
||||
response.setSource(BytesReference.bytes(source));
|
||||
|
||||
XContentType contentType = randomFrom(XContentType.values());
|
||||
XContentBuilder expectedResponse = XContentFactory.contentBuilder(contentType)
|
||||
.startObject()
|
||||
.startObject("template_output")
|
||||
.startObject("query")
|
||||
.startObject("terms")
|
||||
.field("status", new String[]{"pending", "published"})
|
||||
.endObject()
|
||||
.endObject()
|
||||
.endObject()
|
||||
.endObject();
|
||||
|
||||
XContentBuilder actualResponse = XContentFactory.contentBuilder(contentType);
|
||||
response.toXContent(actualResponse, ToXContent.EMPTY_PARAMS);
|
||||
|
||||
assertToXContentEquivalent(
|
||||
BytesReference.bytes(expectedResponse),
|
||||
BytesReference.bytes(actualResponse),
|
||||
contentType);
|
||||
}
|
||||
|
||||
public void testSearchResponseToXContent() throws IOException {
|
||||
SearchHit hit = new SearchHit(1, "id", new Text("type"), Collections.emptyMap());
|
||||
hit.score(2.0f);
|
||||
SearchHit[] hits = new SearchHit[] { hit };
|
||||
|
||||
InternalSearchResponse internalSearchResponse = new InternalSearchResponse(
|
||||
new SearchHits(hits, 100, 1.5f), null, null, null, false, null, 1);
|
||||
SearchResponse searchResponse = new SearchResponse(internalSearchResponse, null,
|
||||
0, 0, 0, 0, ShardSearchFailure.EMPTY_ARRAY, SearchResponse.Clusters.EMPTY);
|
||||
|
||||
SearchTemplateResponse response = new SearchTemplateResponse();
|
||||
response.setResponse(searchResponse);
|
||||
|
||||
XContentType contentType = randomFrom(XContentType.values());
|
||||
XContentBuilder expectedResponse = XContentFactory.contentBuilder(contentType)
|
||||
.startObject()
|
||||
.field("took", 0)
|
||||
.field("timed_out", false)
|
||||
.startObject("_shards")
|
||||
.field("total", 0)
|
||||
.field("successful", 0)
|
||||
.field("skipped", 0)
|
||||
.field("failed", 0)
|
||||
.endObject()
|
||||
.startObject("hits")
|
||||
.field("total", 100)
|
||||
.field("max_score", 1.5F)
|
||||
.startArray("hits")
|
||||
.startObject()
|
||||
.field("_type", "type")
|
||||
.field("_id", "id")
|
||||
.field("_score", 2.0F)
|
||||
.endObject()
|
||||
.endArray()
|
||||
.endObject()
|
||||
.endObject();
|
||||
|
||||
XContentBuilder actualResponse = XContentFactory.contentBuilder(contentType);
|
||||
response.toXContent(actualResponse, ToXContent.EMPTY_PARAMS);
|
||||
|
||||
assertToXContentEquivalent(
|
||||
BytesReference.bytes(expectedResponse),
|
||||
BytesReference.bytes(actualResponse),
|
||||
contentType);
|
||||
}
|
||||
}
|
@ -82,7 +82,7 @@ public class RandomSearchRequestGenerator {
|
||||
* @param randomSearchSourceBuilder builds a random {@link SearchSourceBuilder}. You can use
|
||||
* {@link #randomSearchSourceBuilder(Supplier, Supplier, Supplier, Supplier, Supplier)}.
|
||||
*/
|
||||
public static SearchRequest randomSearchRequest(Supplier<SearchSourceBuilder> randomSearchSourceBuilder) throws IOException {
|
||||
public static SearchRequest randomSearchRequest(Supplier<SearchSourceBuilder> randomSearchSourceBuilder) {
|
||||
SearchRequest searchRequest = new SearchRequest();
|
||||
searchRequest.allowPartialSearchResults(true);
|
||||
if (randomBoolean()) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user