diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/RequestConverters.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/RequestConverters.java index c1ffb0c97131c..b399a50d30eb5 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/RequestConverters.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/RequestConverters.java @@ -505,7 +505,7 @@ static Request count(CountRequest countRequest) throws IOException { params.putParam("min_score", String.valueOf(countRequest.minScore())); } request.addParameters(params.asMap()); - request.setEntity(createEntity(countRequest.source(), REQUEST_BODY_CONTENT_TYPE)); + request.setEntity(createEntity(countRequest, REQUEST_BODY_CONTENT_TYPE)); return request; } diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/core/CountRequest.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/core/CountRequest.java index 6b9f62111987d..95ab3db7e71f3 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/core/CountRequest.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/core/CountRequest.java @@ -24,9 +24,13 @@ import org.elasticsearch.action.IndicesRequest; import org.elasticsearch.action.support.IndicesOptions; import org.elasticsearch.common.Strings; +import org.elasticsearch.common.xcontent.ToXContentObject; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.index.query.QueryBuilder; import org.elasticsearch.search.builder.SearchSourceBuilder; import org.elasticsearch.search.internal.SearchContext; +import java.io.IOException; import java.util.Arrays; import java.util.Objects; @@ -35,34 +39,43 @@ /** * Encapsulates a request to _count API against one, several or all indices. */ -public final class CountRequest extends ActionRequest implements IndicesRequest.Replaceable { +public final class CountRequest extends ActionRequest implements IndicesRequest.Replaceable, ToXContentObject { private String[] indices = Strings.EMPTY_ARRAY; private String[] types = Strings.EMPTY_ARRAY; private String routing; private String preference; - private SearchSourceBuilder searchSourceBuilder; + private QueryBuilder query; private IndicesOptions indicesOptions = DEFAULT_INDICES_OPTIONS; private int terminateAfter = SearchContext.DEFAULT_TERMINATE_AFTER; private Float minScore; - public CountRequest() { - this.searchSourceBuilder = new SearchSourceBuilder(); - } + public CountRequest() {} /** * Constructs a new count request against the indices. No indices provided here means that count will execute on all indices. */ public CountRequest(String... indices) { - this(indices, new SearchSourceBuilder()); + indices(indices); } /** * Constructs a new search request against the provided indices with the given search source. + * + * @deprecated The count api only supports a query. Use {@link #CountRequest(String[], QueryBuilder)} instead. */ + @Deprecated public CountRequest(String[] indices, SearchSourceBuilder searchSourceBuilder) { indices(indices); - this.searchSourceBuilder = searchSourceBuilder; + this.query = Objects.requireNonNull(searchSourceBuilder, "source must not be null").query(); + } + + /** + * Constructs a new search request against the provided indices with the given query. + */ + public CountRequest(String[] indices, QueryBuilder query) { + indices(indices); + this.query = Objects.requireNonNull(query, "query must not be null");; } @Override @@ -84,9 +97,20 @@ public CountRequest indices(String... indices) { /** * The source of the count request. + * + * @deprecated The count api only supports a query. Use {@link #query(QueryBuilder)} instead. */ + @Deprecated public CountRequest source(SearchSourceBuilder searchSourceBuilder) { - this.searchSourceBuilder = Objects.requireNonNull(searchSourceBuilder, "source must not be null"); + this.query = Objects.requireNonNull(searchSourceBuilder, "source must not be null").query(); + return this; + } + + /** + * Sets the query to execute for this count request. + */ + public CountRequest query(QueryBuilder query) { + this.query = Objects.requireNonNull(query, "query must not be null"); return this; } @@ -188,8 +212,31 @@ public String[] types() { return Arrays.copyOf(this.types, this.types.length); } + /** + * @return the source builder + * @deprecated The count api only supports a query. Use {@link #query()} instead. + */ + @Deprecated public SearchSourceBuilder source() { - return this.searchSourceBuilder; + return new SearchSourceBuilder().query(query); + } + + /** + * @return The provided query to execute with the count request or + * null if no query was provided. + */ + public QueryBuilder query() { + return query; + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + if (query != null) { + builder.field("query", query); + } + builder.endObject(); + return builder; } @Override @@ -205,12 +252,15 @@ public boolean equals(Object o) { Arrays.equals(indices, that.indices) && Arrays.equals(types, that.types) && Objects.equals(routing, that.routing) && - Objects.equals(preference, that.preference); + Objects.equals(preference, that.preference) && + Objects.equals(terminateAfter, that.terminateAfter) && + Objects.equals(minScore, that.minScore) && + Objects.equals(query, that.query); } @Override public int hashCode() { - int result = Objects.hash(indicesOptions, routing, preference); + int result = Objects.hash(indicesOptions, routing, preference, terminateAfter, minScore, query); result = 31 * result + Arrays.hashCode(indices); result = 31 * result + Arrays.hashCode(types); return result; diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/AbstractRequestTestCase.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/AbstractRequestTestCase.java index 5436cdf1c379f..2d5cb843706e5 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/AbstractRequestTestCase.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/AbstractRequestTestCase.java @@ -49,13 +49,20 @@ public final void testFromXContent() throws IOException { final XContent xContent = XContentFactory.xContent(xContentType); final XContentParser parser = xContent.createParser( - NamedXContentRegistry.EMPTY, + xContentRegistry(), LoggingDeprecationHandler.INSTANCE, bytes.streamInput()); final S serverInstance = doParseToServerInstance(parser); assertInstances(serverInstance, clientTestInstance); } + /** + * The {@link NamedXContentRegistry} to use for this test. Subclasses may override this to have a more realistic registry. + */ + protected NamedXContentRegistry xContentRegistry() { + return NamedXContentRegistry.EMPTY; + } + /** * @return The client test instance to be serialized to xcontent as bytes */ diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/RequestConvertersTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/RequestConvertersTests.java index 18d2fef776dbf..7d894c86174c8 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/RequestConvertersTests.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/RequestConvertersTests.java @@ -72,6 +72,7 @@ import org.elasticsearch.common.xcontent.json.JsonXContent; import org.elasticsearch.index.VersionType; import org.elasticsearch.index.mapper.MapperService; +import org.elasticsearch.index.query.MatchAllQueryBuilder; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.index.query.TermQueryBuilder; import org.elasticsearch.index.rankeval.PrecisionAtK; @@ -1151,13 +1152,12 @@ public void testCount() throws Exception { setRandomCountParams(countRequest, expectedParams); setRandomIndicesOptions(countRequest::indicesOptions, countRequest::indicesOptions, expectedParams); - SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); - if (frequently()) { - if (randomBoolean()) { - searchSourceBuilder.minScore(randomFloat()); - } + if (randomBoolean()) { + SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); + countRequest.source(searchSourceBuilder); + } else { + countRequest.query(new MatchAllQueryBuilder()); } - countRequest.source(searchSourceBuilder); Request request = RequestConverters.count(countRequest); StringJoiner endpoint = new StringJoiner("/", "/", ""); String index = String.join(",", indices); @@ -1172,7 +1172,7 @@ public void testCount() throws Exception { assertEquals(HttpPost.METHOD_NAME, request.getMethod()); assertEquals(endpoint.toString(), request.getEndpoint()); assertEquals(expectedParams, request.getParameters()); - assertToXContentBody(searchSourceBuilder, request.getEntity()); + assertToXContentBody(countRequest, request.getEntity()); } public void testCountNullIndicesAndTypes() { diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/SearchIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/SearchIT.java index 171a0cae9da31..cf0772d8170e1 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/SearchIT.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/SearchIT.java @@ -45,6 +45,7 @@ import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.index.query.MatchAllQueryBuilder; import org.elasticsearch.index.query.MatchQueryBuilder; +import org.elasticsearch.index.query.QueryBuilder; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.index.query.ScriptQueryBuilder; import org.elasticsearch.index.query.TermsQueryBuilder; @@ -1352,9 +1353,14 @@ public void testCountOneIndexMatchQuery() throws IOException { } public void testCountMultipleIndicesMatchQueryUsingConstructor() throws IOException { - - SearchSourceBuilder sourceBuilder = new SearchSourceBuilder().query(new MatchQueryBuilder("field", "value1")); - CountRequest countRequest = new CountRequest(new String[]{"index1", "index2", "index3"}, sourceBuilder); + CountRequest countRequest; + if (randomBoolean()) { + SearchSourceBuilder sourceBuilder = new SearchSourceBuilder().query(new MatchQueryBuilder("field", "value1")); + countRequest = new CountRequest(new String[]{"index1", "index2", "index3"}, sourceBuilder); + } else { + QueryBuilder query = new MatchQueryBuilder("field", "value1"); + countRequest = new CountRequest(new String[]{"index1", "index2", "index3"}, query); + } CountResponse countResponse = execute(countRequest, highLevelClient()::count, highLevelClient()::countAsync); assertCountHeader(countResponse); assertEquals(3, countResponse.getCount()); @@ -1362,9 +1368,12 @@ public void testCountMultipleIndicesMatchQueryUsingConstructor() throws IOExcept } public void testCountMultipleIndicesMatchQuery() throws IOException { - CountRequest countRequest = new CountRequest("index1", "index2", "index3"); - countRequest.source(new SearchSourceBuilder().query(new MatchQueryBuilder("field", "value1"))); + if (randomBoolean()) { + countRequest.source(new SearchSourceBuilder().query(new MatchQueryBuilder("field", "value1"))); + } else { + countRequest.query(new MatchQueryBuilder("field", "value1")); + } CountResponse countResponse = execute(countRequest, highLevelClient()::count, highLevelClient()::countAsync); assertCountHeader(countResponse); assertEquals(3, countResponse.getCount()); @@ -1378,7 +1387,7 @@ public void testCountAllIndicesMatchQuery() throws IOException { assertCountHeader(countResponse); assertEquals(3, countResponse.getCount()); } - + public void testSearchWithBasicLicensedQuery() throws IOException { SearchRequest searchRequest = new SearchRequest("index"); SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); @@ -1390,7 +1399,7 @@ public void testSearchWithBasicLicensedQuery() throws IOException { assertFirstHit(searchResponse, hasId("2")); assertSecondHit(searchResponse, hasId("1")); } - + private static void assertCountHeader(CountResponse countResponse) { assertEquals(0, countResponse.getSkippedShards()); assertEquals(0, countResponse.getFailedShards()); diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/core/CountRequestTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/core/CountRequestTests.java index 1030f4401e160..de6eea6b00354 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/core/CountRequestTests.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/core/CountRequestTests.java @@ -20,18 +20,55 @@ package org.elasticsearch.client.core; import org.elasticsearch.action.support.IndicesOptions; +import org.elasticsearch.client.AbstractRequestTestCase; +import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.ArrayUtils; +import org.elasticsearch.common.xcontent.NamedXContentRegistry; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.index.query.MatchAllQueryBuilder; import org.elasticsearch.index.query.MatchQueryBuilder; +import org.elasticsearch.index.query.QueryBuilder; +import org.elasticsearch.rest.action.RestActions; +import org.elasticsearch.search.SearchModule; import org.elasticsearch.search.builder.SearchSourceBuilder; -import org.elasticsearch.test.ESTestCase; +import java.io.IOException; import java.util.ArrayList; import java.util.List; import static org.elasticsearch.test.EqualsHashCodeTestUtils.checkEqualsAndHashCode; +import static org.hamcrest.Matchers.equalTo; -//similar to SearchRequestTests as CountRequest inline several members (and functionality) from SearchRequest -public class CountRequestTests extends ESTestCase { +// similar to SearchRequestTests as CountRequest inline several members (and functionality) from SearchRequest +// In RestCountAction the request body is parsed as QueryBuilder (top level query field), +// so that is why this is chosen as server side instance. +public class CountRequestTests extends AbstractRequestTestCase { + + @Override + protected CountRequest createClientTestInstance() { + CountRequest countRequest = new CountRequest(); + // query is the only property that is serialized as xcontent: + if (randomBoolean()) { + countRequest.query(new MatchAllQueryBuilder()); + } + return countRequest; + } + + @Override + protected QueryBuilder doParseToServerInstance(XContentParser parser) throws IOException { + return RestActions.getQueryContent(parser); + } + + @Override + protected void assertInstances(QueryBuilder serverInstance, CountRequest clientTestInstance) { + // query is the only property that is serialized as xcontent: + assertThat(serverInstance, equalTo(clientTestInstance.query())); + } + + @Override + protected NamedXContentRegistry xContentRegistry() { + return new NamedXContentRegistry(new SearchModule(Settings.EMPTY, List.of()).getNamedXContents()); + } public void testIllegalArguments() { CountRequest countRequest = new CountRequest(); @@ -55,6 +92,8 @@ public void testIllegalArguments() { e = expectThrows(NullPointerException.class, () -> countRequest.source(null)); assertEquals("source must not be null", e.getMessage()); + e = expectThrows(NullPointerException.class, () -> countRequest.query(null)); + assertEquals("query must not be null", e.getMessage()); } public void testEqualsAndHashcode() { @@ -63,7 +102,11 @@ public void testEqualsAndHashcode() { private CountRequest createCountRequest() { CountRequest countRequest = new CountRequest("index"); - countRequest.source(new SearchSourceBuilder().query(new MatchQueryBuilder("num", 10))); + if (randomBoolean()) { + countRequest.source(new SearchSourceBuilder().query(new MatchQueryBuilder("num", 10))); + } else { + countRequest.query(new MatchQueryBuilder("num", 10)); + } return countRequest; } @@ -76,6 +119,10 @@ private CountRequest mutate(CountRequest countRequest) { mutators.add(() -> mutation.types(ArrayUtils.concat(countRequest.types(), new String[]{randomAlphaOfLength(10)}))); mutators.add(() -> mutation.preference(randomValueOtherThan(countRequest.preference(), () -> randomAlphaOfLengthBetween(3, 10)))); mutators.add(() -> mutation.routing(randomValueOtherThan(countRequest.routing(), () -> randomAlphaOfLengthBetween(3, 10)))); + mutators.add(() -> mutation.terminateAfter(randomValueOtherThan(countRequest.terminateAfter(), () -> randomIntBetween(0, 10)))); + mutators.add(() -> mutation.minScore(randomValueOtherThan(countRequest.minScore(), () -> (float) randomIntBetween(0, 10)))); + mutators.add(() -> mutation.query(randomValueOtherThan(countRequest.query(), + () -> new MatchQueryBuilder(randomAlphaOfLength(4), randomAlphaOfLength(4))))); randomFrom(mutators).run(); return mutation; } @@ -87,9 +134,11 @@ private static CountRequest copyRequest(CountRequest countRequest) { result.types(countRequest.types()); result.routing(countRequest.routing()); result.preference(countRequest.preference()); - if (countRequest.source() != null) { - result.source(countRequest.source()); + if (countRequest.query() != null) { + result.query(countRequest.query()); } + result.terminateAfter(countRequest.terminateAfter()); + result.minScore(countRequest.minScore()); return result; } }