HLRC: Add document _count API (#34267)
Add `count()` api method, `CountRequest` and `CountResponse` classes to HLRC. Code in server module is unchanged. Relates to #27205
This commit is contained in:
parent
12072b200e
commit
34677b9c83
|
@ -49,6 +49,7 @@ import org.elasticsearch.action.support.ActiveShardCount;
|
||||||
import org.elasticsearch.action.support.IndicesOptions;
|
import org.elasticsearch.action.support.IndicesOptions;
|
||||||
import org.elasticsearch.action.support.WriteRequest;
|
import org.elasticsearch.action.support.WriteRequest;
|
||||||
import org.elasticsearch.action.update.UpdateRequest;
|
import org.elasticsearch.action.update.UpdateRequest;
|
||||||
|
import org.elasticsearch.client.core.CountRequest;
|
||||||
import org.elasticsearch.client.security.RefreshPolicy;
|
import org.elasticsearch.client.security.RefreshPolicy;
|
||||||
import org.elasticsearch.cluster.health.ClusterHealthStatus;
|
import org.elasticsearch.cluster.health.ClusterHealthStatus;
|
||||||
import org.elasticsearch.common.Nullable;
|
import org.elasticsearch.common.Nullable;
|
||||||
|
@ -442,6 +443,16 @@ final class RequestConverters {
|
||||||
return request;
|
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 {
|
static Request explain(ExplainRequest explainRequest) throws IOException {
|
||||||
Request request = new Request(HttpGet.METHOD_NAME,
|
Request request = new Request(HttpGet.METHOD_NAME,
|
||||||
endpoint(explainRequest.index(), explainRequest.type(), explainRequest.id(), "_explain"));
|
endpoint(explainRequest.index(), explainRequest.type(), explainRequest.id(), "_explain"));
|
||||||
|
|
|
@ -56,6 +56,8 @@ import org.elasticsearch.action.search.SearchScrollRequest;
|
||||||
import org.elasticsearch.action.support.master.AcknowledgedResponse;
|
import org.elasticsearch.action.support.master.AcknowledgedResponse;
|
||||||
import org.elasticsearch.action.update.UpdateRequest;
|
import org.elasticsearch.action.update.UpdateRequest;
|
||||||
import org.elasticsearch.action.update.UpdateResponse;
|
import org.elasticsearch.action.update.UpdateResponse;
|
||||||
|
import org.elasticsearch.client.core.CountRequest;
|
||||||
|
import org.elasticsearch.client.core.CountResponse;
|
||||||
import org.elasticsearch.client.core.TermVectorsResponse;
|
import org.elasticsearch.client.core.TermVectorsResponse;
|
||||||
import org.elasticsearch.client.core.TermVectorsRequest;
|
import org.elasticsearch.client.core.TermVectorsRequest;
|
||||||
import org.elasticsearch.common.CheckedConsumer;
|
import org.elasticsearch.common.CheckedConsumer;
|
||||||
|
@ -791,6 +793,31 @@ public class RestHighLevelClient implements Closeable {
|
||||||
emptySet());
|
emptySet());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes a count request using the Count API.
|
||||||
|
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-count.html">Count API on elastic.co</a>
|
||||||
|
* @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.
|
||||||
|
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-count.html">Count API on elastic.co</a>
|
||||||
|
* @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<CountResponse> listener) {
|
||||||
|
performRequestAsyncAndParseEntity(countRequest, RequestConverters::count, options,CountResponse::fromXContent,
|
||||||
|
listener, emptySet());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates a document using the Update API.
|
* Updates a document using the Update API.
|
||||||
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-update.html">Update API on elastic.co</a>
|
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-update.html">Update API on elastic.co</a>
|
||||||
|
|
|
@ -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.core;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,236 @@
|
||||||
|
/*
|
||||||
|
* 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.core;
|
||||||
|
|
||||||
|
import org.elasticsearch.action.ActionResponse;
|
||||||
|
import org.elasticsearch.action.search.ShardSearchFailure;
|
||||||
|
import org.elasticsearch.common.ParseField;
|
||||||
|
import org.elasticsearch.common.xcontent.XContentParser;
|
||||||
|
import org.elasticsearch.rest.RestStatus;
|
||||||
|
|
||||||
|
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 {
|
||||||
|
|
||||||
|
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;
|
||||||
|
private final ShardStats shardStats;
|
||||||
|
|
||||||
|
public CountResponse(long count, Boolean terminatedEarly, ShardStats shardStats) {
|
||||||
|
this.count = count;
|
||||||
|
this.terminatedEarly = terminatedEarly;
|
||||||
|
this.shardStats = shardStats;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ShardStats getShardStats() {
|
||||||
|
return 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RestStatus status() {
|
||||||
|
return RestStatus.status(shardStats.successfulShards, shardStats.totalShards, shardStats.shardFailures);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 String toString() {
|
||||||
|
String s = "{" +
|
||||||
|
"count=" + count +
|
||||||
|
(isTerminatedEarly() != null ? ", terminatedEarly=" + terminatedEarly : "") +
|
||||||
|
", " + shardStats +
|
||||||
|
'}';
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean isTerminatedEarly() {
|
||||||
|
return terminatedEarly;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encapsulates _shards section of count api response.
|
||||||
|
*/
|
||||||
|
public static final class ShardStats {
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
int skippedShards = 0; //BWC @see org.elasticsearch.action.search.SearchResponse
|
||||||
|
List<ShardSearchFailure> 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 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): "") +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -56,6 +56,7 @@ import org.elasticsearch.action.support.replication.ReplicationRequest;
|
||||||
import org.elasticsearch.client.core.TermVectorsRequest;
|
import org.elasticsearch.client.core.TermVectorsRequest;
|
||||||
import org.elasticsearch.action.update.UpdateRequest;
|
import org.elasticsearch.action.update.UpdateRequest;
|
||||||
import org.elasticsearch.client.RequestConverters.EndpointBuilder;
|
import org.elasticsearch.client.RequestConverters.EndpointBuilder;
|
||||||
|
import org.elasticsearch.client.core.CountRequest;
|
||||||
import org.elasticsearch.common.CheckedBiConsumer;
|
import org.elasticsearch.common.CheckedBiConsumer;
|
||||||
import org.elasticsearch.common.Strings;
|
import org.elasticsearch.common.Strings;
|
||||||
import org.elasticsearch.common.bytes.BytesArray;
|
import org.elasticsearch.common.bytes.BytesArray;
|
||||||
|
@ -968,6 +969,72 @@ public class RequestConvertersTests extends ESTestCase {
|
||||||
expectThrows(NullPointerException.class, () -> new SearchRequest().types((String[]) null));
|
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<String, String> 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));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void setRandomCountParams(CountRequest countRequest,
|
||||||
|
Map<String, String> 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 {
|
public void testMultiSearch() throws IOException {
|
||||||
int numberOfSearchRequests = randomIntBetween(0, 32);
|
int numberOfSearchRequests = randomIntBetween(0, 32);
|
||||||
MultiSearchRequest multiSearchRequest = new MultiSearchRequest();
|
MultiSearchRequest multiSearchRequest = new MultiSearchRequest();
|
||||||
|
|
|
@ -662,7 +662,6 @@ public class RestHighLevelClientTests extends ESTestCase {
|
||||||
//this list should be empty once the high-level client is feature complete
|
//this list should be empty once the high-level client is feature complete
|
||||||
String[] notYetSupportedApi = new String[]{
|
String[] notYetSupportedApi = new String[]{
|
||||||
"cluster.remote_info",
|
"cluster.remote_info",
|
||||||
"count",
|
|
||||||
"create",
|
"create",
|
||||||
"get_source",
|
"get_source",
|
||||||
"indices.delete_alias",
|
"indices.delete_alias",
|
||||||
|
|
|
@ -35,6 +35,8 @@ import org.elasticsearch.action.search.MultiSearchResponse;
|
||||||
import org.elasticsearch.action.search.SearchRequest;
|
import org.elasticsearch.action.search.SearchRequest;
|
||||||
import org.elasticsearch.action.search.SearchResponse;
|
import org.elasticsearch.action.search.SearchResponse;
|
||||||
import org.elasticsearch.action.search.SearchScrollRequest;
|
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.Strings;
|
||||||
import org.elasticsearch.common.bytes.BytesReference;
|
import org.elasticsearch.common.bytes.BytesReference;
|
||||||
import org.elasticsearch.common.unit.TimeValue;
|
import org.elasticsearch.common.unit.TimeValue;
|
||||||
|
@ -1233,4 +1235,69 @@ public class SearchIT extends ESRestHighLevelClientTestCase {
|
||||||
assertEquals(0, searchResponse.getShardFailures().length);
|
assertEquals(0, searchResponse.getShardFailures().length);
|
||||||
assertEquals(SearchResponse.Clusters.EMPTY, searchResponse.getClusters());
|
assertEquals(SearchResponse.Clusters.EMPTY, searchResponse.getClusters());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,95 @@
|
||||||
|
/*
|
||||||
|
* 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.core;
|
||||||
|
|
||||||
|
import org.elasticsearch.action.support.IndicesOptions;
|
||||||
|
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;
|
||||||
|
|
||||||
|
//similar to SearchRequestTests as CountRequest inline several members (and functionality) from 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<Runnable> 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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,126 @@
|
||||||
|
/*
|
||||||
|
* 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.core;
|
||||||
|
|
||||||
|
import org.elasticsearch.action.search.ShardSearchFailure;
|
||||||
|
import org.elasticsearch.cluster.metadata.IndexMetaData;
|
||||||
|
import org.elasticsearch.common.ParsingException;
|
||||||
|
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.ESTestCase;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
private 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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")
|
||||||
|
private 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
private 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 + "]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -49,6 +49,8 @@ import org.elasticsearch.client.RequestOptions;
|
||||||
import org.elasticsearch.client.Response;
|
import org.elasticsearch.client.Response;
|
||||||
import org.elasticsearch.client.RestClient;
|
import org.elasticsearch.client.RestClient;
|
||||||
import org.elasticsearch.client.RestHighLevelClient;
|
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.bytes.BytesReference;
|
||||||
import org.elasticsearch.common.document.DocumentField;
|
import org.elasticsearch.common.document.DocumentField;
|
||||||
import org.elasticsearch.common.text.Text;
|
import org.elasticsearch.common.text.Text;
|
||||||
|
@ -1287,4 +1289,124 @@ public class SearchDocumentationIT extends ESRestHighLevelClientTestCase {
|
||||||
assertSame(RestStatus.OK, bulkResponse.status());
|
assertSame(RestStatus.OK, bulkResponse.status());
|
||||||
assertFalse(bulkResponse.hasFailures());
|
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<CountResponse> listener =
|
||||||
|
new ActionListener<CountResponse>() {
|
||||||
|
|
||||||
|
@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());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,114 @@
|
||||||
|
--
|
||||||
|
:api: count
|
||||||
|
:request: CountRequest
|
||||||
|
:response: CountResponse
|
||||||
|
--
|
||||||
|
[id="{upid}-{api}"]
|
||||||
|
|
||||||
|
=== Count API
|
||||||
|
|
||||||
|
[id="{upid}-{api}-request"]
|
||||||
|
|
||||||
|
==== Count Request
|
||||||
|
|
||||||
|
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-file}[{api}-request-basic]
|
||||||
|
--------------------------------------------------
|
||||||
|
|
||||||
|
<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 +{request}+.
|
||||||
|
|
||||||
|
[[java-rest-high-count-request-optional]]
|
||||||
|
===== Count Request optional arguments
|
||||||
|
|
||||||
|
Let's first look at some of the optional arguments of a +{request}+:
|
||||||
|
|
||||||
|
["source","java",subs="attributes,callouts,macros"]
|
||||||
|
--------------------------------------------------
|
||||||
|
include-tagged::{doc-tests-file}[{api}-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-file}[{api}-request-routing]
|
||||||
|
--------------------------------------------------
|
||||||
|
<1> Set a routing parameter
|
||||||
|
|
||||||
|
["source","java",subs="attributes,callouts,macros"]
|
||||||
|
--------------------------------------------------
|
||||||
|
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-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
|
||||||
|
|
||||||
|
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-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
|
||||||
|
+{request}+:
|
||||||
|
|
||||||
|
["source","java",subs="attributes,callouts,macros"]
|
||||||
|
--------------------------------------------------
|
||||||
|
include-tagged::{doc-tests-file}[{api}-source-setter]
|
||||||
|
--------------------------------------------------
|
||||||
|
|
||||||
|
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 +{request}+ we're interested in total number of matches and these methods
|
||||||
|
have no meaning.
|
||||||
|
|
||||||
|
The <<java-rest-high-query-builders, Building Queries>> page gives a list of all available search queries with
|
||||||
|
their corresponding `QueryBuilder` objects and `QueryBuilders` helper methods.
|
||||||
|
|
||||||
|
include::../execution.asciidoc[]
|
||||||
|
|
||||||
|
[id="{upid}-{api}-response"]
|
||||||
|
==== CountResponse
|
||||||
|
|
||||||
|
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-file}[{api}-response-1]
|
||||||
|
--------------------------------------------------
|
||||||
|
|
||||||
|
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-file}[{api}-response-2]
|
||||||
|
--------------------------------------------------
|
||||||
|
|
|
@ -54,6 +54,7 @@ The Java High Level REST Client supports the following Search APIs:
|
||||||
* <<{upid}-field-caps>>
|
* <<{upid}-field-caps>>
|
||||||
* <<{upid}-rank-eval>>
|
* <<{upid}-rank-eval>>
|
||||||
* <<{upid}-explain>>
|
* <<{upid}-explain>>
|
||||||
|
* <<{upid}-count>>
|
||||||
|
|
||||||
include::search/search.asciidoc[]
|
include::search/search.asciidoc[]
|
||||||
include::search/scroll.asciidoc[]
|
include::search/scroll.asciidoc[]
|
||||||
|
@ -63,6 +64,7 @@ include::search/multi-search-template.asciidoc[]
|
||||||
include::search/field-caps.asciidoc[]
|
include::search/field-caps.asciidoc[]
|
||||||
include::search/rank-eval.asciidoc[]
|
include::search/rank-eval.asciidoc[]
|
||||||
include::search/explain.asciidoc[]
|
include::search/explain.asciidoc[]
|
||||||
|
include::search/count.asciidoc[]
|
||||||
|
|
||||||
== Miscellaneous APIs
|
== Miscellaneous APIs
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue