From df329924c41bfa756f77f5a3a22cb4b761c97c9a Mon Sep 17 00:00:00 2001 From: Milan Mrdjen Date: Wed, 3 Oct 2018 14:10:23 +0200 Subject: [PATCH 1/3] Add document _count API support to Rest High Level Client. Add `count()` api method, `CountRequest` and `CountResponse` classes to HLRC. Code in server module is unchanged. Relates to #27205 --- .../client/RequestConverters.java | 11 + .../client/RestHighLevelClient.java | 25 ++ .../client/count/CountRequest.java | 206 ++++++++++++++++ .../client/count/CountResponse.java | 225 ++++++++++++++++++ .../org/elasticsearch/client/CountIT.java | 138 +++++++++++ .../client/CountRequestTests.java | 96 ++++++++ .../client/CountResponseTests.java | 150 ++++++++++++ .../client/RequestConvertersTests.java | 135 ++++++++--- .../client/RestHighLevelClientTests.java | 1 - .../documentation/CountDocumentationIT.java | 171 +++++++++++++ .../high-level/search/count.asciidoc | 145 +++++++++++ .../high-level/supported-apis.asciidoc | 2 + 12 files changed, 1270 insertions(+), 35 deletions(-) create mode 100644 client/rest-high-level/src/main/java/org/elasticsearch/client/count/CountRequest.java create mode 100644 client/rest-high-level/src/main/java/org/elasticsearch/client/count/CountResponse.java create mode 100644 client/rest-high-level/src/test/java/org/elasticsearch/client/CountIT.java create mode 100644 client/rest-high-level/src/test/java/org/elasticsearch/client/CountRequestTests.java create mode 100644 client/rest-high-level/src/test/java/org/elasticsearch/client/CountResponseTests.java create mode 100644 client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/CountDocumentationIT.java create mode 100644 docs/java-rest/high-level/search/count.asciidoc 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 9c461a404cf8f..afdf63a136360 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 @@ -49,6 +49,7 @@ import org.elasticsearch.action.support.IndicesOptions; import org.elasticsearch.action.support.WriteRequest; import org.elasticsearch.action.update.UpdateRequest; +import org.elasticsearch.client.count.CountRequest; import org.elasticsearch.client.security.RefreshPolicy; import org.elasticsearch.cluster.health.ClusterHealthStatus; import org.elasticsearch.common.Nullable; @@ -427,6 +428,16 @@ static Request multiSearchTemplate(MultiSearchTemplateRequest multiSearchTemplat return request; } + static Request count(CountRequest countRequest) throws IOException { + Request request = new Request(HttpPost.METHOD_NAME, endpoint(countRequest.indices(), countRequest.types(), "_count")); + Params params = new Params(request); + params.withRouting(countRequest.routing()); + params.withPreference(countRequest.preference()); + params.withIndicesOptions(countRequest.indicesOptions()); + request.setEntity(createEntity(countRequest.source(), REQUEST_BODY_CONTENT_TYPE)); + return request; + } + static Request explain(ExplainRequest explainRequest) throws IOException { Request request = new Request(HttpGet.METHOD_NAME, endpoint(explainRequest.index(), explainRequest.type(), explainRequest.id(), "_explain")); diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/RestHighLevelClient.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/RestHighLevelClient.java index 86782b364a060..37b2291f97e7f 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/RestHighLevelClient.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/RestHighLevelClient.java @@ -56,6 +56,8 @@ import org.elasticsearch.action.support.master.AcknowledgedResponse; import org.elasticsearch.action.update.UpdateRequest; import org.elasticsearch.action.update.UpdateResponse; +import org.elasticsearch.client.count.CountRequest; +import org.elasticsearch.client.count.CountResponse; import org.elasticsearch.common.CheckedConsumer; import org.elasticsearch.common.CheckedFunction; import org.elasticsearch.common.ParseField; @@ -749,6 +751,29 @@ public final void indexAsync(IndexRequest indexRequest, RequestOptions options, emptySet()); } + /** + * Executes a count request using the Count API + * @param countRequest the request + * @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized + * @return the response + * @throws IOException in case there is a problem sending the request or parsing back the response + */ + public final CountResponse count(CountRequest countRequest, RequestOptions options) throws IOException { + return performRequestAndParseEntity(countRequest, RequestConverters::count, options, CountResponse::fromXContent, + emptySet()); + } + + /** + * Asynchronously executes a count request using the Count API + * @param countRequest the request + * @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized + * @param listener the listener to be notified upon request completion + */ + public final void countAsync(CountRequest countRequest, RequestOptions options, ActionListener listener) { + performRequestAsyncAndParseEntity(countRequest, RequestConverters::count, options,CountResponse::fromXContent, + listener, emptySet()); + } + /** * Updates a document using the Update API. * See Update API on elastic.co diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/count/CountRequest.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/count/CountRequest.java new file mode 100644 index 0000000000000..5e5b1973cf8e8 --- /dev/null +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/count/CountRequest.java @@ -0,0 +1,206 @@ +/* + * 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.client.count; + +import org.elasticsearch.action.ActionRequest; +import org.elasticsearch.action.ActionRequestValidationException; +import org.elasticsearch.action.IndicesRequest; +import org.elasticsearch.action.support.IndicesOptions; +import org.elasticsearch.common.Strings; +import org.elasticsearch.search.builder.SearchSourceBuilder; + +import java.util.Arrays; +import java.util.Objects; + +import static org.elasticsearch.action.search.SearchRequest.DEFAULT_INDICES_OPTIONS; + +/** + * Encapsulates a request to _count API against one, several or all indices. + */ +public final class CountRequest extends ActionRequest implements IndicesRequest.Replaceable { + + private String[] indices = Strings.EMPTY_ARRAY; + private String[] types = Strings.EMPTY_ARRAY; + private String routing; + private String preference; + private SearchSourceBuilder searchSourceBuilder; + private IndicesOptions indicesOptions = DEFAULT_INDICES_OPTIONS; + + public CountRequest() { + this.searchSourceBuilder = new SearchSourceBuilder(); + } + + /** + * 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()); + } + + /** + * Constructs a new search request against the provided indices with the given search source. + */ + public CountRequest(String[] indices, SearchSourceBuilder searchSourceBuilder) { + indices(indices); + this.searchSourceBuilder = searchSourceBuilder; + } + + @Override + public ActionRequestValidationException validate() { + return null; + } + + /** + * Sets the indices the count will be executed on. + */ + public CountRequest indices(String... indices) { + Objects.requireNonNull(indices, "indices must not be null"); + for (String index : indices) { + Objects.requireNonNull(index, "index must not be null"); + } + this.indices = indices; + return this; + } + + /** + * The source of the count request. + */ + public CountRequest source(SearchSourceBuilder searchSourceBuilder) { + this.searchSourceBuilder = Objects.requireNonNull(searchSourceBuilder, "source must not be null"); + return this; + } + + /** + * The document types to execute the count against. Defaults to be executed against all types. + * + * @deprecated Types are going away, prefer filtering on a type. + */ + @Deprecated + public CountRequest types(String... types) { + Objects.requireNonNull(types, "types must not be null"); + for (String type : types) { + Objects.requireNonNull(type, "type must not be null"); + } + this.types = types; + return this; + } + + /** + * The routing values to control the shards that the search will be executed on. + */ + public CountRequest routing(String routing) { + this.routing = routing; + return this; + } + + /** + * A comma separated list of routing values to control the shards the count will be executed on. + */ + public CountRequest routing(String... routings) { + this.routing = Strings.arrayToCommaDelimitedString(routings); + return this; + } + + /** + * Returns the indices options used to resolve indices. They tell for instance whether a single index is accepted, whether an empty + * array will be converted to _all, and how wildcards will be expanded if needed. + * + * @see org.elasticsearch.action.support.IndicesOptions + */ + public CountRequest indicesOptions(IndicesOptions indicesOptions) { + this.indicesOptions = Objects.requireNonNull(indicesOptions, "indicesOptions must not be null"); + return this; + } + + /** + * Sets the preference to execute the count. Defaults to randomize across shards. Can be set to {@code _local} to prefer local shards + * or a custom value, which guarantees that the same order will be used across different requests. + */ + public CountRequest preference(String preference) { + this.preference = preference; + return this; + } + + public IndicesOptions indicesOptions() { + return this.indicesOptions; + } + + public String routing() { + return this.routing; + } + + public String preference() { + return this.preference; + } + + public String[] indices() { + return Arrays.copyOf(this.indices, this.indices.length); + } + + public Float minScore() { + return this.searchSourceBuilder.minScore(); + } + + public CountRequest minScore(Float minScore) { + this.searchSourceBuilder.minScore(minScore); + return this; + } + + public int terminateAfter() { + return this.searchSourceBuilder.terminateAfter(); + } + + public CountRequest terminateAfter(int terminateAfter) { + this.searchSourceBuilder.terminateAfter(terminateAfter); + return this; + } + + public String[] types() { + return Arrays.copyOf(this.types, this.types.length); + } + + public SearchSourceBuilder source() { + return this.searchSourceBuilder; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + CountRequest that = (CountRequest) o; + return Objects.equals(indicesOptions, that.indicesOptions) && + Arrays.equals(indices, that.indices) && + Arrays.equals(types, that.types) && + Objects.equals(routing, that.routing) && + Objects.equals(preference, that.preference); + } + + @Override + public int hashCode() { + int result = Objects.hash(indicesOptions, routing, preference); + result = 31 * result + Arrays.hashCode(indices); + result = 31 * result + Arrays.hashCode(types); + return result; + } +} diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/count/CountResponse.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/count/CountResponse.java new file mode 100644 index 0000000000000..fd643c8faa85e --- /dev/null +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/count/CountResponse.java @@ -0,0 +1,225 @@ +/* + * 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.client.count; + +import org.elasticsearch.action.ActionResponse; +import org.elasticsearch.action.search.ShardSearchFailure; +import org.elasticsearch.common.ParseField; +import org.elasticsearch.common.Strings; +import org.elasticsearch.common.xcontent.StatusToXContentObject; +import org.elasticsearch.common.xcontent.ToXContent; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.rest.RestStatus; +import org.elasticsearch.rest.action.RestActions; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureExpectedToken; + +/** + * A response to _count API request. + */ +public final class CountResponse extends ActionResponse implements StatusToXContentObject { + + private static final ParseField COUNT = new ParseField("count"); + private static final ParseField TERMINATED_EARLY = new ParseField("terminated_early"); + private static final ParseField SHARDS = new ParseField("_shards"); + + private final long count; + private final Boolean terminatedEarly; + private final ShardStats shardStats; + + public CountResponse(long count, Boolean terminatedEarly, ShardStats shardStats) { + this.count = count; + this.terminatedEarly = terminatedEarly; + this.shardStats = shardStats; + } + + /** + * Number of documents matching request. + */ + public long getCount() { + return count; + } + + /** + * The total number of shards the search was executed on. + */ + public int getTotalShards() { + return shardStats.totalShards; + } + + /** + * The successful number of shards the search was executed on. + */ + public int getSuccessfulShards() { + return shardStats.successfulShards; + } + + /** + * The number of shards skipped due to pre-filtering + */ + public int getSkippedShards() { + return shardStats.skippedShards; + } + + /** + * The failed number of shards the search was executed on. + */ + public int getFailedShards() { + return shardStats.shardFailures.length; + } + + /** + * The failures that occurred during the search. + */ + public ShardSearchFailure[] getShardFailures() { + return shardStats.shardFailures; + } + + @Override + public RestStatus status() { + return RestStatus.status(shardStats.successfulShards, shardStats.totalShards, shardStats.shardFailures); + } + + @Override + public String toString() { + return Strings.toString(this); + } + + public static CountResponse fromXContent(XContentParser parser) throws IOException { + ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.nextToken(), parser::getTokenLocation); + parser.nextToken(); + ensureExpectedToken(XContentParser.Token.FIELD_NAME, parser.currentToken(), parser::getTokenLocation); + String currentName = parser.currentName(); + Boolean terminatedEarly = null; + long count = 0; + ShardStats shardStats = new ShardStats(-1, -1,0, ShardSearchFailure.EMPTY_ARRAY); + + for (XContentParser.Token token = parser.nextToken(); token != XContentParser.Token.END_OBJECT; token = parser.nextToken()) { + if (token == XContentParser.Token.FIELD_NAME) { + currentName = parser.currentName(); + } else if (token.isValue()) { + if (COUNT.match(currentName, parser.getDeprecationHandler())) { + count = parser.longValue(); + } else if (TERMINATED_EARLY.match(currentName, parser.getDeprecationHandler())) { + terminatedEarly = parser.booleanValue(); + } else { + parser.skipChildren(); + } + } else if (token == XContentParser.Token.START_OBJECT) { + if (SHARDS.match(currentName, parser.getDeprecationHandler())) { + shardStats = ShardStats.fromXContent(parser); + } else { + parser.skipChildren(); + } + } + } + return new CountResponse(count, terminatedEarly, shardStats); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + builder.field(COUNT.getPreferredName(), count); + if (isTerminatedEarly() != null) { + builder.field(TERMINATED_EARLY.getPreferredName(), isTerminatedEarly()); + } + shardStats.toXContent(builder, params); + builder.endObject(); + return builder; + } + + public Boolean isTerminatedEarly() { + return terminatedEarly; + } + + /** + * Encapsulates _shards section of count api response. + */ + public static final class ShardStats implements ToXContent { + + static final ParseField FAILED = new ParseField("failed"); + static final ParseField SKIPPED = new ParseField("skipped"); + static final ParseField TOTAL = new ParseField("total"); + static final ParseField SUCCESSFUL = new ParseField("successful"); + static final ParseField FAILURES = new ParseField("failures"); + + private final int successfulShards; + private final int totalShards; + private final int skippedShards; + private final ShardSearchFailure[] shardFailures; + + public ShardStats(int successfulShards, int totalShards, int skippedShards, ShardSearchFailure[] shardFailures) { + this.successfulShards = successfulShards; + this.totalShards = totalShards; + this.skippedShards = skippedShards; + this.shardFailures = Arrays.copyOf(shardFailures, shardFailures.length); + } + + static ShardStats fromXContent(XContentParser parser) throws IOException { + int successfulShards = -1; + int totalShards = -1; + int skippedShards = 0; //BWC @see org.elasticsearch.action.search.SearchResponse + List failures = new ArrayList<>(); + XContentParser.Token token; + String currentName = parser.currentName(); + while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { + if (token == XContentParser.Token.FIELD_NAME) { + currentName = parser.currentName(); + } else if (token.isValue()) { + if (FAILED.match(currentName, parser.getDeprecationHandler())) { + parser.intValue(); + } else if (SKIPPED.match(currentName, parser.getDeprecationHandler())) { + skippedShards = parser.intValue(); + } else if (TOTAL.match(currentName, parser.getDeprecationHandler())) { + totalShards = parser.intValue(); + } else if (SUCCESSFUL.match(currentName, parser.getDeprecationHandler())) { + successfulShards = parser.intValue(); + } else { + parser.skipChildren(); + } + } else if (token == XContentParser.Token.START_ARRAY) { + if (FAILURES.match(currentName, parser.getDeprecationHandler())) { + while ((parser.nextToken()) != XContentParser.Token.END_ARRAY) { + failures.add(ShardSearchFailure.fromXContent(parser)); + } + } else { + parser.skipChildren(); + } + } else { + parser.skipChildren(); + } + } + return new ShardStats(successfulShards, totalShards, skippedShards, failures.toArray(new ShardSearchFailure[failures.size()])); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + RestActions.buildBroadcastShardsHeader(builder, params, totalShards, successfulShards, skippedShards, + shardFailures.length, shardFailures); + return builder; + } + } +} diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/CountIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/CountIT.java new file mode 100644 index 0000000000000..17dfb9405e830 --- /dev/null +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/CountIT.java @@ -0,0 +1,138 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.client; + +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.methods.HttpPut; +import org.elasticsearch.client.count.CountRequest; +import org.elasticsearch.client.count.CountResponse; +import org.elasticsearch.index.query.MatchQueryBuilder; +import org.elasticsearch.search.builder.SearchSourceBuilder; +import org.junit.Before; + +import java.io.IOException; + +import static org.hamcrest.Matchers.greaterThan; + +public class CountIT extends ESRestHighLevelClientTestCase { + + @Before + public void indexDocuments() throws IOException { + + Request doc1 = new Request(HttpPut.METHOD_NAME, "/index/type/1"); + doc1.setJsonEntity("{\"type\":\"type1\", \"num\":10, \"num2\":50}"); + client().performRequest(doc1); + + Request doc2 = new Request(HttpPut.METHOD_NAME, "/index/type/2"); + doc2.setJsonEntity("{\"type\":\"type1\", \"num\":20, \"num2\":40}"); + client().performRequest(doc2); + + Request doc3 = new Request(HttpPut.METHOD_NAME, "/index/type/3"); + doc3.setJsonEntity("{\"type\":\"type1\", \"num\":50, \"num2\":35}"); + client().performRequest(doc3); + + Request doc4 = new Request(HttpPut.METHOD_NAME, "/index/type/4"); + doc4.setJsonEntity("{\"type\":\"type2\", \"num\":100, \"num2\":10}"); + client().performRequest(doc4); + + Request doc5 = new Request(HttpPut.METHOD_NAME, "/index/type/5"); + doc5.setJsonEntity("{\"type\":\"type2\", \"num\":100, \"num2\":10}"); + client().performRequest(doc5); + + client().performRequest(new Request(HttpPost.METHOD_NAME, "/_refresh")); + + Request doc6 = new Request(HttpPut.METHOD_NAME, "/index1/doc/1"); + doc6.setJsonEntity("{\"field\":\"value1\", \"rating\": 7}"); + client().performRequest(doc6); + + Request doc7 = new Request(HttpPut.METHOD_NAME, "/index1/doc/2"); + doc7.setJsonEntity("{\"field\":\"value2\"}"); + client().performRequest(doc7); + + Request doc2_1 = new Request(HttpPut.METHOD_NAME, "/index2/doc/1"); + doc2_1.setJsonEntity("{\"type\":\"type1\", \"num\":10, \"num2\":50}"); + client().performRequest(doc2_1); + + client().performRequest(new Request(HttpPost.METHOD_NAME, "/index,index1,index2/_refresh")); + + } + + public void testCountOneIndexNoQuery() throws IOException { + CountRequest countRequest = new CountRequest("index"); + CountResponse countResponse = execute(countRequest, highLevelClient()::count, highLevelClient()::countAsync); + assertCountHeader(countResponse); + assertEquals(5, countResponse.getCount()); + } + + public void testCountMultipleIndicesNoQuery() throws IOException { + CountRequest countRequest = new CountRequest("index", "index1"); + CountResponse countResponse = execute(countRequest, highLevelClient()::count, highLevelClient()::countAsync); + assertCountHeader(countResponse); + assertEquals(7, countResponse.getCount()); + } + + public void testCountAllIndicesNoQuery() throws IOException { + CountRequest countRequest = new CountRequest(); + CountResponse countResponse = execute(countRequest, highLevelClient()::count, highLevelClient()::countAsync); + assertCountHeader(countResponse); + assertEquals(8, countResponse.getCount()); + } + + public void testCountOneIndexMatchQuery() throws IOException { + CountRequest countRequest = new CountRequest("index"); + countRequest.source(new SearchSourceBuilder().query(new MatchQueryBuilder("num", 10))); + CountResponse countResponse = execute(countRequest, highLevelClient()::count, highLevelClient()::countAsync); + assertCountHeader(countResponse); + assertEquals(1, countResponse.getCount()); + } + + public void testCountMultipleIndicesMatchQueryUsingConstructor() throws IOException { + SearchSourceBuilder sourceBuilder = new SearchSourceBuilder().query(new MatchQueryBuilder("num", 10)); + CountRequest countRequest = new CountRequest(new String[]{"index", "index2"}, sourceBuilder); + CountResponse countResponse = execute(countRequest, highLevelClient()::count, highLevelClient()::countAsync); + assertCountHeader(countResponse); + assertEquals(2, countResponse.getCount()); + } + + public void testCountMultipleIndicesMatchQuery() throws IOException { + CountRequest countRequest = new CountRequest("index", "index2"); + countRequest.source(new SearchSourceBuilder().query(new MatchQueryBuilder("num", 10))); + CountResponse countResponse = execute(countRequest, highLevelClient()::count, highLevelClient()::countAsync); + assertCountHeader(countResponse); + assertEquals(2, countResponse.getCount()); + } + + public void testCountAllIndicesMatchQuery() throws IOException { + CountRequest countRequest = new CountRequest(); + countRequest.source(new SearchSourceBuilder().query(new MatchQueryBuilder("num", 10))); + CountResponse countResponse = execute(countRequest, highLevelClient()::count, highLevelClient()::countAsync); + assertCountHeader(countResponse); + assertEquals(2, countResponse.getCount()); + } + + private static void assertCountHeader(CountResponse countResponse) { + assertEquals(0, countResponse.getSkippedShards()); + assertEquals(0, countResponse.getFailedShards()); + assertThat(countResponse.getTotalShards(), greaterThan(0)); + assertEquals(countResponse.getTotalShards(), countResponse.getSuccessfulShards()); + assertEquals(0, countResponse.getShardFailures().length); + } + +} diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/CountRequestTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/CountRequestTests.java new file mode 100644 index 0000000000000..d67a720a25cfc --- /dev/null +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/CountRequestTests.java @@ -0,0 +1,96 @@ +/* + * 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.client; + +import org.elasticsearch.action.support.IndicesOptions; +import org.elasticsearch.client.count.CountRequest; +import org.elasticsearch.common.util.ArrayUtils; +import org.elasticsearch.index.query.MatchQueryBuilder; +import org.elasticsearch.search.builder.SearchSourceBuilder; +import org.elasticsearch.test.ESTestCase; + +import java.util.ArrayList; +import java.util.List; + +import static org.elasticsearch.test.EqualsHashCodeTestUtils.checkEqualsAndHashCode; + +//quite similar to SearchRequestTests as CountRequest wraps SearchRequest +public class CountRequestTests extends ESTestCase { + + public void testIllegalArguments() { + CountRequest countRequest = new CountRequest(); + assertNotNull(countRequest.indices()); + assertNotNull(countRequest.indicesOptions()); + assertNotNull(countRequest.types()); + + NullPointerException e = expectThrows(NullPointerException.class, () -> countRequest.indices((String[]) null)); + assertEquals("indices must not be null", e.getMessage()); + e = expectThrows(NullPointerException.class, () -> countRequest.indices((String) null)); + assertEquals("index must not be null", e.getMessage()); + + e = expectThrows(NullPointerException.class, () -> countRequest.indicesOptions(null)); + assertEquals("indicesOptions must not be null", e.getMessage()); + + e = expectThrows(NullPointerException.class, () -> countRequest.types((String[]) null)); + assertEquals("types must not be null", e.getMessage()); + e = expectThrows(NullPointerException.class, () -> countRequest.types((String) null)); + assertEquals("type must not be null", e.getMessage()); + + e = expectThrows(NullPointerException.class, () -> countRequest.source(null)); + assertEquals("source must not be null", e.getMessage()); + + } + + public void testEqualsAndHashcode() { + checkEqualsAndHashCode(createCountRequest(), CountRequestTests::copyRequest, this::mutate); + } + + private CountRequest createCountRequest() { + CountRequest countRequest = new CountRequest("index"); + countRequest.source(new SearchSourceBuilder().query(new MatchQueryBuilder("num", 10))); + return countRequest; + } + + private CountRequest mutate(CountRequest countRequest) { + CountRequest mutation = copyRequest(countRequest); + List mutators = new ArrayList<>(); + mutators.add(() -> mutation.indices(ArrayUtils.concat(countRequest.indices(), new String[]{randomAlphaOfLength(10)}))); + mutators.add(() -> mutation.indicesOptions(randomValueOtherThan(countRequest.indicesOptions(), + () -> IndicesOptions.fromOptions(randomBoolean(), randomBoolean(), randomBoolean(), randomBoolean())))); + 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)))); + randomFrom(mutators).run(); + return mutation; + } + + private static CountRequest copyRequest(CountRequest countRequest) { + CountRequest result = new CountRequest(); + result.indices(countRequest.indices()); + result.indicesOptions(countRequest.indicesOptions()); + result.types(countRequest.types()); + result.routing(countRequest.routing()); + result.preference(countRequest.preference()); + if (countRequest.source() != null) { + result.source(countRequest.source()); + } + return result; + } +} diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/CountResponseTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/CountResponseTests.java new file mode 100644 index 0000000000000..fb0c1855c9189 --- /dev/null +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/CountResponseTests.java @@ -0,0 +1,150 @@ +/* + * 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.client; + +import org.elasticsearch.action.OriginalIndices; +import org.elasticsearch.action.search.ShardSearchFailure; +import org.elasticsearch.client.count.CountResponse; +import org.elasticsearch.cluster.metadata.IndexMetaData; +import org.elasticsearch.common.ParsingException; +import org.elasticsearch.common.Strings; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.index.Index; +import org.elasticsearch.index.shard.ShardId; +import org.elasticsearch.search.SearchShardTarget; +import org.elasticsearch.test.AbstractXContentTestCase; + +import java.io.IOException; + +public class CountResponseTests extends AbstractXContentTestCase { + + @Override + protected CountResponse createTestInstance() { + long count = 5; + Boolean terminatedEarly = randomBoolean() ? null : randomBoolean(); + int totalShards = randomIntBetween(1, Integer.MAX_VALUE); + int successfulShards = randomIntBetween(0, totalShards); + int skippedShards = randomIntBetween(0, totalShards); + int numFailures = randomIntBetween(1, 5); + ShardSearchFailure[] failures = new ShardSearchFailure[numFailures]; + for (int i = 0; i < failures.length; i++) { + failures[i] = createShardFailureTestItem(); + } + CountResponse.ShardStats shardStats = new CountResponse.ShardStats(successfulShards, totalShards, skippedShards, + randomBoolean() ? ShardSearchFailure.EMPTY_ARRAY : failures); + return new CountResponse(count, terminatedEarly, shardStats); + } + + + @SuppressWarnings("Duplicates") //suppress warning, as original code is in server:test:SearchResponseTests, would need to add a testJar + // (similar as x-pack), or move test code from server to test framework + public static ShardSearchFailure createShardFailureTestItem() { + String randomMessage = randomAlphaOfLengthBetween(3, 20); + Exception ex = new ParsingException(0, 0, randomMessage , new IllegalArgumentException("some bad argument")); + SearchShardTarget searchShardTarget = null; + if (randomBoolean()) { + String nodeId = randomAlphaOfLengthBetween(5, 10); + String indexName = randomAlphaOfLengthBetween(5, 10); + searchShardTarget = new SearchShardTarget(nodeId, + new ShardId(new Index(indexName, IndexMetaData.INDEX_UUID_NA_VALUE), randomInt()), null, null); + } + return new ShardSearchFailure(ex, searchShardTarget); + } + + @Override + protected CountResponse doParseInstance(XContentParser parser) throws IOException { + return CountResponse.fromXContent(parser); + } + + @Override + protected boolean supportsUnknownFields() { + return true; + } + + @Override + protected boolean assertToXContentEquivalence() { + return false; + } + + @Override + protected void assertEqualInstances(CountResponse expectedInstance, CountResponse newInstance) { + assertEquals(expectedInstance.getCount(), newInstance.getCount()); + assertEquals(expectedInstance.status(), newInstance.status()); + assertEquals(expectedInstance.isTerminatedEarly(), newInstance.isTerminatedEarly()); + assertEquals(expectedInstance.getTotalShards(), newInstance.getTotalShards()); + assertEquals(expectedInstance.getFailedShards(), newInstance.getFailedShards()); + assertEquals(expectedInstance.getSkippedShards(), newInstance.getSkippedShards()); + assertEquals(expectedInstance.getSuccessfulShards(), newInstance.getSuccessfulShards()); + assertEquals(expectedInstance.getShardFailures().length, newInstance.getShardFailures().length); + + ShardSearchFailure[] expectedFailures = expectedInstance.getShardFailures(); + ShardSearchFailure[] newFailures = newInstance.getShardFailures(); + + for (int i = 0; i < newFailures.length; i++) { + ShardSearchFailure parsedFailure = newFailures[i]; + ShardSearchFailure originalFailure = expectedFailures[i]; + assertEquals(originalFailure.index(), parsedFailure.index()); + assertEquals(originalFailure.shard(), parsedFailure.shard()); + assertEquals(originalFailure.shardId(), parsedFailure.shardId()); + String originalMsg = originalFailure.getCause().getMessage(); + assertEquals(parsedFailure.getCause().getMessage(), "Elasticsearch exception [type=parsing_exception, reason=" + + originalMsg + "]"); + String nestedMsg = originalFailure.getCause().getCause().getMessage(); + assertEquals(parsedFailure.getCause().getCause().getMessage(), + "Elasticsearch exception [type=illegal_argument_exception, reason=" + nestedMsg + "]"); + } + } + + + public void testToXContent() { + CountResponse response = new CountResponse(8, null, new CountResponse.ShardStats(1, 1, 0, ShardSearchFailure.EMPTY_ARRAY)); + String expectedString = "{\"count\":8,\"_shards\":{\"total\":1,\"successful\":1,\"skipped\":0,\"failed\":0}}"; + assertEquals(expectedString, Strings.toString(response)); + } + + public void testToXContentWithTerminatedEarly() { + CountResponse response = new CountResponse(8, true, new CountResponse.ShardStats(1, 1, 0, ShardSearchFailure.EMPTY_ARRAY)); + String expectedString = "{\"count\":8,\"terminated_early\":true,\"_shards\":{\"total\":1,\"successful\":1,\"skipped\":0," + + "\"failed\":0}}"; + assertEquals(expectedString, Strings.toString(response)); + } + + public void testToXContentWithTerminatedEarlyAndShardFailures() { + ShardSearchFailure failure = new ShardSearchFailure(new ParsingException(0, 0, "not looking well", null), + new SearchShardTarget("nodeId", new ShardId(new Index("indexName", "indexUuid"), 1), null, OriginalIndices.NONE)); + CountResponse response = new CountResponse(8, true, new CountResponse.ShardStats(1, 2, 0, new ShardSearchFailure[]{failure})); + String expectedString = + "{\"count\":8," + + "\"terminated_early\":true," + + "\"_shards\":" + + "{\"total\":2," + + "\"successful\":1," + + "\"skipped\":0," + + "\"failed\":1," + + "\"failures\":" + + "[{\"shard\":1," + + "\"index\":\"indexName\"," + + "\"node\":\"nodeId\"," + + "\"reason\":{\"type\":\"parsing_exception\",\"reason\":\"not looking well\",\"line\":0,\"col\":0}}]" + + "}" + + "}"; + assertEquals(expectedString, Strings.toString(response)); + } +} 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 3801dfe71de9c..2ca328c6e6aa6 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 @@ -55,6 +55,7 @@ import org.elasticsearch.action.support.replication.ReplicationRequest; import org.elasticsearch.action.update.UpdateRequest; import org.elasticsearch.client.RequestConverters.EndpointBuilder; +import org.elasticsearch.client.count.CountRequest; import org.elasticsearch.common.CheckedBiConsumer; import org.elasticsearch.common.Strings; import org.elasticsearch.common.bytes.BytesArray; @@ -473,7 +474,7 @@ public void testRethrottle() { Float requestsPerSecond; Map expectedParams = new HashMap<>(); if (frequently()) { - requestsPerSecond = (float) randomDoubleBetween(0.0, 100.0, true); + requestsPerSecond = (float) randomDoubleBetween(0.0, 100.0, true); rethrottleRequest = new RethrottleRequest(taskId, requestsPerSecond); expectedParams.put(RethrottleRequest.REQUEST_PER_SECOND_PARAMETER, Float.toString(requestsPerSecond)); } else { @@ -484,9 +485,9 @@ public void testRethrottle() { List>> variants = new ArrayList<>(); variants.add(new Tuple>("_reindex", () -> RequestConverters.rethrottleReindex(rethrottleRequest))); variants.add(new Tuple>("_update_by_query", - () -> RequestConverters.rethrottleUpdateByQuery(rethrottleRequest))); + () -> RequestConverters.rethrottleUpdateByQuery(rethrottleRequest))); variants.add(new Tuple>("_delete_by_query", - () -> RequestConverters.rethrottleDeleteByQuery(rethrottleRequest))); + () -> RequestConverters.rethrottleDeleteByQuery(rethrottleRequest))); for (Tuple> variant : variants) { Request request = variant.v2().get(); @@ -678,7 +679,7 @@ public void testUpdateWithDifferentContentTypes() { RequestConverters.update(updateRequest); }); assertEquals("Update request cannot have different content types for doc [JSON] and upsert [YAML] documents", - exception.getMessage()); + exception.getMessage()); } public void testBulk() throws IOException { @@ -829,20 +830,20 @@ public void testBulkWithDifferentContentTypes() throws IOException { bulkRequest.add(new IndexRequest("index", "type", "1").source(singletonMap("field", "value"), XContentType.JSON)); IllegalArgumentException exception = expectThrows(IllegalArgumentException.class, () -> RequestConverters.bulk(bulkRequest)); assertEquals( - "Mismatching content-type found for request with content-type [JSON], " + "previous requests have content-type [SMILE]", - exception.getMessage()); + "Mismatching content-type found for request with content-type [JSON], " + "previous requests have content-type [SMILE]", + exception.getMessage()); } { BulkRequest bulkRequest = new BulkRequest(); bulkRequest.add(new IndexRequest("index", "type", "0").source(singletonMap("field", "value"), XContentType.JSON)); bulkRequest.add(new IndexRequest("index", "type", "1").source(singletonMap("field", "value"), XContentType.JSON)); bulkRequest.add(new UpdateRequest("index", "type", "2") - .doc(new IndexRequest().source(singletonMap("field", "value"), XContentType.JSON)) - .upsert(new IndexRequest().source(singletonMap("field", "value"), XContentType.SMILE))); + .doc(new IndexRequest().source(singletonMap("field", "value"), XContentType.JSON)) + .upsert(new IndexRequest().source(singletonMap("field", "value"), XContentType.SMILE))); IllegalArgumentException exception = expectThrows(IllegalArgumentException.class, () -> RequestConverters.bulk(bulkRequest)); assertEquals( - "Mismatching content-type found for request with content-type [SMILE], " + "previous requests have content-type [JSON]", - exception.getMessage()); + "Mismatching content-type found for request with content-type [SMILE], " + "previous requests have content-type [JSON]", + exception.getMessage()); } { XContentType xContentType = randomFrom(XContentType.CBOR, XContentType.YAML); @@ -855,7 +856,7 @@ public void testBulkWithDifferentContentTypes() throws IOException { bulkRequest.add(new IndexRequest("index", "type", "1").source(singletonMap("field", "value"), xContentType)); IllegalArgumentException exception = expectThrows(IllegalArgumentException.class, () -> RequestConverters.bulk(bulkRequest)); assertEquals("Unsupported content-type found for request with content-type [" + xContentType - + "], only JSON and SMILE are supported", exception.getMessage()); + + "], only JSON and SMILE are supported", exception.getMessage()); } } @@ -911,15 +912,15 @@ public void testSearch() throws Exception { } if (randomBoolean()) { searchSourceBuilder.aggregation(new TermsAggregationBuilder(randomAlphaOfLengthBetween(3, 10), ValueType.STRING) - .field(randomAlphaOfLengthBetween(3, 10))); + .field(randomAlphaOfLengthBetween(3, 10))); } if (randomBoolean()) { searchSourceBuilder.suggest(new SuggestBuilder().addSuggestion(randomAlphaOfLengthBetween(3, 10), - new CompletionSuggestionBuilder(randomAlphaOfLengthBetween(3, 10)))); + new CompletionSuggestionBuilder(randomAlphaOfLengthBetween(3, 10)))); } if (randomBoolean()) { searchSourceBuilder.addRescorer(new QueryRescorerBuilder( - new TermQueryBuilder(randomAlphaOfLengthBetween(3, 10), randomAlphaOfLengthBetween(3, 10)))); + new TermQueryBuilder(randomAlphaOfLengthBetween(3, 10), randomAlphaOfLengthBetween(3, 10)))); } if (randomBoolean()) { searchSourceBuilder.collapse(new CollapseBuilder(randomAlphaOfLengthBetween(3, 10))); @@ -951,6 +952,60 @@ public void testSearchNullIndicesAndTypes() { expectThrows(NullPointerException.class, () -> new SearchRequest().types((String[]) null)); } + public void testCountNotNullSource() throws IOException { + //as we create SearchSourceBuilder in CountRequest constructor + CountRequest countRequest = new CountRequest(); + Request request = RequestConverters.count(countRequest); + assertEquals(HttpPost.METHOD_NAME, request.getMethod()); + assertEquals("/_count", request.getEndpoint()); + assertNotNull(request.getEntity()); + } + + public void testCount() throws Exception { + String[] indices = randomIndicesNames(0, 5); + CountRequest countRequest = new CountRequest(indices); + + int numTypes = randomIntBetween(0, 5); + String[] types = new String[numTypes]; + for (int i = 0; i < numTypes; i++) { + types[i] = "type-" + randomAlphaOfLengthBetween(2, 5); + } + countRequest.types(types); + + Map expectedParams = new HashMap<>(); + setRandomCountParams(countRequest, expectedParams); + setRandomIndicesOptions(countRequest::indicesOptions, countRequest::indicesOptions, expectedParams); + + SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); + if (frequently()) { + if (randomBoolean()) { + searchSourceBuilder.minScore(randomFloat()); + } + } + countRequest.source(searchSourceBuilder); + Request request = RequestConverters.count(countRequest); + StringJoiner endpoint = new StringJoiner("/", "/", ""); + String index = String.join(",", indices); + if (Strings.hasLength(index)) { + endpoint.add(index); + } + String type = String.join(",", types); + if (Strings.hasLength(type)) { + endpoint.add(type); + } + endpoint.add("_count"); + assertEquals(HttpPost.METHOD_NAME, request.getMethod()); + assertEquals(endpoint.toString(), request.getEndpoint()); + assertEquals(expectedParams, request.getParameters()); + assertToXContentBody(searchSourceBuilder, request.getEntity()); + } + + public void testCountNullIndicesAndTypes() { + expectThrows(NullPointerException.class, () -> new CountRequest((String[]) null)); + expectThrows(NullPointerException.class, () -> new CountRequest().indices((String[]) null)); + expectThrows(NullPointerException.class, () -> new CountRequest().types((String[]) null)); + } + public void testMultiSearch() throws IOException { int numberOfSearchRequests = randomIntBetween(0, 32); MultiSearchRequest multiSearchRequest = new MultiSearchRequest(); @@ -970,8 +1025,8 @@ public void testMultiSearch() throws IOException { IndicesOptions randomlyGenerated = searchRequest.indicesOptions(); IndicesOptions msearchDefault = new MultiSearchRequest().indicesOptions(); searchRequest.indicesOptions(IndicesOptions.fromOptions(randomlyGenerated.ignoreUnavailable(), - randomlyGenerated.allowNoIndices(), randomlyGenerated.expandWildcardsOpen(), randomlyGenerated.expandWildcardsClosed(), - msearchDefault.allowAliasesToMultipleIndices(), msearchDefault.forbidClosedIndices(), msearchDefault.ignoreAliases())); + randomlyGenerated.allowNoIndices(), randomlyGenerated.expandWildcardsOpen(), randomlyGenerated.expandWildcardsClosed(), + msearchDefault.allowAliasesToMultipleIndices(), msearchDefault.forbidClosedIndices(), msearchDefault.ignoreAliases())); multiSearchRequest.add(searchRequest); } @@ -996,8 +1051,8 @@ public void testMultiSearch() throws IOException { requests.add(searchRequest); }; MultiSearchRequest.readMultiLineFormat(new BytesArray(EntityUtils.toByteArray(request.getEntity())), - REQUEST_BODY_CONTENT_TYPE.xContent(), consumer, null, multiSearchRequest.indicesOptions(), null, null, null, - xContentRegistry(), true); + REQUEST_BODY_CONTENT_TYPE.xContent(), consumer, null, multiSearchRequest.indicesOptions(), null, null, null, + xContentRegistry(), true); assertEquals(requests, multiSearchRequest.requests()); } @@ -1217,8 +1272,8 @@ public void testFieldCaps() { public void testRankEval() throws Exception { RankEvalSpec spec = new RankEvalSpec( - Collections.singletonList(new RatedRequest("queryId", Collections.emptyList(), new SearchSourceBuilder())), - new PrecisionAtK()); + Collections.singletonList(new RatedRequest("queryId", Collections.emptyList(), new SearchSourceBuilder())), + new PrecisionAtK()); String[] indices = randomIndicesNames(0, 5); RankEvalRequest rankEvalRequest = new RankEvalRequest(spec, indices); Map expectedParams = new HashMap<>(); @@ -1407,8 +1462,8 @@ public void testEndpointBuilderEncodeParts() { assertEquals("/cluster1:index1,index2/_search", endpointBuilder.build()); } { - EndpointBuilder endpointBuilder = new EndpointBuilder().addCommaSeparatedPathParts(new String[] { "index1", "index2" }) - .addPathPartAsIs("cache/clear"); + EndpointBuilder endpointBuilder = new EndpointBuilder().addCommaSeparatedPathParts(new String[]{"index1", "index2"}) + .addPathPartAsIs("cache/clear"); assertEquals("/index1,index2/cache/clear", endpointBuilder.build()); } } @@ -1416,12 +1471,12 @@ public void testEndpointBuilderEncodeParts() { public void testEndpoint() { assertEquals("/index/type/id", RequestConverters.endpoint("index", "type", "id")); assertEquals("/index/type/id/_endpoint", RequestConverters.endpoint("index", "type", "id", "_endpoint")); - assertEquals("/index1,index2", RequestConverters.endpoint(new String[] { "index1", "index2" })); - assertEquals("/index1,index2/_endpoint", RequestConverters.endpoint(new String[] { "index1", "index2" }, "_endpoint")); + assertEquals("/index1,index2", RequestConverters.endpoint(new String[]{"index1", "index2"})); + assertEquals("/index1,index2/_endpoint", RequestConverters.endpoint(new String[]{"index1", "index2"}, "_endpoint")); assertEquals("/index1,index2/type1,type2/_endpoint", - RequestConverters.endpoint(new String[] { "index1", "index2" }, new String[] { "type1", "type2" }, "_endpoint")); + RequestConverters.endpoint(new String[]{"index1", "index2"}, new String[]{"type1", "type2"}, "_endpoint")); assertEquals("/index1,index2/_endpoint/suffix1,suffix2", - RequestConverters.endpoint(new String[] { "index1", "index2" }, "_endpoint", new String[] { "suffix1", "suffix2" })); + RequestConverters.endpoint(new String[]{"index1", "index2"}, "_endpoint", new String[]{"suffix1", "suffix2"})); } public void testCreateContentType() { @@ -1438,23 +1493,23 @@ public void testEnforceSameContentType() { XContentType bulkContentType = randomBoolean() ? xContentType : null; IllegalArgumentException exception = expectThrows(IllegalArgumentException.class, - () -> enforceSameContentType(new IndexRequest().source(singletonMap("field", "value"), XContentType.CBOR), - bulkContentType)); + () -> enforceSameContentType(new IndexRequest().source(singletonMap("field", "value"), XContentType.CBOR), + bulkContentType)); assertEquals("Unsupported content-type found for request with content-type [CBOR], only JSON and SMILE are supported", - exception.getMessage()); + exception.getMessage()); exception = expectThrows(IllegalArgumentException.class, - () -> enforceSameContentType(new IndexRequest().source(singletonMap("field", "value"), XContentType.YAML), - bulkContentType)); + () -> enforceSameContentType(new IndexRequest().source(singletonMap("field", "value"), XContentType.YAML), + bulkContentType)); assertEquals("Unsupported content-type found for request with content-type [YAML], only JSON and SMILE are supported", - exception.getMessage()); + exception.getMessage()); XContentType requestContentType = xContentType == XContentType.JSON ? XContentType.SMILE : XContentType.JSON; exception = expectThrows(IllegalArgumentException.class, - () -> enforceSameContentType(new IndexRequest().source(singletonMap("field", "value"), requestContentType), xContentType)); + () -> enforceSameContentType(new IndexRequest().source(singletonMap("field", "value"), requestContentType), xContentType)); assertEquals("Mismatching content-type found for request with content-type [" + requestContentType + "], " - + "previous requests have content-type [" + xContentType + "]", exception.getMessage()); + + "previous requests have content-type [" + xContentType + "]", exception.getMessage()); } /** @@ -1519,6 +1574,18 @@ private static void setRandomSearchParams(SearchRequest searchRequest, } } + private static void setRandomCountParams(CountRequest countRequest, + Map expectedParams) { + if (randomBoolean()) { + countRequest.routing(randomAlphaOfLengthBetween(3, 10)); + expectedParams.put("routing", countRequest.routing()); + } + if (randomBoolean()) { + countRequest.preference(randomAlphaOfLengthBetween(3, 10)); + expectedParams.put("preference", countRequest.preference()); + } + } + static void setRandomIndicesOptions(Consumer setter, Supplier getter, Map expectedParams) { diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/RestHighLevelClientTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/RestHighLevelClientTests.java index acdfc50b5a13a..7ae209a184807 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/RestHighLevelClientTests.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/RestHighLevelClientTests.java @@ -648,7 +648,6 @@ public void testApiNamingConventions() throws Exception { //this list should be empty once the high-level client is feature complete String[] notYetSupportedApi = new String[]{ "cluster.remote_info", - "count", "create", "exists_source", "get_source", diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/CountDocumentationIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/CountDocumentationIT.java new file mode 100644 index 0000000000000..4d5393514e85c --- /dev/null +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/CountDocumentationIT.java @@ -0,0 +1,171 @@ +/* + * 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.client.documentation; + +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.action.LatchedActionListener; +import org.elasticsearch.action.admin.indices.create.CreateIndexRequest; +import org.elasticsearch.action.admin.indices.create.CreateIndexResponse; +import org.elasticsearch.action.bulk.BulkRequest; +import org.elasticsearch.action.bulk.BulkResponse; +import org.elasticsearch.action.index.IndexRequest; +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.RequestOptions; +import org.elasticsearch.client.RestHighLevelClient; +import org.elasticsearch.client.count.CountRequest; +import org.elasticsearch.client.count.CountResponse; +import org.elasticsearch.common.xcontent.XContentType; +import org.elasticsearch.index.query.QueryBuilders; +import org.elasticsearch.rest.RestStatus; +import org.elasticsearch.search.builder.SearchSourceBuilder; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +/** + * Documentation for count API in the high level java client. + * Code wrapped in {@code tag} and {@code end} tags is included in the docs. + */ +public class CountDocumentationIT extends ESRestHighLevelClientTestCase { + + @SuppressWarnings({"unused", "unchecked"}) + public void testCount() throws Exception { + indexCountTestData(); + RestHighLevelClient client = highLevelClient(); + { + // tag::count-request-basic + CountRequest countRequest = new CountRequest(); // <1> + SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); // <2> + searchSourceBuilder.query(QueryBuilders.matchAllQuery()); // <3> + countRequest.source(searchSourceBuilder); // <4> + // end::count-request-basic + } + { + // tag::count-request-indices-types + CountRequest countRequest = new CountRequest("blog"); // <1> + countRequest.types("doc"); // <2> + // end::count-request-indices-types + // tag::count-request-routing + countRequest.routing("routing"); // <1> + // end::count-request-routing + // tag::count-request-indicesOptions + countRequest.indicesOptions(IndicesOptions.lenientExpandOpen()); // <1> + // end::count-request-indicesOptions + // tag::count-request-preference + countRequest.preference("_local"); // <1> + // end::count-request-preference + assertNotNull(client.count(countRequest, RequestOptions.DEFAULT)); + } + { + // tag::count-source-basics + SearchSourceBuilder sourceBuilder = new SearchSourceBuilder(); // <1> + sourceBuilder.query(QueryBuilders.termQuery("user", "kimchy")); // <2> + // end::count-source-basics + + // tag::count-source-setter + CountRequest countRequest = new CountRequest(); + countRequest.indices("blog", "author"); + countRequest.source(sourceBuilder); + // end::count-source-setter + + // tag::count-execute + CountResponse countResponse = client + .count(countRequest, RequestOptions.DEFAULT); + // end::count-execute + + // tag::count-execute-listener + ActionListener listener = + new ActionListener() { + + @Override + public void onResponse(CountResponse countResponse) { + // <1> + } + + @Override + public void onFailure(Exception e) { + // <2> + } + }; + // end::count-execute-listener + + // Replace the empty listener by a blocking listener in test + final CountDownLatch latch = new CountDownLatch(1); + listener = new LatchedActionListener<>(listener, latch); + + // tag::count-execute-async + client.countAsync(countRequest, RequestOptions.DEFAULT, listener); // <1> + // end::count-execute-async + + assertTrue(latch.await(30L, TimeUnit.SECONDS)); + + // tag::count-response-1 + long count = countResponse.getCount(); + RestStatus status = countResponse.status(); + Boolean terminatedEarly = countResponse.isTerminatedEarly(); + // end::count-response-1 + + // tag::count-response-2 + int totalShards = countResponse.getTotalShards(); + int skippedShards = countResponse.getSkippedShards(); + int successfulShards = countResponse.getSuccessfulShards(); + int failedShards = countResponse.getFailedShards(); + for (ShardSearchFailure failure : countResponse.getShardFailures()) { + // failures should be handled here + } + // end::count-response-2 + assertNotNull(countResponse); + assertEquals(4, countResponse.getCount()); + } + } + + private static void indexCountTestData() throws IOException { + CreateIndexRequest authorsRequest = new CreateIndexRequest("author") + .mapping("doc", "user", "type=keyword,doc_values=false"); + CreateIndexResponse authorsResponse = highLevelClient().indices().create(authorsRequest, RequestOptions.DEFAULT); + assertTrue(authorsResponse.isAcknowledged()); + + BulkRequest bulkRequest = new BulkRequest(); + bulkRequest.add(new IndexRequest("blog", "doc", "1") + .source(XContentType.JSON, "title", "Doubling Down on Open?", "user", + Collections.singletonList("kimchy"), "innerObject", Collections.singletonMap("key", "value"))); + bulkRequest.add(new IndexRequest("blog", "doc", "2") + .source(XContentType.JSON, "title", "Swiftype Joins Forces with Elastic", "user", + Arrays.asList("kimchy", "matt"), "innerObject", Collections.singletonMap("key", "value"))); + bulkRequest.add(new IndexRequest("blog", "doc", "3") + .source(XContentType.JSON, "title", "On Net Neutrality", "user", + Arrays.asList("tyler", "kimchy"), "innerObject", Collections.singletonMap("key", "value"))); + + bulkRequest.add(new IndexRequest("author", "doc", "1") + .source(XContentType.JSON, "user", "kimchy")); + + + bulkRequest.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE); + BulkResponse bulkResponse = highLevelClient().bulk(bulkRequest, RequestOptions.DEFAULT); + assertSame(RestStatus.OK, bulkResponse.status()); + assertFalse(bulkResponse.hasFailures()); + } +} diff --git a/docs/java-rest/high-level/search/count.asciidoc b/docs/java-rest/high-level/search/count.asciidoc new file mode 100644 index 0000000000000..12d79b8a06f65 --- /dev/null +++ b/docs/java-rest/high-level/search/count.asciidoc @@ -0,0 +1,145 @@ +[[java-rest-high-count]] +=== Count API + +[[java-rest-high-document-count-request]] +==== Count Request + +The `CountRequest` is used to execute a query and get the number of matches for the query. The query to use in `CountRequest` can be +set in similar way as query in `SearchRequest` using `SearchSourceBuilder`. + +In its most basic form, we can add a query to the request: + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/CountDocumentationIT.java[count-request-basic] +-------------------------------------------------- + +<1> Creates the `CountRequest`. Without arguments this runs against all indices. +<2> Most search parameters are added to the `SearchSourceBuilder`. +<3> Add a `match_all` query to the `SearchSourceBuilder`. +<4> Add the `SearchSourceBuilder` to the `CountRequest`. + +[[java-rest-high-count-request-optional]] +===== Count Request optional arguments + +Let's first look at some of the optional arguments of a `CountRequest`: + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/CountDocumentationIT.java[count-request-indices-types] +-------------------------------------------------- +<1> Restricts the request to an index +<2> Limits the request to a type + +There are a couple of other interesting optional parameters: + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/CountDocumentationIT.java[count-request-routing] +-------------------------------------------------- +<1> Set a routing parameter + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/CountDocumentationIT.java[count-request-indicesOptions] +-------------------------------------------------- +<1> Setting `IndicesOptions` controls how unavailable indices are resolved and how wildcard expressions are expanded + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/CountDocumentationIT.java[count-request-preference] +-------------------------------------------------- +<1> Use the preference parameter e.g. to execute the search to prefer local shards. The default is to randomize across shards. + +===== Using the SearchSourceBuilder in CountRequest + +Most options controlling the search behavior can be set on the `SearchSourceBuilder`, +which contains more or less the equivalent of the options in the search request body of the Rest API. + +Here are a few examples of some common options: + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/CountDocumentationIT.java[count-source-basics] +-------------------------------------------------- +<1> Create a `SearchSourceBuilder` with default options. +<2> Set the query. Can be any type of `QueryBuilder` + +After this, the `SearchSourceBuilder` only needs to be added to the +`CountRequest`: + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/CountDocumentationIT.java[count-source-setter] +-------------------------------------------------- + +Note subtle difference when using `SearchSourceBuilder` in `SearchRequest` and using `SearchSourceBuilder` in `CountRequest` - using +`SearchSourceBuilder` in `SearchRequest` one can use `SearchSourceBuilder.size()` and `SearchSourceBuilder.from()` methods to set the +number of search hits to return, and the starting index. In `CountRequest` we're interested in total number of matches and these methods +have no meaning. + +The <> page gives a list of all available search queries with +their corresponding `QueryBuilder` objects and `QueryBuilders` helper methods. + +[[java-rest-high-document-count-sync]] +==== Synchronous Execution + +When executing a `CountRequest` in the following manner, the client waits +for the `CountResponse` to be returned before continuing with code execution: + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/CountDocumentationIT.java[count-execute] +-------------------------------------------------- + +[[java-rest-high-document-count-async]] +==== Asynchronous Execution + +Executing a `CountRequest` can also be done in an asynchronous fashion so that +the client can return directly. Users need to specify how the response or +potential failures will be handled by passing the request and a listeners to the +asynchronous count method: + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/CountDocumentationIT.java[count-execute-async] +-------------------------------------------------- +<1> The `CountRequest` 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 `CountResponse` looks like: + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/CountDocumentationIT.java[count-execute-listener] +-------------------------------------------------- +<1> Called when the execution is successfully completed. +<2> Called when the whole `CountRequest` fails. + +[[java-rest-high-count-response]] +==== CountResponse + +The `CountResponse` that is returned by executing the count API call provides total count of hits and details about the count execution +itself, like the HTTP status code, or whether the request terminated early: + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/CountDocumentationIT.java[count-response-1] +-------------------------------------------------- + +Second, the response also provides information about the execution on the +shard level by offering statistics about the total number of shards that were +affected by the underlying search, and the successful vs. unsuccessful shards. Possible +failures can also be handled by iterating over an array off +`ShardSearchFailures` like in the following example: + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/CountDocumentationIT.java[count-response-2] +-------------------------------------------------- + diff --git a/docs/java-rest/high-level/supported-apis.asciidoc b/docs/java-rest/high-level/supported-apis.asciidoc index cfac4a3c2938c..80d1d7bf6d524 100644 --- a/docs/java-rest/high-level/supported-apis.asciidoc +++ b/docs/java-rest/high-level/supported-apis.asciidoc @@ -51,6 +51,7 @@ The Java High Level REST Client supports the following Search APIs: * <<{upid}-field-caps>> * <<{upid}-rank-eval>> * <<{upid}-explain>> +* <<{upid}-count>> include::search/search.asciidoc[] include::search/scroll.asciidoc[] @@ -60,6 +61,7 @@ include::search/multi-search-template.asciidoc[] include::search/field-caps.asciidoc[] include::search/rank-eval.asciidoc[] include::search/explain.asciidoc[] +include::search/count.asciidoc[] == Miscellaneous APIs From 5ac1d10d422ffe205c7c40daa1dc6e5e185a7469 Mon Sep 17 00:00:00 2001 From: Milan Mrdjen Date: Tue, 9 Oct 2018 12:09:00 +0200 Subject: [PATCH 2/3] Address changes requested during review 34267 - move CountRequest and CountResponse into org.elasticsearch.client.core - remove ToXContent in CountResponse and use XContentTester in tests - remove CountIT and CountDocumentationIT classes, add the tests into SearchIT and SearchDocumentationIT - use new markdown and structure in count.asciidoc docs - add javadocs in RestHighLevelClient and revert existing spacing in RestHighLevelClientTests --- .../client/RequestConverters.java | 2 +- .../client/RestHighLevelClient.java | 10 +- .../client/{count => core}/CountRequest.java | 2 +- .../client/{count => core}/CountResponse.java | 71 +++++--- .../org/elasticsearch/client/CountIT.java | 138 -------------- .../client/RequestConvertersTests.java | 96 +++++----- .../org/elasticsearch/client/SearchIT.java | 69 +++++++ .../client/{ => core}/CountRequestTests.java | 5 +- .../client/{ => core}/CountResponseTests.java | 108 +++++------ .../documentation/CountDocumentationIT.java | 171 ------------------ .../documentation/SearchDocumentationIT.java | 122 +++++++++++++ .../high-level/search/count.asciidoc | 91 +++------- 12 files changed, 362 insertions(+), 523 deletions(-) rename client/rest-high-level/src/main/java/org/elasticsearch/client/{count => core}/CountRequest.java (99%) rename client/rest-high-level/src/main/java/org/elasticsearch/client/{count => core}/CountResponse.java (83%) delete mode 100644 client/rest-high-level/src/test/java/org/elasticsearch/client/CountIT.java rename client/rest-high-level/src/test/java/org/elasticsearch/client/{ => core}/CountRequestTests.java (96%) rename client/rest-high-level/src/test/java/org/elasticsearch/client/{ => core}/CountResponseTests.java (57%) delete mode 100644 client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/CountDocumentationIT.java 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 afdf63a136360..ef1e9fb77f1f1 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 @@ -49,7 +49,7 @@ import org.elasticsearch.action.support.IndicesOptions; import org.elasticsearch.action.support.WriteRequest; import org.elasticsearch.action.update.UpdateRequest; -import org.elasticsearch.client.count.CountRequest; +import org.elasticsearch.client.core.CountRequest; import org.elasticsearch.client.security.RefreshPolicy; import org.elasticsearch.cluster.health.ClusterHealthStatus; import org.elasticsearch.common.Nullable; diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/RestHighLevelClient.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/RestHighLevelClient.java index 37b2291f97e7f..d8d570edd4e31 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/RestHighLevelClient.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/RestHighLevelClient.java @@ -56,8 +56,8 @@ import org.elasticsearch.action.support.master.AcknowledgedResponse; import org.elasticsearch.action.update.UpdateRequest; import org.elasticsearch.action.update.UpdateResponse; -import org.elasticsearch.client.count.CountRequest; -import org.elasticsearch.client.count.CountResponse; +import org.elasticsearch.client.core.CountRequest; +import org.elasticsearch.client.core.CountResponse; import org.elasticsearch.common.CheckedConsumer; import org.elasticsearch.common.CheckedFunction; import org.elasticsearch.common.ParseField; @@ -752,7 +752,8 @@ public final void indexAsync(IndexRequest indexRequest, RequestOptions options, } /** - * Executes a count request using the Count API + * Executes a count request using the Count API. + * See Count API on elastic.co * @param countRequest the request * @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized * @return the response @@ -764,7 +765,8 @@ public final CountResponse count(CountRequest countRequest, RequestOptions optio } /** - * Asynchronously executes a count request using the Count API + * Asynchronously executes a count request using the Count API. + * See Count API on elastic.co * @param countRequest the request * @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized * @param listener the listener to be notified upon request completion diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/count/CountRequest.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/core/CountRequest.java similarity index 99% rename from client/rest-high-level/src/main/java/org/elasticsearch/client/count/CountRequest.java rename to client/rest-high-level/src/main/java/org/elasticsearch/client/core/CountRequest.java index 5e5b1973cf8e8..6d4589c7861f6 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/count/CountRequest.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/core/CountRequest.java @@ -17,7 +17,7 @@ * under the License. */ -package org.elasticsearch.client.count; +package org.elasticsearch.client.core; import org.elasticsearch.action.ActionRequest; import org.elasticsearch.action.ActionRequestValidationException; diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/count/CountResponse.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/core/CountResponse.java similarity index 83% rename from client/rest-high-level/src/main/java/org/elasticsearch/client/count/CountResponse.java rename to client/rest-high-level/src/main/java/org/elasticsearch/client/core/CountResponse.java index fd643c8faa85e..f97f79127e690 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/count/CountResponse.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/core/CountResponse.java @@ -17,18 +17,13 @@ * under the License. */ -package org.elasticsearch.client.count; +package org.elasticsearch.client.core; import org.elasticsearch.action.ActionResponse; import org.elasticsearch.action.search.ShardSearchFailure; import org.elasticsearch.common.ParseField; -import org.elasticsearch.common.Strings; -import org.elasticsearch.common.xcontent.StatusToXContentObject; -import org.elasticsearch.common.xcontent.ToXContent; -import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.rest.RestStatus; -import org.elasticsearch.rest.action.RestActions; import java.io.IOException; import java.util.ArrayList; @@ -40,11 +35,11 @@ /** * A response to _count API request. */ -public final class CountResponse extends ActionResponse implements StatusToXContentObject { +public final class CountResponse extends ActionResponse { - private static final ParseField COUNT = new ParseField("count"); - private static final ParseField TERMINATED_EARLY = new ParseField("terminated_early"); - private static final ParseField SHARDS = new ParseField("_shards"); + static final ParseField COUNT = new ParseField("count"); + static final ParseField TERMINATED_EARLY = new ParseField("terminated_early"); + static final ParseField SHARDS = new ParseField("_shards"); private final long count; private final Boolean terminatedEarly; @@ -56,6 +51,10 @@ public CountResponse(long count, Boolean terminatedEarly, ShardStats shardStats) this.shardStats = shardStats; } + public ShardStats getShardStats() { + return shardStats; + } + /** * Number of documents matching request. */ @@ -98,16 +97,10 @@ public ShardSearchFailure[] getShardFailures() { return shardStats.shardFailures; } - @Override public RestStatus status() { return RestStatus.status(shardStats.successfulShards, shardStats.totalShards, shardStats.shardFailures); } - @Override - public String toString() { - return Strings.toString(this); - } - public static CountResponse fromXContent(XContentParser parser) throws IOException { ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.nextToken(), parser::getTokenLocation); parser.nextToken(); @@ -140,15 +133,13 @@ public static CountResponse fromXContent(XContentParser parser) throws IOExcepti } @Override - public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { - builder.startObject(); - builder.field(COUNT.getPreferredName(), count); - if (isTerminatedEarly() != null) { - builder.field(TERMINATED_EARLY.getPreferredName(), isTerminatedEarly()); - } - shardStats.toXContent(builder, params); - builder.endObject(); - return builder; + public String toString() { + String s = "{" + + "count=" + count + + (isTerminatedEarly() != null ? ", terminatedEarly=" + terminatedEarly : "") + + ", " + shardStats + + '}'; + return s; } public Boolean isTerminatedEarly() { @@ -158,7 +149,7 @@ public Boolean isTerminatedEarly() { /** * Encapsulates _shards section of count api response. */ - public static final class ShardStats implements ToXContent { + public static final class ShardStats { static final ParseField FAILED = new ParseField("failed"); static final ParseField SKIPPED = new ParseField("skipped"); @@ -178,6 +169,22 @@ public ShardStats(int successfulShards, int totalShards, int skippedShards, Shar this.shardFailures = Arrays.copyOf(shardFailures, shardFailures.length); } + public int getSuccessfulShards() { + return successfulShards; + } + + public int getTotalShards() { + return totalShards; + } + + public int getSkippedShards() { + return skippedShards; + } + + public ShardSearchFailure[] getShardFailures() { + return Arrays.copyOf(shardFailures, shardFailures.length, ShardSearchFailure[].class); + } + static ShardStats fromXContent(XContentParser parser) throws IOException { int successfulShards = -1; int totalShards = -1; @@ -216,10 +223,14 @@ static ShardStats fromXContent(XContentParser parser) throws IOException { } @Override - public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { - RestActions.buildBroadcastShardsHeader(builder, params, totalShards, successfulShards, skippedShards, - shardFailures.length, shardFailures); - return builder; + public String toString() { + return "_shards : {" + + "total=" + totalShards + + ", successful=" + successfulShards + + ", skipped=" + skippedShards + + ", failed=" + (shardFailures != null && shardFailures.length > 0 ? shardFailures.length : 0 ) + + (shardFailures != null && shardFailures.length > 0 ? ", failures: " + Arrays.asList(shardFailures): "") + + '}'; } } } diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/CountIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/CountIT.java deleted file mode 100644 index 17dfb9405e830..0000000000000 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/CountIT.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * 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.client; - -import org.apache.http.client.methods.HttpPost; -import org.apache.http.client.methods.HttpPut; -import org.elasticsearch.client.count.CountRequest; -import org.elasticsearch.client.count.CountResponse; -import org.elasticsearch.index.query.MatchQueryBuilder; -import org.elasticsearch.search.builder.SearchSourceBuilder; -import org.junit.Before; - -import java.io.IOException; - -import static org.hamcrest.Matchers.greaterThan; - -public class CountIT extends ESRestHighLevelClientTestCase { - - @Before - public void indexDocuments() throws IOException { - - Request doc1 = new Request(HttpPut.METHOD_NAME, "/index/type/1"); - doc1.setJsonEntity("{\"type\":\"type1\", \"num\":10, \"num2\":50}"); - client().performRequest(doc1); - - Request doc2 = new Request(HttpPut.METHOD_NAME, "/index/type/2"); - doc2.setJsonEntity("{\"type\":\"type1\", \"num\":20, \"num2\":40}"); - client().performRequest(doc2); - - Request doc3 = new Request(HttpPut.METHOD_NAME, "/index/type/3"); - doc3.setJsonEntity("{\"type\":\"type1\", \"num\":50, \"num2\":35}"); - client().performRequest(doc3); - - Request doc4 = new Request(HttpPut.METHOD_NAME, "/index/type/4"); - doc4.setJsonEntity("{\"type\":\"type2\", \"num\":100, \"num2\":10}"); - client().performRequest(doc4); - - Request doc5 = new Request(HttpPut.METHOD_NAME, "/index/type/5"); - doc5.setJsonEntity("{\"type\":\"type2\", \"num\":100, \"num2\":10}"); - client().performRequest(doc5); - - client().performRequest(new Request(HttpPost.METHOD_NAME, "/_refresh")); - - Request doc6 = new Request(HttpPut.METHOD_NAME, "/index1/doc/1"); - doc6.setJsonEntity("{\"field\":\"value1\", \"rating\": 7}"); - client().performRequest(doc6); - - Request doc7 = new Request(HttpPut.METHOD_NAME, "/index1/doc/2"); - doc7.setJsonEntity("{\"field\":\"value2\"}"); - client().performRequest(doc7); - - Request doc2_1 = new Request(HttpPut.METHOD_NAME, "/index2/doc/1"); - doc2_1.setJsonEntity("{\"type\":\"type1\", \"num\":10, \"num2\":50}"); - client().performRequest(doc2_1); - - client().performRequest(new Request(HttpPost.METHOD_NAME, "/index,index1,index2/_refresh")); - - } - - public void testCountOneIndexNoQuery() throws IOException { - CountRequest countRequest = new CountRequest("index"); - CountResponse countResponse = execute(countRequest, highLevelClient()::count, highLevelClient()::countAsync); - assertCountHeader(countResponse); - assertEquals(5, countResponse.getCount()); - } - - public void testCountMultipleIndicesNoQuery() throws IOException { - CountRequest countRequest = new CountRequest("index", "index1"); - CountResponse countResponse = execute(countRequest, highLevelClient()::count, highLevelClient()::countAsync); - assertCountHeader(countResponse); - assertEquals(7, countResponse.getCount()); - } - - public void testCountAllIndicesNoQuery() throws IOException { - CountRequest countRequest = new CountRequest(); - CountResponse countResponse = execute(countRequest, highLevelClient()::count, highLevelClient()::countAsync); - assertCountHeader(countResponse); - assertEquals(8, countResponse.getCount()); - } - - public void testCountOneIndexMatchQuery() throws IOException { - CountRequest countRequest = new CountRequest("index"); - countRequest.source(new SearchSourceBuilder().query(new MatchQueryBuilder("num", 10))); - CountResponse countResponse = execute(countRequest, highLevelClient()::count, highLevelClient()::countAsync); - assertCountHeader(countResponse); - assertEquals(1, countResponse.getCount()); - } - - public void testCountMultipleIndicesMatchQueryUsingConstructor() throws IOException { - SearchSourceBuilder sourceBuilder = new SearchSourceBuilder().query(new MatchQueryBuilder("num", 10)); - CountRequest countRequest = new CountRequest(new String[]{"index", "index2"}, sourceBuilder); - CountResponse countResponse = execute(countRequest, highLevelClient()::count, highLevelClient()::countAsync); - assertCountHeader(countResponse); - assertEquals(2, countResponse.getCount()); - } - - public void testCountMultipleIndicesMatchQuery() throws IOException { - CountRequest countRequest = new CountRequest("index", "index2"); - countRequest.source(new SearchSourceBuilder().query(new MatchQueryBuilder("num", 10))); - CountResponse countResponse = execute(countRequest, highLevelClient()::count, highLevelClient()::countAsync); - assertCountHeader(countResponse); - assertEquals(2, countResponse.getCount()); - } - - public void testCountAllIndicesMatchQuery() throws IOException { - CountRequest countRequest = new CountRequest(); - countRequest.source(new SearchSourceBuilder().query(new MatchQueryBuilder("num", 10))); - CountResponse countResponse = execute(countRequest, highLevelClient()::count, highLevelClient()::countAsync); - assertCountHeader(countResponse); - assertEquals(2, countResponse.getCount()); - } - - private static void assertCountHeader(CountResponse countResponse) { - assertEquals(0, countResponse.getSkippedShards()); - assertEquals(0, countResponse.getFailedShards()); - assertThat(countResponse.getTotalShards(), greaterThan(0)); - assertEquals(countResponse.getTotalShards(), countResponse.getSuccessfulShards()); - assertEquals(0, countResponse.getShardFailures().length); - } - -} 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 2ca328c6e6aa6..feb759674f7b8 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 @@ -55,7 +55,7 @@ import org.elasticsearch.action.support.replication.ReplicationRequest; import org.elasticsearch.action.update.UpdateRequest; import org.elasticsearch.client.RequestConverters.EndpointBuilder; -import org.elasticsearch.client.count.CountRequest; +import org.elasticsearch.client.core.CountRequest; import org.elasticsearch.common.CheckedBiConsumer; import org.elasticsearch.common.Strings; import org.elasticsearch.common.bytes.BytesArray; @@ -474,7 +474,7 @@ public void testRethrottle() { Float requestsPerSecond; Map expectedParams = new HashMap<>(); if (frequently()) { - requestsPerSecond = (float) randomDoubleBetween(0.0, 100.0, true); + requestsPerSecond = (float) randomDoubleBetween(0.0, 100.0, true); rethrottleRequest = new RethrottleRequest(taskId, requestsPerSecond); expectedParams.put(RethrottleRequest.REQUEST_PER_SECOND_PARAMETER, Float.toString(requestsPerSecond)); } else { @@ -485,9 +485,9 @@ public void testRethrottle() { List>> variants = new ArrayList<>(); variants.add(new Tuple>("_reindex", () -> RequestConverters.rethrottleReindex(rethrottleRequest))); variants.add(new Tuple>("_update_by_query", - () -> RequestConverters.rethrottleUpdateByQuery(rethrottleRequest))); + () -> RequestConverters.rethrottleUpdateByQuery(rethrottleRequest))); variants.add(new Tuple>("_delete_by_query", - () -> RequestConverters.rethrottleDeleteByQuery(rethrottleRequest))); + () -> RequestConverters.rethrottleDeleteByQuery(rethrottleRequest))); for (Tuple> variant : variants) { Request request = variant.v2().get(); @@ -679,7 +679,7 @@ public void testUpdateWithDifferentContentTypes() { RequestConverters.update(updateRequest); }); assertEquals("Update request cannot have different content types for doc [JSON] and upsert [YAML] documents", - exception.getMessage()); + exception.getMessage()); } public void testBulk() throws IOException { @@ -830,20 +830,20 @@ public void testBulkWithDifferentContentTypes() throws IOException { bulkRequest.add(new IndexRequest("index", "type", "1").source(singletonMap("field", "value"), XContentType.JSON)); IllegalArgumentException exception = expectThrows(IllegalArgumentException.class, () -> RequestConverters.bulk(bulkRequest)); assertEquals( - "Mismatching content-type found for request with content-type [JSON], " + "previous requests have content-type [SMILE]", - exception.getMessage()); + "Mismatching content-type found for request with content-type [JSON], " + "previous requests have content-type [SMILE]", + exception.getMessage()); } { BulkRequest bulkRequest = new BulkRequest(); bulkRequest.add(new IndexRequest("index", "type", "0").source(singletonMap("field", "value"), XContentType.JSON)); bulkRequest.add(new IndexRequest("index", "type", "1").source(singletonMap("field", "value"), XContentType.JSON)); bulkRequest.add(new UpdateRequest("index", "type", "2") - .doc(new IndexRequest().source(singletonMap("field", "value"), XContentType.JSON)) - .upsert(new IndexRequest().source(singletonMap("field", "value"), XContentType.SMILE))); + .doc(new IndexRequest().source(singletonMap("field", "value"), XContentType.JSON)) + .upsert(new IndexRequest().source(singletonMap("field", "value"), XContentType.SMILE))); IllegalArgumentException exception = expectThrows(IllegalArgumentException.class, () -> RequestConverters.bulk(bulkRequest)); assertEquals( - "Mismatching content-type found for request with content-type [SMILE], " + "previous requests have content-type [JSON]", - exception.getMessage()); + "Mismatching content-type found for request with content-type [SMILE], " + "previous requests have content-type [JSON]", + exception.getMessage()); } { XContentType xContentType = randomFrom(XContentType.CBOR, XContentType.YAML); @@ -856,7 +856,7 @@ public void testBulkWithDifferentContentTypes() throws IOException { bulkRequest.add(new IndexRequest("index", "type", "1").source(singletonMap("field", "value"), xContentType)); IllegalArgumentException exception = expectThrows(IllegalArgumentException.class, () -> RequestConverters.bulk(bulkRequest)); assertEquals("Unsupported content-type found for request with content-type [" + xContentType - + "], only JSON and SMILE are supported", exception.getMessage()); + + "], only JSON and SMILE are supported", exception.getMessage()); } } @@ -912,15 +912,15 @@ public void testSearch() throws Exception { } if (randomBoolean()) { searchSourceBuilder.aggregation(new TermsAggregationBuilder(randomAlphaOfLengthBetween(3, 10), ValueType.STRING) - .field(randomAlphaOfLengthBetween(3, 10))); + .field(randomAlphaOfLengthBetween(3, 10))); } if (randomBoolean()) { searchSourceBuilder.suggest(new SuggestBuilder().addSuggestion(randomAlphaOfLengthBetween(3, 10), - new CompletionSuggestionBuilder(randomAlphaOfLengthBetween(3, 10)))); + new CompletionSuggestionBuilder(randomAlphaOfLengthBetween(3, 10)))); } if (randomBoolean()) { searchSourceBuilder.addRescorer(new QueryRescorerBuilder( - new TermQueryBuilder(randomAlphaOfLengthBetween(3, 10), randomAlphaOfLengthBetween(3, 10)))); + new TermQueryBuilder(randomAlphaOfLengthBetween(3, 10), randomAlphaOfLengthBetween(3, 10)))); } if (randomBoolean()) { searchSourceBuilder.collapse(new CollapseBuilder(randomAlphaOfLengthBetween(3, 10))); @@ -952,7 +952,7 @@ public void testSearchNullIndicesAndTypes() { expectThrows(NullPointerException.class, () -> new SearchRequest().types((String[]) null)); } - public void testCountNotNullSource() throws IOException { + public void testCountNotNullSource() throws IOException { //as we create SearchSourceBuilder in CountRequest constructor CountRequest countRequest = new CountRequest(); Request request = RequestConverters.count(countRequest); @@ -1006,6 +1006,18 @@ public void testCountNullIndicesAndTypes() { expectThrows(NullPointerException.class, () -> new CountRequest().types((String[]) null)); } + private static void setRandomCountParams(CountRequest countRequest, + Map expectedParams) { + if (randomBoolean()) { + countRequest.routing(randomAlphaOfLengthBetween(3, 10)); + expectedParams.put("routing", countRequest.routing()); + } + if (randomBoolean()) { + countRequest.preference(randomAlphaOfLengthBetween(3, 10)); + expectedParams.put("preference", countRequest.preference()); + } + } + public void testMultiSearch() throws IOException { int numberOfSearchRequests = randomIntBetween(0, 32); MultiSearchRequest multiSearchRequest = new MultiSearchRequest(); @@ -1025,8 +1037,8 @@ public void testMultiSearch() throws IOException { IndicesOptions randomlyGenerated = searchRequest.indicesOptions(); IndicesOptions msearchDefault = new MultiSearchRequest().indicesOptions(); searchRequest.indicesOptions(IndicesOptions.fromOptions(randomlyGenerated.ignoreUnavailable(), - randomlyGenerated.allowNoIndices(), randomlyGenerated.expandWildcardsOpen(), randomlyGenerated.expandWildcardsClosed(), - msearchDefault.allowAliasesToMultipleIndices(), msearchDefault.forbidClosedIndices(), msearchDefault.ignoreAliases())); + randomlyGenerated.allowNoIndices(), randomlyGenerated.expandWildcardsOpen(), randomlyGenerated.expandWildcardsClosed(), + msearchDefault.allowAliasesToMultipleIndices(), msearchDefault.forbidClosedIndices(), msearchDefault.ignoreAliases())); multiSearchRequest.add(searchRequest); } @@ -1051,8 +1063,8 @@ public void testMultiSearch() throws IOException { requests.add(searchRequest); }; MultiSearchRequest.readMultiLineFormat(new BytesArray(EntityUtils.toByteArray(request.getEntity())), - REQUEST_BODY_CONTENT_TYPE.xContent(), consumer, null, multiSearchRequest.indicesOptions(), null, null, null, - xContentRegistry(), true); + REQUEST_BODY_CONTENT_TYPE.xContent(), consumer, null, multiSearchRequest.indicesOptions(), null, null, null, + xContentRegistry(), true); assertEquals(requests, multiSearchRequest.requests()); } @@ -1272,8 +1284,8 @@ public void testFieldCaps() { public void testRankEval() throws Exception { RankEvalSpec spec = new RankEvalSpec( - Collections.singletonList(new RatedRequest("queryId", Collections.emptyList(), new SearchSourceBuilder())), - new PrecisionAtK()); + Collections.singletonList(new RatedRequest("queryId", Collections.emptyList(), new SearchSourceBuilder())), + new PrecisionAtK()); String[] indices = randomIndicesNames(0, 5); RankEvalRequest rankEvalRequest = new RankEvalRequest(spec, indices); Map expectedParams = new HashMap<>(); @@ -1462,8 +1474,8 @@ public void testEndpointBuilderEncodeParts() { assertEquals("/cluster1:index1,index2/_search", endpointBuilder.build()); } { - EndpointBuilder endpointBuilder = new EndpointBuilder().addCommaSeparatedPathParts(new String[]{"index1", "index2"}) - .addPathPartAsIs("cache/clear"); + EndpointBuilder endpointBuilder = new EndpointBuilder().addCommaSeparatedPathParts(new String[] { "index1", "index2" }) + .addPathPartAsIs("cache/clear"); assertEquals("/index1,index2/cache/clear", endpointBuilder.build()); } } @@ -1471,12 +1483,12 @@ public void testEndpointBuilderEncodeParts() { public void testEndpoint() { assertEquals("/index/type/id", RequestConverters.endpoint("index", "type", "id")); assertEquals("/index/type/id/_endpoint", RequestConverters.endpoint("index", "type", "id", "_endpoint")); - assertEquals("/index1,index2", RequestConverters.endpoint(new String[]{"index1", "index2"})); - assertEquals("/index1,index2/_endpoint", RequestConverters.endpoint(new String[]{"index1", "index2"}, "_endpoint")); + assertEquals("/index1,index2", RequestConverters.endpoint(new String[] { "index1", "index2" })); + assertEquals("/index1,index2/_endpoint", RequestConverters.endpoint(new String[] { "index1", "index2" }, "_endpoint")); assertEquals("/index1,index2/type1,type2/_endpoint", - RequestConverters.endpoint(new String[]{"index1", "index2"}, new String[]{"type1", "type2"}, "_endpoint")); + RequestConverters.endpoint(new String[] { "index1", "index2" }, new String[] { "type1", "type2" }, "_endpoint")); assertEquals("/index1,index2/_endpoint/suffix1,suffix2", - RequestConverters.endpoint(new String[]{"index1", "index2"}, "_endpoint", new String[]{"suffix1", "suffix2"})); + RequestConverters.endpoint(new String[] { "index1", "index2" }, "_endpoint", new String[] { "suffix1", "suffix2" })); } public void testCreateContentType() { @@ -1493,23 +1505,23 @@ public void testEnforceSameContentType() { XContentType bulkContentType = randomBoolean() ? xContentType : null; IllegalArgumentException exception = expectThrows(IllegalArgumentException.class, - () -> enforceSameContentType(new IndexRequest().source(singletonMap("field", "value"), XContentType.CBOR), - bulkContentType)); + () -> enforceSameContentType(new IndexRequest().source(singletonMap("field", "value"), XContentType.CBOR), + bulkContentType)); assertEquals("Unsupported content-type found for request with content-type [CBOR], only JSON and SMILE are supported", - exception.getMessage()); + exception.getMessage()); exception = expectThrows(IllegalArgumentException.class, - () -> enforceSameContentType(new IndexRequest().source(singletonMap("field", "value"), XContentType.YAML), - bulkContentType)); + () -> enforceSameContentType(new IndexRequest().source(singletonMap("field", "value"), XContentType.YAML), + bulkContentType)); assertEquals("Unsupported content-type found for request with content-type [YAML], only JSON and SMILE are supported", - exception.getMessage()); + exception.getMessage()); XContentType requestContentType = xContentType == XContentType.JSON ? XContentType.SMILE : XContentType.JSON; exception = expectThrows(IllegalArgumentException.class, - () -> enforceSameContentType(new IndexRequest().source(singletonMap("field", "value"), requestContentType), xContentType)); + () -> enforceSameContentType(new IndexRequest().source(singletonMap("field", "value"), requestContentType), xContentType)); assertEquals("Mismatching content-type found for request with content-type [" + requestContentType + "], " - + "previous requests have content-type [" + xContentType + "]", exception.getMessage()); + + "previous requests have content-type [" + xContentType + "]", exception.getMessage()); } /** @@ -1574,18 +1586,6 @@ private static void setRandomSearchParams(SearchRequest searchRequest, } } - private static void setRandomCountParams(CountRequest countRequest, - Map expectedParams) { - if (randomBoolean()) { - countRequest.routing(randomAlphaOfLengthBetween(3, 10)); - expectedParams.put("routing", countRequest.routing()); - } - if (randomBoolean()) { - countRequest.preference(randomAlphaOfLengthBetween(3, 10)); - expectedParams.put("preference", countRequest.preference()); - } - } - static void setRandomIndicesOptions(Consumer setter, Supplier getter, Map expectedParams) { 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 063fce9bcac5e..a3b51d4f0e92f 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 @@ -35,6 +35,8 @@ import org.elasticsearch.action.search.SearchRequest; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.action.search.SearchScrollRequest; +import org.elasticsearch.client.core.CountRequest; +import org.elasticsearch.client.core.CountResponse; import org.elasticsearch.common.Strings; import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.unit.TimeValue; @@ -1233,4 +1235,71 @@ private static void assertSearchHeader(SearchResponse searchResponse) { assertEquals(0, searchResponse.getShardFailures().length); assertEquals(SearchResponse.Clusters.EMPTY, searchResponse.getClusters()); } + + //Count API IT tests start + public void testCountOneIndexNoQuery() throws IOException { + CountRequest countRequest = new CountRequest("index"); + CountResponse countResponse = execute(countRequest, highLevelClient()::count, highLevelClient()::countAsync); + assertCountHeader(countResponse); + assertEquals(5, countResponse.getCount()); + } + + public void testCountMultipleIndicesNoQuery() throws IOException { + CountRequest countRequest = new CountRequest("index", "index1"); + CountResponse countResponse = execute(countRequest, highLevelClient()::count, highLevelClient()::countAsync); + assertCountHeader(countResponse); + assertEquals(7, countResponse.getCount()); + } + + public void testCountAllIndicesNoQuery() throws IOException { + CountRequest countRequest = new CountRequest(); + CountResponse countResponse = execute(countRequest, highLevelClient()::count, highLevelClient()::countAsync); + assertCountHeader(countResponse); + assertEquals(12, countResponse.getCount()); + } + + public void testCountOneIndexMatchQuery() throws IOException { + CountRequest countRequest = new CountRequest("index"); + countRequest.source(new SearchSourceBuilder().query(new MatchQueryBuilder("num", 10))); + CountResponse countResponse = execute(countRequest, highLevelClient()::count, highLevelClient()::countAsync); + assertCountHeader(countResponse); + assertEquals(1, countResponse.getCount()); + } + + public void testCountMultipleIndicesMatchQueryUsingConstructor() throws IOException { + + SearchSourceBuilder sourceBuilder = new SearchSourceBuilder().query(new MatchQueryBuilder("field", "value1")); + CountRequest countRequest = new CountRequest(new String[]{"index1", "index2", "index3"}, sourceBuilder); + CountResponse countResponse = execute(countRequest, highLevelClient()::count, highLevelClient()::countAsync); + assertCountHeader(countResponse); + assertEquals(3, countResponse.getCount()); + + } + + public void testCountMultipleIndicesMatchQuery() throws IOException { + + CountRequest countRequest = new CountRequest("index1", "index2", "index3"); + countRequest.source(new SearchSourceBuilder().query(new MatchQueryBuilder("field", "value1"))); + CountResponse countResponse = execute(countRequest, highLevelClient()::count, highLevelClient()::countAsync); + assertCountHeader(countResponse); + assertEquals(3, countResponse.getCount()); + } + + public void testCountAllIndicesMatchQuery() throws IOException { + + CountRequest countRequest = new CountRequest(); + countRequest.source(new SearchSourceBuilder().query(new MatchQueryBuilder("field", "value1"))); + CountResponse countResponse = execute(countRequest, highLevelClient()::count, highLevelClient()::countAsync); + assertCountHeader(countResponse); + assertEquals(3, countResponse.getCount()); + } + + private static void assertCountHeader(CountResponse countResponse) { + assertEquals(0, countResponse.getSkippedShards()); + assertEquals(0, countResponse.getFailedShards()); + assertThat(countResponse.getTotalShards(), greaterThan(0)); + assertEquals(countResponse.getTotalShards(), countResponse.getSuccessfulShards()); + assertEquals(0, countResponse.getShardFailures().length); + } + //Count API IT tests end } diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/CountRequestTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/core/CountRequestTests.java similarity index 96% rename from client/rest-high-level/src/test/java/org/elasticsearch/client/CountRequestTests.java rename to client/rest-high-level/src/test/java/org/elasticsearch/client/core/CountRequestTests.java index d67a720a25cfc..1030f4401e160 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/CountRequestTests.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/core/CountRequestTests.java @@ -17,10 +17,9 @@ * under the License. */ -package org.elasticsearch.client; +package org.elasticsearch.client.core; import org.elasticsearch.action.support.IndicesOptions; -import org.elasticsearch.client.count.CountRequest; import org.elasticsearch.common.util.ArrayUtils; import org.elasticsearch.index.query.MatchQueryBuilder; import org.elasticsearch.search.builder.SearchSourceBuilder; @@ -31,7 +30,7 @@ import static org.elasticsearch.test.EqualsHashCodeTestUtils.checkEqualsAndHashCode; -//quite similar to SearchRequestTests as CountRequest wraps SearchRequest +//similar to SearchRequestTests as CountRequest inline several members (and functionality) from SearchRequest public class CountRequestTests extends ESTestCase { public void testIllegalArguments() { diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/CountResponseTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/core/CountResponseTests.java similarity index 57% rename from client/rest-high-level/src/test/java/org/elasticsearch/client/CountResponseTests.java rename to client/rest-high-level/src/test/java/org/elasticsearch/client/core/CountResponseTests.java index fb0c1855c9189..c2fc668d604e5 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/CountResponseTests.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/core/CountResponseTests.java @@ -17,26 +17,41 @@ * under the License. */ -package org.elasticsearch.client; +package org.elasticsearch.client.core; -import org.elasticsearch.action.OriginalIndices; import org.elasticsearch.action.search.ShardSearchFailure; -import org.elasticsearch.client.count.CountResponse; import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.common.ParsingException; -import org.elasticsearch.common.Strings; -import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.common.xcontent.ToXContent; +import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.index.Index; import org.elasticsearch.index.shard.ShardId; +import org.elasticsearch.rest.action.RestActions; import org.elasticsearch.search.SearchShardTarget; -import org.elasticsearch.test.AbstractXContentTestCase; +import org.elasticsearch.test.ESTestCase; import java.io.IOException; -public class CountResponseTests extends AbstractXContentTestCase { +import static org.elasticsearch.test.AbstractXContentTestCase.xContentTester; + +public class CountResponseTests extends ESTestCase { + + // Not comparing XContent for equivalence as we cannot compare the ShardSearchFailure#cause, because it will be wrapped in an outer + // ElasticSearchException. Best effort: try to check that the original message appears somewhere in the rendered xContent + // For more see ShardSearchFailureTests. + public void testFromXContent() throws IOException { + xContentTester( + this::createParser, + this::createTestInstance, + this::toXContent, + CountResponse::fromXContent) + .supportsUnknownFields(false) + .assertEqualsConsumer(this::assertEqualInstances) + .assertToXContentEquivalence(false) + .test(); + } - @Override - protected CountResponse createTestInstance() { + private CountResponse createTestInstance() { long count = 5; Boolean terminatedEarly = randomBoolean() ? null : randomBoolean(); int totalShards = randomIntBetween(1, Integer.MAX_VALUE); @@ -52,12 +67,25 @@ protected CountResponse createTestInstance() { return new CountResponse(count, terminatedEarly, shardStats); } + private void toXContent(CountResponse response, XContentBuilder builder) throws IOException { + builder.startObject(); + builder.field(CountResponse.COUNT.getPreferredName(), response.getCount()); + if (response.isTerminatedEarly() != null) { + builder.field(CountResponse.TERMINATED_EARLY.getPreferredName(), response.isTerminatedEarly()); + } + toXContent(response.getShardStats(), builder, ToXContent.EMPTY_PARAMS); + builder.endObject(); + } + + private void toXContent(CountResponse.ShardStats stats, XContentBuilder builder, ToXContent.Params params) throws IOException { + RestActions.buildBroadcastShardsHeader(builder, params, stats.getTotalShards(), stats.getSuccessfulShards(), stats + .getSkippedShards(), stats.getShardFailures().length, stats.getShardFailures()); + } - @SuppressWarnings("Duplicates") //suppress warning, as original code is in server:test:SearchResponseTests, would need to add a testJar - // (similar as x-pack), or move test code from server to test framework - public static ShardSearchFailure createShardFailureTestItem() { + @SuppressWarnings("Duplicates") + private static ShardSearchFailure createShardFailureTestItem() { String randomMessage = randomAlphaOfLengthBetween(3, 20); - Exception ex = new ParsingException(0, 0, randomMessage , new IllegalArgumentException("some bad argument")); + Exception ex = new ParsingException(0, 0, randomMessage, new IllegalArgumentException("some bad argument")); SearchShardTarget searchShardTarget = null; if (randomBoolean()) { String nodeId = randomAlphaOfLengthBetween(5, 10); @@ -68,23 +96,7 @@ public static ShardSearchFailure createShardFailureTestItem() { return new ShardSearchFailure(ex, searchShardTarget); } - @Override - protected CountResponse doParseInstance(XContentParser parser) throws IOException { - return CountResponse.fromXContent(parser); - } - - @Override - protected boolean supportsUnknownFields() { - return true; - } - - @Override - protected boolean assertToXContentEquivalence() { - return false; - } - - @Override - protected void assertEqualInstances(CountResponse expectedInstance, CountResponse newInstance) { + private void assertEqualInstances(CountResponse expectedInstance, CountResponse newInstance) { assertEquals(expectedInstance.getCount(), newInstance.getCount()); assertEquals(expectedInstance.status(), newInstance.status()); assertEquals(expectedInstance.isTerminatedEarly(), newInstance.isTerminatedEarly()); @@ -111,40 +123,4 @@ protected void assertEqualInstances(CountResponse expectedInstance, CountRespons "Elasticsearch exception [type=illegal_argument_exception, reason=" + nestedMsg + "]"); } } - - - public void testToXContent() { - CountResponse response = new CountResponse(8, null, new CountResponse.ShardStats(1, 1, 0, ShardSearchFailure.EMPTY_ARRAY)); - String expectedString = "{\"count\":8,\"_shards\":{\"total\":1,\"successful\":1,\"skipped\":0,\"failed\":0}}"; - assertEquals(expectedString, Strings.toString(response)); - } - - public void testToXContentWithTerminatedEarly() { - CountResponse response = new CountResponse(8, true, new CountResponse.ShardStats(1, 1, 0, ShardSearchFailure.EMPTY_ARRAY)); - String expectedString = "{\"count\":8,\"terminated_early\":true,\"_shards\":{\"total\":1,\"successful\":1,\"skipped\":0," + - "\"failed\":0}}"; - assertEquals(expectedString, Strings.toString(response)); - } - - public void testToXContentWithTerminatedEarlyAndShardFailures() { - ShardSearchFailure failure = new ShardSearchFailure(new ParsingException(0, 0, "not looking well", null), - new SearchShardTarget("nodeId", new ShardId(new Index("indexName", "indexUuid"), 1), null, OriginalIndices.NONE)); - CountResponse response = new CountResponse(8, true, new CountResponse.ShardStats(1, 2, 0, new ShardSearchFailure[]{failure})); - String expectedString = - "{\"count\":8," + - "\"terminated_early\":true," + - "\"_shards\":" + - "{\"total\":2," + - "\"successful\":1," + - "\"skipped\":0," + - "\"failed\":1," + - "\"failures\":" + - "[{\"shard\":1," + - "\"index\":\"indexName\"," + - "\"node\":\"nodeId\"," + - "\"reason\":{\"type\":\"parsing_exception\",\"reason\":\"not looking well\",\"line\":0,\"col\":0}}]" + - "}" + - "}"; - assertEquals(expectedString, Strings.toString(response)); - } } diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/CountDocumentationIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/CountDocumentationIT.java deleted file mode 100644 index 4d5393514e85c..0000000000000 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/CountDocumentationIT.java +++ /dev/null @@ -1,171 +0,0 @@ -/* - * 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.client.documentation; - -import org.elasticsearch.action.ActionListener; -import org.elasticsearch.action.LatchedActionListener; -import org.elasticsearch.action.admin.indices.create.CreateIndexRequest; -import org.elasticsearch.action.admin.indices.create.CreateIndexResponse; -import org.elasticsearch.action.bulk.BulkRequest; -import org.elasticsearch.action.bulk.BulkResponse; -import org.elasticsearch.action.index.IndexRequest; -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.RequestOptions; -import org.elasticsearch.client.RestHighLevelClient; -import org.elasticsearch.client.count.CountRequest; -import org.elasticsearch.client.count.CountResponse; -import org.elasticsearch.common.xcontent.XContentType; -import org.elasticsearch.index.query.QueryBuilders; -import org.elasticsearch.rest.RestStatus; -import org.elasticsearch.search.builder.SearchSourceBuilder; - -import java.io.IOException; -import java.util.Arrays; -import java.util.Collections; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -/** - * Documentation for count API in the high level java client. - * Code wrapped in {@code tag} and {@code end} tags is included in the docs. - */ -public class CountDocumentationIT extends ESRestHighLevelClientTestCase { - - @SuppressWarnings({"unused", "unchecked"}) - public void testCount() throws Exception { - indexCountTestData(); - RestHighLevelClient client = highLevelClient(); - { - // tag::count-request-basic - CountRequest countRequest = new CountRequest(); // <1> - SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); // <2> - searchSourceBuilder.query(QueryBuilders.matchAllQuery()); // <3> - countRequest.source(searchSourceBuilder); // <4> - // end::count-request-basic - } - { - // tag::count-request-indices-types - CountRequest countRequest = new CountRequest("blog"); // <1> - countRequest.types("doc"); // <2> - // end::count-request-indices-types - // tag::count-request-routing - countRequest.routing("routing"); // <1> - // end::count-request-routing - // tag::count-request-indicesOptions - countRequest.indicesOptions(IndicesOptions.lenientExpandOpen()); // <1> - // end::count-request-indicesOptions - // tag::count-request-preference - countRequest.preference("_local"); // <1> - // end::count-request-preference - assertNotNull(client.count(countRequest, RequestOptions.DEFAULT)); - } - { - // tag::count-source-basics - SearchSourceBuilder sourceBuilder = new SearchSourceBuilder(); // <1> - sourceBuilder.query(QueryBuilders.termQuery("user", "kimchy")); // <2> - // end::count-source-basics - - // tag::count-source-setter - CountRequest countRequest = new CountRequest(); - countRequest.indices("blog", "author"); - countRequest.source(sourceBuilder); - // end::count-source-setter - - // tag::count-execute - CountResponse countResponse = client - .count(countRequest, RequestOptions.DEFAULT); - // end::count-execute - - // tag::count-execute-listener - ActionListener listener = - new ActionListener() { - - @Override - public void onResponse(CountResponse countResponse) { - // <1> - } - - @Override - public void onFailure(Exception e) { - // <2> - } - }; - // end::count-execute-listener - - // Replace the empty listener by a blocking listener in test - final CountDownLatch latch = new CountDownLatch(1); - listener = new LatchedActionListener<>(listener, latch); - - // tag::count-execute-async - client.countAsync(countRequest, RequestOptions.DEFAULT, listener); // <1> - // end::count-execute-async - - assertTrue(latch.await(30L, TimeUnit.SECONDS)); - - // tag::count-response-1 - long count = countResponse.getCount(); - RestStatus status = countResponse.status(); - Boolean terminatedEarly = countResponse.isTerminatedEarly(); - // end::count-response-1 - - // tag::count-response-2 - int totalShards = countResponse.getTotalShards(); - int skippedShards = countResponse.getSkippedShards(); - int successfulShards = countResponse.getSuccessfulShards(); - int failedShards = countResponse.getFailedShards(); - for (ShardSearchFailure failure : countResponse.getShardFailures()) { - // failures should be handled here - } - // end::count-response-2 - assertNotNull(countResponse); - assertEquals(4, countResponse.getCount()); - } - } - - private static void indexCountTestData() throws IOException { - CreateIndexRequest authorsRequest = new CreateIndexRequest("author") - .mapping("doc", "user", "type=keyword,doc_values=false"); - CreateIndexResponse authorsResponse = highLevelClient().indices().create(authorsRequest, RequestOptions.DEFAULT); - assertTrue(authorsResponse.isAcknowledged()); - - BulkRequest bulkRequest = new BulkRequest(); - bulkRequest.add(new IndexRequest("blog", "doc", "1") - .source(XContentType.JSON, "title", "Doubling Down on Open?", "user", - Collections.singletonList("kimchy"), "innerObject", Collections.singletonMap("key", "value"))); - bulkRequest.add(new IndexRequest("blog", "doc", "2") - .source(XContentType.JSON, "title", "Swiftype Joins Forces with Elastic", "user", - Arrays.asList("kimchy", "matt"), "innerObject", Collections.singletonMap("key", "value"))); - bulkRequest.add(new IndexRequest("blog", "doc", "3") - .source(XContentType.JSON, "title", "On Net Neutrality", "user", - Arrays.asList("tyler", "kimchy"), "innerObject", Collections.singletonMap("key", "value"))); - - bulkRequest.add(new IndexRequest("author", "doc", "1") - .source(XContentType.JSON, "user", "kimchy")); - - - bulkRequest.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE); - BulkResponse bulkResponse = highLevelClient().bulk(bulkRequest, RequestOptions.DEFAULT); - assertSame(RestStatus.OK, bulkResponse.status()); - assertFalse(bulkResponse.hasFailures()); - } -} diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/SearchDocumentationIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/SearchDocumentationIT.java index 4382924bb97e9..1a9a3dd25bef9 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/SearchDocumentationIT.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/SearchDocumentationIT.java @@ -49,6 +49,8 @@ import org.elasticsearch.client.Response; import org.elasticsearch.client.RestClient; import org.elasticsearch.client.RestHighLevelClient; +import org.elasticsearch.client.core.CountRequest; +import org.elasticsearch.client.core.CountResponse; import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.document.DocumentField; import org.elasticsearch.common.text.Text; @@ -1288,4 +1290,124 @@ private void indexSearchTestData() throws IOException { assertSame(RestStatus.OK, bulkResponse.status()); assertFalse(bulkResponse.hasFailures()); } + + + @SuppressWarnings({"unused", "unchecked"}) + public void testCount() throws Exception { + indexCountTestData(); + RestHighLevelClient client = highLevelClient(); + { + // tag::count-request-basic + CountRequest countRequest = new CountRequest(); // <1> + SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); // <2> + searchSourceBuilder.query(QueryBuilders.matchAllQuery()); // <3> + countRequest.source(searchSourceBuilder); // <4> + // end::count-request-basic + } + { + // tag::count-request-indices-types + CountRequest countRequest = new CountRequest("blog"); // <1> + countRequest.types("doc"); // <2> + // end::count-request-indices-types + // tag::count-request-routing + countRequest.routing("routing"); // <1> + // end::count-request-routing + // tag::count-request-indicesOptions + countRequest.indicesOptions(IndicesOptions.lenientExpandOpen()); // <1> + // end::count-request-indicesOptions + // tag::count-request-preference + countRequest.preference("_local"); // <1> + // end::count-request-preference + assertNotNull(client.count(countRequest, RequestOptions.DEFAULT)); + } + { + // tag::count-source-basics + SearchSourceBuilder sourceBuilder = new SearchSourceBuilder(); // <1> + sourceBuilder.query(QueryBuilders.termQuery("user", "kimchy")); // <2> + // end::count-source-basics + + // tag::count-source-setter + CountRequest countRequest = new CountRequest(); + countRequest.indices("blog", "author"); + countRequest.source(sourceBuilder); + // end::count-source-setter + + // tag::count-execute + CountResponse countResponse = client + .count(countRequest, RequestOptions.DEFAULT); + // end::count-execute + + // tag::count-execute-listener + ActionListener listener = + new ActionListener() { + + @Override + public void onResponse(CountResponse countResponse) { + // <1> + } + + @Override + public void onFailure(Exception e) { + // <2> + } + }; + // end::count-execute-listener + + // Replace the empty listener by a blocking listener in test + final CountDownLatch latch = new CountDownLatch(1); + listener = new LatchedActionListener<>(listener, latch); + + // tag::count-execute-async + client.countAsync(countRequest, RequestOptions.DEFAULT, listener); // <1> + // end::count-execute-async + + assertTrue(latch.await(30L, TimeUnit.SECONDS)); + + // tag::count-response-1 + long count = countResponse.getCount(); + RestStatus status = countResponse.status(); + Boolean terminatedEarly = countResponse.isTerminatedEarly(); + // end::count-response-1 + + // tag::count-response-2 + int totalShards = countResponse.getTotalShards(); + int skippedShards = countResponse.getSkippedShards(); + int successfulShards = countResponse.getSuccessfulShards(); + int failedShards = countResponse.getFailedShards(); + for (ShardSearchFailure failure : countResponse.getShardFailures()) { + // failures should be handled here + } + // end::count-response-2 + assertNotNull(countResponse); + assertEquals(4, countResponse.getCount()); + } + } + + private static void indexCountTestData() throws IOException { + CreateIndexRequest authorsRequest = new CreateIndexRequest("author") + .mapping("doc", "user", "type=keyword,doc_values=false"); + CreateIndexResponse authorsResponse = highLevelClient().indices().create(authorsRequest, RequestOptions.DEFAULT); + assertTrue(authorsResponse.isAcknowledged()); + + BulkRequest bulkRequest = new BulkRequest(); + bulkRequest.add(new IndexRequest("blog", "doc", "1") + .source(XContentType.JSON, "title", "Doubling Down on Open?", "user", + Collections.singletonList("kimchy"), "innerObject", Collections.singletonMap("key", "value"))); + bulkRequest.add(new IndexRequest("blog", "doc", "2") + .source(XContentType.JSON, "title", "Swiftype Joins Forces with Elastic", "user", + Arrays.asList("kimchy", "matt"), "innerObject", Collections.singletonMap("key", "value"))); + bulkRequest.add(new IndexRequest("blog", "doc", "3") + .source(XContentType.JSON, "title", "On Net Neutrality", "user", + Arrays.asList("tyler", "kimchy"), "innerObject", Collections.singletonMap("key", "value"))); + + bulkRequest.add(new IndexRequest("author", "doc", "1") + .source(XContentType.JSON, "user", "kimchy")); + + + bulkRequest.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE); + BulkResponse bulkResponse = highLevelClient().bulk(bulkRequest, RequestOptions.DEFAULT); + assertSame(RestStatus.OK, bulkResponse.status()); + assertFalse(bulkResponse.hasFailures()); + } + } diff --git a/docs/java-rest/high-level/search/count.asciidoc b/docs/java-rest/high-level/search/count.asciidoc index 12d79b8a06f65..f70e1e1fd4d22 100644 --- a/docs/java-rest/high-level/search/count.asciidoc +++ b/docs/java-rest/high-level/search/count.asciidoc @@ -1,32 +1,39 @@ -[[java-rest-high-count]] +-- +:api: count +:request: CountRequest +:response: CountResponse +-- +[id="{upid}-{api}"] + === Count API -[[java-rest-high-document-count-request]] +[id="{upid}-{api}-request"] + ==== Count Request -The `CountRequest` is used to execute a query and get the number of matches for the query. The query to use in `CountRequest` can be +The +{request}+ is used to execute a query and get the number of matches for the query. The query to use in +{request}+ can be set in similar way as query in `SearchRequest` using `SearchSourceBuilder`. In its most basic form, we can add a query to the request: ["source","java",subs="attributes,callouts,macros"] -------------------------------------------------- -include-tagged::{doc-tests}/CountDocumentationIT.java[count-request-basic] +include-tagged::{doc-tests-file}[{api}-request-basic] -------------------------------------------------- -<1> Creates the `CountRequest`. Without arguments this runs against all indices. +<1> Creates the +{request}+. Without arguments this runs against all indices. <2> Most search parameters are added to the `SearchSourceBuilder`. <3> Add a `match_all` query to the `SearchSourceBuilder`. -<4> Add the `SearchSourceBuilder` to the `CountRequest`. +<4> Add the `SearchSourceBuilder` to the +{request}+. [[java-rest-high-count-request-optional]] ===== Count Request optional arguments -Let's first look at some of the optional arguments of a `CountRequest`: +Let's first look at some of the optional arguments of a +{request}+: ["source","java",subs="attributes,callouts,macros"] -------------------------------------------------- -include-tagged::{doc-tests}/CountDocumentationIT.java[count-request-indices-types] +include-tagged::{doc-tests-file}[{api}-request-indices-types] -------------------------------------------------- <1> Restricts the request to an index <2> Limits the request to a type @@ -35,104 +42,66 @@ There are a couple of other interesting optional parameters: ["source","java",subs="attributes,callouts,macros"] -------------------------------------------------- -include-tagged::{doc-tests}/CountDocumentationIT.java[count-request-routing] +include-tagged::{doc-tests-file}[{api}-request-routing] -------------------------------------------------- <1> Set a routing parameter ["source","java",subs="attributes,callouts,macros"] -------------------------------------------------- -include-tagged::{doc-tests}/CountDocumentationIT.java[count-request-indicesOptions] +include-tagged::{doc-tests-file}[{api}-request-indicesOptions] -------------------------------------------------- <1> Setting `IndicesOptions` controls how unavailable indices are resolved and how wildcard expressions are expanded ["source","java",subs="attributes,callouts,macros"] -------------------------------------------------- -include-tagged::{doc-tests}/CountDocumentationIT.java[count-request-preference] +include-tagged::{doc-tests-file}[{api}-request-preference] -------------------------------------------------- <1> Use the preference parameter e.g. to execute the search to prefer local shards. The default is to randomize across shards. ===== Using the SearchSourceBuilder in CountRequest -Most options controlling the search behavior can be set on the `SearchSourceBuilder`, +Both in search and count API calls, most options controlling the search behavior can be set on the `SearchSourceBuilder`, which contains more or less the equivalent of the options in the search request body of the Rest API. Here are a few examples of some common options: ["source","java",subs="attributes,callouts,macros"] -------------------------------------------------- -include-tagged::{doc-tests}/CountDocumentationIT.java[count-source-basics] +include-tagged::{doc-tests-file}[{api}-source-basics] -------------------------------------------------- <1> Create a `SearchSourceBuilder` with default options. <2> Set the query. Can be any type of `QueryBuilder` After this, the `SearchSourceBuilder` only needs to be added to the -`CountRequest`: ++{request}+: ["source","java",subs="attributes,callouts,macros"] -------------------------------------------------- -include-tagged::{doc-tests}/CountDocumentationIT.java[count-source-setter] +include-tagged::{doc-tests-file}[{api}-source-setter] -------------------------------------------------- -Note subtle difference when using `SearchSourceBuilder` in `SearchRequest` and using `SearchSourceBuilder` in `CountRequest` - using +Note subtle difference when using `SearchSourceBuilder` in `SearchRequest` and using `SearchSourceBuilder` in +{request}+ - using `SearchSourceBuilder` in `SearchRequest` one can use `SearchSourceBuilder.size()` and `SearchSourceBuilder.from()` methods to set the -number of search hits to return, and the starting index. In `CountRequest` we're interested in total number of matches and these methods +number of search hits to return, and the starting index. In +{request}+ we're interested in total number of matches and these methods have no meaning. The <> page gives a list of all available search queries with their corresponding `QueryBuilder` objects and `QueryBuilders` helper methods. -[[java-rest-high-document-count-sync]] -==== Synchronous Execution - -When executing a `CountRequest` in the following manner, the client waits -for the `CountResponse` to be returned before continuing with code execution: - -["source","java",subs="attributes,callouts,macros"] --------------------------------------------------- -include-tagged::{doc-tests}/CountDocumentationIT.java[count-execute] --------------------------------------------------- - -[[java-rest-high-document-count-async]] -==== Asynchronous Execution - -Executing a `CountRequest` can also be done in an asynchronous fashion so that -the client can return directly. Users need to specify how the response or -potential failures will be handled by passing the request and a listeners to the -asynchronous count method: - -["source","java",subs="attributes,callouts,macros"] --------------------------------------------------- -include-tagged::{doc-tests}/CountDocumentationIT.java[count-execute-async] --------------------------------------------------- -<1> The `CountRequest` 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 `CountResponse` looks like: - -["source","java",subs="attributes,callouts,macros"] --------------------------------------------------- -include-tagged::{doc-tests}/CountDocumentationIT.java[count-execute-listener] --------------------------------------------------- -<1> Called when the execution is successfully completed. -<2> Called when the whole `CountRequest` fails. +include::../execution.asciidoc[] -[[java-rest-high-count-response]] +[id="{upid}-{api}-response"] ==== CountResponse -The `CountResponse` that is returned by executing the count API call provides total count of hits and details about the count execution +The +{response}+ that is returned by executing the count API call provides total count of hits and details about the count execution itself, like the HTTP status code, or whether the request terminated early: ["source","java",subs="attributes,callouts,macros"] -------------------------------------------------- -include-tagged::{doc-tests}/CountDocumentationIT.java[count-response-1] +include-tagged::{doc-tests-file}[{api}-response-1] -------------------------------------------------- -Second, the response also provides information about the execution on the +The response also provides information about the execution on the shard level by offering statistics about the total number of shards that were affected by the underlying search, and the successful vs. unsuccessful shards. Possible failures can also be handled by iterating over an array off @@ -140,6 +109,6 @@ failures can also be handled by iterating over an array off ["source","java",subs="attributes,callouts,macros"] -------------------------------------------------- -include-tagged::{doc-tests}/CountDocumentationIT.java[count-response-2] +include-tagged::{doc-tests-file}[{api}-response-2] -------------------------------------------------- From 1935caf7515f02fc5b1f91e13488a19ad035b03c Mon Sep 17 00:00:00 2001 From: Milan Mrdjen Date: Wed, 17 Oct 2018 21:08:20 +0200 Subject: [PATCH 3/3] Address changes requested during review 34267 - remove comments in SearchIT --- .../src/test/java/org/elasticsearch/client/SearchIT.java | 2 -- 1 file changed, 2 deletions(-) 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 a3b51d4f0e92f..d895737906568 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 @@ -1236,7 +1236,6 @@ private static void assertSearchHeader(SearchResponse searchResponse) { assertEquals(SearchResponse.Clusters.EMPTY, searchResponse.getClusters()); } - //Count API IT tests start public void testCountOneIndexNoQuery() throws IOException { CountRequest countRequest = new CountRequest("index"); CountResponse countResponse = execute(countRequest, highLevelClient()::count, highLevelClient()::countAsync); @@ -1301,5 +1300,4 @@ private static void assertCountHeader(CountResponse countResponse) { assertEquals(countResponse.getTotalShards(), countResponse.getSuccessfulShards()); assertEquals(0, countResponse.getShardFailures().length); } - //Count API IT tests end }