REST high-level client: add validate query API (#31077)
Adds the validate query API to the high level rest client.
This commit is contained in:
parent
47095357bc
commit
c4f8df3ad6
|
@ -57,6 +57,8 @@ import org.elasticsearch.action.admin.indices.template.get.GetIndexTemplatesRequ
|
|||
import org.elasticsearch.action.admin.indices.template.get.GetIndexTemplatesResponse;
|
||||
import org.elasticsearch.action.admin.indices.template.put.PutIndexTemplateRequest;
|
||||
import org.elasticsearch.action.admin.indices.template.put.PutIndexTemplateResponse;
|
||||
import org.elasticsearch.action.admin.indices.validate.query.ValidateQueryRequest;
|
||||
import org.elasticsearch.action.admin.indices.validate.query.ValidateQueryResponse;
|
||||
import org.elasticsearch.rest.RestStatus;
|
||||
|
||||
import java.io.IOException;
|
||||
|
@ -661,6 +663,36 @@ public final class IndicesClient {
|
|||
PutIndexTemplateResponse::fromXContent, listener, emptySet());
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a potentially expensive query without executing it.
|
||||
* <p>
|
||||
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-validate.html"> Validate Query API
|
||||
* on elastic.co</a>
|
||||
* @param validateQueryRequest 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 ValidateQueryResponse validateQuery(ValidateQueryRequest validateQueryRequest, RequestOptions options) throws IOException {
|
||||
return restHighLevelClient.performRequestAndParseEntity(validateQueryRequest, RequestConverters::validateQuery, options,
|
||||
ValidateQueryResponse::fromXContent, emptySet());
|
||||
}
|
||||
|
||||
/**
|
||||
* Asynchronously validate a potentially expensive query without executing it.
|
||||
* <p>
|
||||
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-validate.html"> Validate Query API
|
||||
* on elastic.co</a>
|
||||
* @param validateQueryRequest 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 void validateQueryAsync(ValidateQueryRequest validateQueryRequest, RequestOptions options,
|
||||
ActionListener<ValidateQueryResponse> listener) {
|
||||
restHighLevelClient.performRequestAsyncAndParseEntity(validateQueryRequest, RequestConverters::validateQuery, options,
|
||||
ValidateQueryResponse::fromXContent, listener, emptySet());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets index templates using the Index Templates API
|
||||
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-templates.html"> Index Templates API
|
||||
|
|
|
@ -58,6 +58,7 @@ import org.elasticsearch.action.admin.indices.shrink.ResizeRequest;
|
|||
import org.elasticsearch.action.admin.indices.shrink.ResizeType;
|
||||
import org.elasticsearch.action.admin.indices.template.get.GetIndexTemplatesRequest;
|
||||
import org.elasticsearch.action.admin.indices.template.put.PutIndexTemplateRequest;
|
||||
import org.elasticsearch.action.admin.indices.validate.query.ValidateQueryRequest;
|
||||
import org.elasticsearch.action.bulk.BulkRequest;
|
||||
import org.elasticsearch.action.delete.DeleteRequest;
|
||||
import org.elasticsearch.action.fieldcaps.FieldCapabilitiesRequest;
|
||||
|
@ -856,6 +857,20 @@ final class RequestConverters {
|
|||
return request;
|
||||
}
|
||||
|
||||
static Request validateQuery(ValidateQueryRequest validateQueryRequest) throws IOException {
|
||||
String[] indices = validateQueryRequest.indices() == null ? Strings.EMPTY_ARRAY : validateQueryRequest.indices();
|
||||
String[] types = validateQueryRequest.types() == null || indices.length <= 0 ? Strings.EMPTY_ARRAY : validateQueryRequest.types();
|
||||
String endpoint = endpoint(indices, types, "_validate/query");
|
||||
Request request = new Request(HttpGet.METHOD_NAME, endpoint);
|
||||
Params params = new Params(request);
|
||||
params.withIndicesOptions(validateQueryRequest.indicesOptions());
|
||||
params.putParam("explain", Boolean.toString(validateQueryRequest.explain()));
|
||||
params.putParam("all_shards", Boolean.toString(validateQueryRequest.allShards()));
|
||||
params.putParam("rewrite", Boolean.toString(validateQueryRequest.rewrite()));
|
||||
request.setEntity(createEntity(validateQueryRequest, REQUEST_BODY_CONTENT_TYPE));
|
||||
return request;
|
||||
}
|
||||
|
||||
static Request getAlias(GetAliasesRequest getAliasesRequest) {
|
||||
String[] indices = getAliasesRequest.indices() == null ? Strings.EMPTY_ARRAY : getAliasesRequest.indices();
|
||||
String[] aliases = getAliasesRequest.aliases() == null ? Strings.EMPTY_ARRAY : getAliasesRequest.aliases();
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
package org.elasticsearch.client;
|
||||
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.client.methods.HttpPut;
|
||||
import org.elasticsearch.ElasticsearchException;
|
||||
import org.elasticsearch.ElasticsearchStatusException;
|
||||
|
@ -63,6 +64,8 @@ import org.elasticsearch.action.admin.indices.template.get.GetIndexTemplatesRequ
|
|||
import org.elasticsearch.action.admin.indices.template.get.GetIndexTemplatesResponse;
|
||||
import org.elasticsearch.action.admin.indices.template.put.PutIndexTemplateRequest;
|
||||
import org.elasticsearch.action.admin.indices.template.put.PutIndexTemplateResponse;
|
||||
import org.elasticsearch.action.admin.indices.validate.query.ValidateQueryRequest;
|
||||
import org.elasticsearch.action.admin.indices.validate.query.ValidateQueryResponse;
|
||||
import org.elasticsearch.action.index.IndexRequest;
|
||||
import org.elasticsearch.action.support.IndicesOptions;
|
||||
import org.elasticsearch.action.support.WriteRequest;
|
||||
|
@ -80,6 +83,8 @@ import org.elasticsearch.common.xcontent.XContentBuilder;
|
|||
import org.elasticsearch.common.xcontent.json.JsonXContent;
|
||||
import org.elasticsearch.common.xcontent.support.XContentMapValues;
|
||||
import org.elasticsearch.index.IndexSettings;
|
||||
import org.elasticsearch.index.query.QueryBuilder;
|
||||
import org.elasticsearch.index.query.QueryBuilders;
|
||||
import org.elasticsearch.rest.RestStatus;
|
||||
|
||||
import java.io.IOException;
|
||||
|
@ -1155,6 +1160,40 @@ public class IndicesClientIT extends ESRestHighLevelClientTestCase {
|
|||
assertThat(unknownSettingError.getDetailedMessage(), containsString("unknown setting [index.this-setting-does-not-exist]"));
|
||||
}
|
||||
|
||||
public void testValidateQuery() throws IOException{
|
||||
String index = "some_index";
|
||||
createIndex(index, Settings.EMPTY);
|
||||
QueryBuilder builder = QueryBuilders
|
||||
.boolQuery()
|
||||
.must(QueryBuilders.queryStringQuery("*:*"))
|
||||
.filter(QueryBuilders.termQuery("user", "kimchy"));
|
||||
ValidateQueryRequest request = new ValidateQueryRequest(index).query(builder);
|
||||
request.explain(randomBoolean());
|
||||
ValidateQueryResponse response = execute(request, highLevelClient().indices()::validateQuery,
|
||||
highLevelClient().indices()::validateQueryAsync);
|
||||
assertTrue(response.isValid());
|
||||
}
|
||||
|
||||
public void testInvalidValidateQuery() throws IOException{
|
||||
String index = "shakespeare";
|
||||
|
||||
createIndex(index, Settings.EMPTY);
|
||||
Request postDoc = new Request(HttpPost.METHOD_NAME, "/" + index + "/1");
|
||||
postDoc.setJsonEntity(
|
||||
"{\"type\":\"act\",\"line_id\":1,\"play_name\":\"Henry IV\", \"speech_number\":\"\"," +
|
||||
"\"line_number\":\"\",\"speaker\":\"\",\"text_entry\":\"ACT I\"}");
|
||||
assertOK(client().performRequest(postDoc));
|
||||
|
||||
QueryBuilder builder = QueryBuilders
|
||||
.queryStringQuery("line_id:foo")
|
||||
.lenient(false);
|
||||
ValidateQueryRequest request = new ValidateQueryRequest(index).query(builder);
|
||||
request.explain(true);
|
||||
ValidateQueryResponse response = execute(request, highLevelClient().indices()::validateQuery,
|
||||
highLevelClient().indices()::validateQueryAsync);
|
||||
assertFalse(response.isValid());
|
||||
}
|
||||
|
||||
public void testGetIndexTemplate() throws Exception {
|
||||
RestHighLevelClient client = highLevelClient();
|
||||
|
||||
|
|
|
@ -60,6 +60,7 @@ import org.elasticsearch.action.admin.indices.shrink.ResizeRequest;
|
|||
import org.elasticsearch.action.admin.indices.shrink.ResizeType;
|
||||
import org.elasticsearch.action.admin.indices.template.get.GetIndexTemplatesRequest;
|
||||
import org.elasticsearch.action.admin.indices.template.put.PutIndexTemplateRequest;
|
||||
import org.elasticsearch.action.admin.indices.validate.query.ValidateQueryRequest;
|
||||
import org.elasticsearch.action.bulk.BulkRequest;
|
||||
import org.elasticsearch.action.bulk.BulkShardRequest;
|
||||
import org.elasticsearch.action.delete.DeleteRequest;
|
||||
|
@ -1895,6 +1896,40 @@ public class RequestConvertersTests extends ESTestCase {
|
|||
assertToXContentBody(putTemplateRequest, request.getEntity());
|
||||
}
|
||||
|
||||
public void testValidateQuery() throws Exception {
|
||||
String[] indices = randomBoolean() ? null : randomIndicesNames(0, 5);
|
||||
String[] types = randomBoolean() ? generateRandomStringArray(5, 5, false, false) : null;
|
||||
ValidateQueryRequest validateQueryRequest;
|
||||
if (randomBoolean()) {
|
||||
validateQueryRequest = new ValidateQueryRequest(indices);
|
||||
} else {
|
||||
validateQueryRequest = new ValidateQueryRequest();
|
||||
validateQueryRequest.indices(indices);
|
||||
}
|
||||
validateQueryRequest.types(types);
|
||||
Map<String, String> expectedParams = new HashMap<>();
|
||||
setRandomIndicesOptions(validateQueryRequest::indicesOptions, validateQueryRequest::indicesOptions, expectedParams);
|
||||
validateQueryRequest.explain(randomBoolean());
|
||||
validateQueryRequest.rewrite(randomBoolean());
|
||||
validateQueryRequest.allShards(randomBoolean());
|
||||
expectedParams.put("explain", Boolean.toString(validateQueryRequest.explain()));
|
||||
expectedParams.put("rewrite", Boolean.toString(validateQueryRequest.rewrite()));
|
||||
expectedParams.put("all_shards", Boolean.toString(validateQueryRequest.allShards()));
|
||||
Request request = RequestConverters.validateQuery(validateQueryRequest);
|
||||
StringJoiner endpoint = new StringJoiner("/", "/", "");
|
||||
if (indices != null && indices.length > 0) {
|
||||
endpoint.add(String.join(",", indices));
|
||||
if (types != null && types.length > 0) {
|
||||
endpoint.add(String.join(",", types));
|
||||
}
|
||||
}
|
||||
endpoint.add("_validate/query");
|
||||
assertThat(request.getEndpoint(), equalTo(endpoint.toString()));
|
||||
assertThat(request.getParameters(), equalTo(expectedParams));
|
||||
assertToXContentBody(validateQueryRequest, request.getEntity());
|
||||
assertThat(request.getMethod(), equalTo(HttpGet.METHOD_NAME));
|
||||
}
|
||||
|
||||
public void testGetTemplateRequest() throws Exception {
|
||||
Map<String, String> encodes = new HashMap<>();
|
||||
encodes.put("log", "log");
|
||||
|
|
|
@ -62,6 +62,9 @@ import org.elasticsearch.action.admin.indices.template.get.GetIndexTemplatesRequ
|
|||
import org.elasticsearch.action.admin.indices.template.get.GetIndexTemplatesResponse;
|
||||
import org.elasticsearch.action.admin.indices.template.put.PutIndexTemplateRequest;
|
||||
import org.elasticsearch.action.admin.indices.template.put.PutIndexTemplateResponse;
|
||||
import org.elasticsearch.action.admin.indices.validate.query.QueryExplanation;
|
||||
import org.elasticsearch.action.admin.indices.validate.query.ValidateQueryRequest;
|
||||
import org.elasticsearch.action.admin.indices.validate.query.ValidateQueryResponse;
|
||||
import org.elasticsearch.action.support.ActiveShardCount;
|
||||
import org.elasticsearch.action.support.DefaultShardOperationFailedException;
|
||||
import org.elasticsearch.action.support.IndicesOptions;
|
||||
|
@ -81,6 +84,7 @@ import org.elasticsearch.common.unit.TimeValue;
|
|||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.common.xcontent.XContentFactory;
|
||||
import org.elasticsearch.common.xcontent.XContentType;
|
||||
import org.elasticsearch.index.query.QueryBuilder;
|
||||
import org.elasticsearch.index.query.QueryBuilders;
|
||||
import org.elasticsearch.rest.RestStatus;
|
||||
|
||||
|
@ -2128,4 +2132,83 @@ public class IndicesClientDocumentationIT extends ESRestHighLevelClientTestCase
|
|||
|
||||
assertTrue(latch.await(30L, TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
public void testValidateQuery() throws IOException, InterruptedException {
|
||||
RestHighLevelClient client = highLevelClient();
|
||||
|
||||
String index = "some_index";
|
||||
createIndex(index, Settings.EMPTY);
|
||||
|
||||
// tag::validate-query-request
|
||||
ValidateQueryRequest request = new ValidateQueryRequest(index); // <1>
|
||||
// end::validate-query-request
|
||||
|
||||
// tag::validate-query-request-query
|
||||
QueryBuilder builder = QueryBuilders
|
||||
.boolQuery() // <1>
|
||||
.must(QueryBuilders.queryStringQuery("*:*"))
|
||||
.filter(QueryBuilders.termQuery("user", "kimchy"));
|
||||
request.query(builder); // <2>
|
||||
// end::validate-query-request-query
|
||||
|
||||
// tag::validate-query-request-explain
|
||||
request.explain(true); // <1>
|
||||
// end::validate-query-request-explain
|
||||
|
||||
// tag::validate-query-request-allShards
|
||||
request.allShards(true); // <1>
|
||||
// end::validate-query-request-allShards
|
||||
|
||||
// tag::validate-query-request-rewrite
|
||||
request.rewrite(true); // <1>
|
||||
// end::validate-query-request-rewrite
|
||||
|
||||
// tag::validate-query-execute
|
||||
ValidateQueryResponse response = client.indices().validateQuery(request, RequestOptions.DEFAULT); // <1>
|
||||
// end::validate-query-execute
|
||||
|
||||
// tag::validate-query-response
|
||||
boolean isValid = response.isValid(); // <1>
|
||||
int totalShards = response.getTotalShards(); // <2>
|
||||
int successfulShards = response.getSuccessfulShards(); // <3>
|
||||
int failedShards = response.getFailedShards(); // <4>
|
||||
if (failedShards > 0) {
|
||||
for(DefaultShardOperationFailedException failure: response.getShardFailures()) { // <5>
|
||||
String failedIndex = failure.index(); // <6>
|
||||
int shardId = failure.shardId(); // <7>
|
||||
String reason = failure.reason(); // <8>
|
||||
}
|
||||
}
|
||||
for(QueryExplanation explanation: response.getQueryExplanation()) { // <9>
|
||||
String explanationIndex = explanation.getIndex(); // <10>
|
||||
int shardId = explanation.getShard(); // <11>
|
||||
String explanationString = explanation.getExplanation(); // <12>
|
||||
}
|
||||
// end::validate-query-response
|
||||
|
||||
// tag::validate-query-execute-listener
|
||||
ActionListener<ValidateQueryResponse> listener =
|
||||
new ActionListener<ValidateQueryResponse>() {
|
||||
@Override
|
||||
public void onResponse(ValidateQueryResponse validateQueryResponse) {
|
||||
// <1>
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Exception e) {
|
||||
// <2>
|
||||
}
|
||||
};
|
||||
// end::validate-query-execute-listener
|
||||
|
||||
// Replace the empty listener by a blocking listener in test
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
listener = new LatchedActionListener<>(listener, latch);
|
||||
|
||||
// tag::validate-query-execute-async
|
||||
client.indices().validateQueryAsync(request, RequestOptions.DEFAULT, listener); // <1>
|
||||
// end::validate-query-execute-async
|
||||
|
||||
assertTrue(latch.await(30L, TimeUnit.SECONDS));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,113 @@
|
|||
[[java-rest-high-indices-validate-query]]
|
||||
=== Validate Query API
|
||||
|
||||
[[java-rest-high-indices-validate-query-request]]
|
||||
==== Validate Query Request
|
||||
|
||||
A `ValidateQueryRequest` requires one or more `indices` on which the query is validated. If no index
|
||||
is provided the request is executed on all indices.
|
||||
|
||||
["source","java",subs="attributes,callouts,macros"]
|
||||
--------------------------------------------------
|
||||
include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[validate-query-request]
|
||||
--------------------------------------------------
|
||||
<1> The index on which to run the request.
|
||||
|
||||
In addition it also needs the query that needs to be validated. The query can be built using the `QueryBuilders` utility class.
|
||||
The following code snippet builds a sample boolean query.
|
||||
|
||||
["source","java",subs="attributes,callouts,macros"]
|
||||
--------------------------------------------------
|
||||
include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[validate-query-request-query]
|
||||
--------------------------------------------------
|
||||
<1> Build the desired query.
|
||||
<2> Set it to the request.
|
||||
|
||||
==== Optional arguments
|
||||
The following arguments can optionally be provided:
|
||||
|
||||
["source","java",subs="attributes,callouts,macros"]
|
||||
--------------------------------------------------
|
||||
include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[validate-query-request-explain]
|
||||
--------------------------------------------------
|
||||
<1> The explain parameter can be set to true to get more detailed information about why a query failed
|
||||
|
||||
By default, the request is executed on a single shard only, which is randomly selected. The detailed explanation of
|
||||
the query may depend on which shard is being hit, and therefore may vary from one request to another. So, in case of
|
||||
query rewrite the `allShards` parameter should be used to get response from all available shards.
|
||||
|
||||
["source","java",subs="attributes,callouts,macros"]
|
||||
--------------------------------------------------
|
||||
include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[validate-query-request-allShards]
|
||||
--------------------------------------------------
|
||||
<1> Set the allShards parameter.
|
||||
|
||||
When the query is valid, the explanation defaults to the string representation of that query. With rewrite set to true,
|
||||
the explanation is more detailed showing the actual Lucene query that will be executed
|
||||
|
||||
["source","java",subs="attributes,callouts,macros"]
|
||||
--------------------------------------------------
|
||||
include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[validate-query-request-rewrite]
|
||||
--------------------------------------------------
|
||||
<1> Set the rewrite parameter.
|
||||
|
||||
[[java-rest-high-indices-validate-query-sync]]
|
||||
==== Synchronous Execution
|
||||
|
||||
["source","java",subs="attributes,callouts,macros"]
|
||||
--------------------------------------------------
|
||||
include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[validate-query-execute]
|
||||
--------------------------------------------------
|
||||
<1> Execute the request and get back the response in a ValidateQueryResponse object.
|
||||
|
||||
[[java-rest-high-indices-validate-query-async]]
|
||||
==== Asynchronous Execution
|
||||
|
||||
The asynchronous execution of a validate query request requires both the `ValidateQueryRequest`
|
||||
instance and an `ActionListener` instance to be passed to the asynchronous
|
||||
method:
|
||||
|
||||
["source","java",subs="attributes,callouts,macros"]
|
||||
--------------------------------------------------
|
||||
include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[validate-query-execute-async]
|
||||
--------------------------------------------------
|
||||
<1> The `ValidateQueryRequest` 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 `ValidateQueryResponse` looks like:
|
||||
|
||||
["source","java",subs="attributes,callouts,macros"]
|
||||
--------------------------------------------------
|
||||
include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[validate-query-execute-listener]
|
||||
--------------------------------------------------
|
||||
<1> Called when the execution is successfully completed. The response is
|
||||
provided as an argument
|
||||
<2> Called in case of failure. The raised exception is provided as an argument
|
||||
|
||||
[[java-rest-high-indices-validate-query-response]]
|
||||
==== Validate Query Response
|
||||
|
||||
The returned `ValidateQueryResponse` allows to retrieve information about the executed
|
||||
operation as follows:
|
||||
|
||||
["source","java",subs="attributes,callouts,macros"]
|
||||
--------------------------------------------------
|
||||
include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[validate-query-response]
|
||||
--------------------------------------------------
|
||||
<1> Check if the query is valid or not.
|
||||
<2> Get total number of shards.
|
||||
<3> Get number of shards that were successful.
|
||||
<4> Get number of shards that failed.
|
||||
<5> Get the shard failures as `DefaultShardOperationFailedException`.
|
||||
<6> Get the index of a failed shard.
|
||||
<7> Get the shard id of a failed shard.
|
||||
<8> Get the reason for shard failure.
|
||||
<9> Get the detailed explanation for the shards (if explain was set to `true`).
|
||||
<10> Get the index to which a particular explanation belongs.
|
||||
<11> Get the shard id to which a particular explanation belongs.
|
||||
<12> Get the actual explanation string.
|
|
@ -73,6 +73,7 @@ Index Management::
|
|||
* <<java-rest-high-rollover-index>>
|
||||
* <<java-rest-high-indices-put-settings>>
|
||||
* <<java-rest-high-get-settings>>
|
||||
* <<java-rest-high-indices-validate-query>>
|
||||
|
||||
Mapping Management::
|
||||
* <<java-rest-high-put-mapping>>
|
||||
|
@ -103,6 +104,7 @@ include::indices/get_alias.asciidoc[]
|
|||
include::indices/put_settings.asciidoc[]
|
||||
include::indices/get_settings.asciidoc[]
|
||||
include::indices/put_template.asciidoc[]
|
||||
include::indices/validate_query.asciidoc[]
|
||||
include::indices/get_templates.asciidoc[]
|
||||
|
||||
== Cluster APIs
|
||||
|
|
|
@ -20,16 +20,57 @@
|
|||
package org.elasticsearch.action.admin.indices.validate.query;
|
||||
|
||||
import org.elasticsearch.Version;
|
||||
import org.elasticsearch.common.ParseField;
|
||||
import org.elasticsearch.common.io.stream.StreamInput;
|
||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||
import org.elasticsearch.common.io.stream.Streamable;
|
||||
import org.elasticsearch.common.xcontent.ConstructingObjectParser;
|
||||
import org.elasticsearch.common.xcontent.ToXContentFragment;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
|
||||
public class QueryExplanation implements Streamable {
|
||||
import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg;
|
||||
import static org.elasticsearch.common.xcontent.ConstructingObjectParser.optionalConstructorArg;
|
||||
|
||||
public class QueryExplanation implements Streamable, ToXContentFragment {
|
||||
|
||||
public static final String INDEX_FIELD = "index";
|
||||
public static final String SHARD_FIELD = "shard";
|
||||
public static final String VALID_FIELD = "valid";
|
||||
public static final String ERROR_FIELD = "error";
|
||||
public static final String EXPLANATION_FIELD = "explanation";
|
||||
|
||||
public static final int RANDOM_SHARD = -1;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
static ConstructingObjectParser<QueryExplanation, Void> PARSER = new ConstructingObjectParser<>(
|
||||
"query_explanation",
|
||||
true,
|
||||
a -> {
|
||||
int shard = RANDOM_SHARD;
|
||||
if (a[1] != null) {
|
||||
shard = (int)a[1];
|
||||
}
|
||||
return new QueryExplanation(
|
||||
(String)a[0],
|
||||
shard,
|
||||
(boolean)a[2],
|
||||
(String)a[3],
|
||||
(String)a[4]
|
||||
);
|
||||
}
|
||||
);
|
||||
static {
|
||||
PARSER.declareString(optionalConstructorArg(), new ParseField(INDEX_FIELD));
|
||||
PARSER.declareInt(optionalConstructorArg(), new ParseField(SHARD_FIELD));
|
||||
PARSER.declareBoolean(constructorArg(), new ParseField(VALID_FIELD));
|
||||
PARSER.declareString(optionalConstructorArg(), new ParseField(EXPLANATION_FIELD));
|
||||
PARSER.declareString(optionalConstructorArg(), new ParseField(ERROR_FIELD));
|
||||
}
|
||||
|
||||
private String index;
|
||||
|
||||
private int shard = RANDOM_SHARD;
|
||||
|
@ -110,4 +151,43 @@ public class QueryExplanation implements Streamable {
|
|||
exp.readFrom(in);
|
||||
return exp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
||||
if (getIndex() != null) {
|
||||
builder.field(INDEX_FIELD, getIndex());
|
||||
}
|
||||
if(getShard() >= 0) {
|
||||
builder.field(SHARD_FIELD, getShard());
|
||||
}
|
||||
builder.field(VALID_FIELD, isValid());
|
||||
if (getError() != null) {
|
||||
builder.field(ERROR_FIELD, getError());
|
||||
}
|
||||
if (getExplanation() != null) {
|
||||
builder.field(EXPLANATION_FIELD, getExplanation());
|
||||
}
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static QueryExplanation fromXContent(XContentParser parser) {
|
||||
return PARSER.apply(parser, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
QueryExplanation other = (QueryExplanation) o;
|
||||
return Objects.equals(getIndex(), other.getIndex()) &&
|
||||
Objects.equals(getShard(), other.getShard()) &&
|
||||
Objects.equals(isValid(), other.isValid()) &&
|
||||
Objects.equals(getError(), other.getError()) &&
|
||||
Objects.equals(getExplanation(), other.getExplanation());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(getIndex(), getShard(), isValid(), getError(), getExplanation());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,6 +27,8 @@ import org.elasticsearch.action.support.broadcast.BroadcastRequest;
|
|||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.common.io.stream.StreamInput;
|
||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||
import org.elasticsearch.common.xcontent.ToXContentObject;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.index.query.MatchAllQueryBuilder;
|
||||
import org.elasticsearch.index.query.QueryBuilder;
|
||||
|
||||
|
@ -38,7 +40,7 @@ import java.util.Arrays;
|
|||
* <p>
|
||||
* The request requires the query to be set using {@link #query(QueryBuilder)}
|
||||
*/
|
||||
public class ValidateQueryRequest extends BroadcastRequest<ValidateQueryRequest> {
|
||||
public class ValidateQueryRequest extends BroadcastRequest<ValidateQueryRequest> implements ToXContentObject {
|
||||
|
||||
private QueryBuilder query = new MatchAllQueryBuilder();
|
||||
|
||||
|
@ -179,4 +181,12 @@ public class ValidateQueryRequest extends BroadcastRequest<ValidateQueryRequest>
|
|||
return "[" + Arrays.toString(indices) + "]" + Arrays.toString(types) + ", query[" + query + "], explain:" + explain +
|
||||
", rewrite:" + rewrite + ", all_shards:" + allShards;
|
||||
}
|
||||
|
||||
@Override
|
||||
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
||||
builder.startObject();
|
||||
builder.field("query");
|
||||
query.toXContent(builder, params);
|
||||
return builder.endObject();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,16 +21,22 @@ package org.elasticsearch.action.admin.indices.validate.query;
|
|||
|
||||
import org.elasticsearch.action.support.DefaultShardOperationFailedException;
|
||||
import org.elasticsearch.action.support.broadcast.BroadcastResponse;
|
||||
import org.elasticsearch.common.ParseField;
|
||||
import org.elasticsearch.common.io.stream.StreamInput;
|
||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||
import org.elasticsearch.common.xcontent.ConstructingObjectParser;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static org.elasticsearch.action.admin.indices.validate.query.QueryExplanation.readQueryExplanation;
|
||||
import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg;
|
||||
import static org.elasticsearch.common.xcontent.ConstructingObjectParser.optionalConstructorArg;
|
||||
|
||||
/**
|
||||
* The response of the validate action.
|
||||
|
@ -39,12 +45,33 @@ import static org.elasticsearch.action.admin.indices.validate.query.QueryExplana
|
|||
*/
|
||||
public class ValidateQueryResponse extends BroadcastResponse {
|
||||
|
||||
public static final String INDEX_FIELD = "index";
|
||||
public static final String SHARD_FIELD = "shard";
|
||||
public static final String VALID_FIELD = "valid";
|
||||
public static final String EXPLANATIONS_FIELD = "explanations";
|
||||
public static final String ERROR_FIELD = "error";
|
||||
public static final String EXPLANATION_FIELD = "explanation";
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
static ConstructingObjectParser<ValidateQueryResponse, Void> PARSER = new ConstructingObjectParser<>(
|
||||
"validate_query",
|
||||
true,
|
||||
arg -> {
|
||||
BroadcastResponse response = (BroadcastResponse) arg[0];
|
||||
return
|
||||
new ValidateQueryResponse(
|
||||
(boolean)arg[1],
|
||||
(List<QueryExplanation>)arg[2],
|
||||
response.getTotalShards(),
|
||||
response.getSuccessfulShards(),
|
||||
response.getFailedShards(),
|
||||
Arrays.asList(response.getShardFailures())
|
||||
);
|
||||
}
|
||||
);
|
||||
static {
|
||||
declareBroadcastFields(PARSER);
|
||||
PARSER.declareBoolean(constructorArg(), new ParseField(VALID_FIELD));
|
||||
PARSER.declareObjectArray(
|
||||
optionalConstructorArg(), QueryExplanation.PARSER, new ParseField(EXPLANATIONS_FIELD)
|
||||
);
|
||||
}
|
||||
|
||||
private boolean valid;
|
||||
|
||||
|
@ -112,22 +139,14 @@ public class ValidateQueryResponse extends BroadcastResponse {
|
|||
builder.startArray(EXPLANATIONS_FIELD);
|
||||
for (QueryExplanation explanation : getQueryExplanation()) {
|
||||
builder.startObject();
|
||||
if (explanation.getIndex() != null) {
|
||||
builder.field(INDEX_FIELD, explanation.getIndex());
|
||||
}
|
||||
if(explanation.getShard() >= 0) {
|
||||
builder.field(SHARD_FIELD, explanation.getShard());
|
||||
}
|
||||
builder.field(VALID_FIELD, explanation.isValid());
|
||||
if (explanation.getError() != null) {
|
||||
builder.field(ERROR_FIELD, explanation.getError());
|
||||
}
|
||||
if (explanation.getExplanation() != null) {
|
||||
builder.field(EXPLANATION_FIELD, explanation.getExplanation());
|
||||
}
|
||||
explanation.toXContent(builder, params);
|
||||
builder.endObject();
|
||||
}
|
||||
builder.endArray();
|
||||
}
|
||||
}
|
||||
|
||||
public static ValidateQueryResponse fromXContent(XContentParser parser) {
|
||||
return PARSER.apply(parser, null);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
package org.elasticsearch.rest.action.admin.indices;
|
||||
|
||||
import org.elasticsearch.action.admin.indices.validate.query.QueryExplanation;
|
||||
import org.elasticsearch.action.admin.indices.validate.query.ValidateQueryRequest;
|
||||
import org.elasticsearch.action.admin.indices.validate.query.ValidateQueryResponse;
|
||||
import org.elasticsearch.action.support.IndicesOptions;
|
||||
|
@ -101,7 +102,7 @@ public class RestValidateQueryAction extends BaseRestHandler {
|
|||
builder.startObject();
|
||||
builder.field(ValidateQueryResponse.VALID_FIELD, false);
|
||||
if (explain) {
|
||||
builder.field(ValidateQueryResponse.ERROR_FIELD, error);
|
||||
builder.field(QueryExplanation.ERROR_FIELD, error);
|
||||
}
|
||||
builder.endObject();
|
||||
return new BytesRestResponse(OK, builder);
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* 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.action.admin.indices.validate.query;
|
||||
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.test.AbstractStreamableXContentTestCase;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class QueryExplanationTests extends AbstractStreamableXContentTestCase<QueryExplanation> {
|
||||
|
||||
static QueryExplanation createRandomQueryExplanation(boolean isValid) {
|
||||
String index = "index_" + randomInt(1000);
|
||||
int shard = randomInt(100);
|
||||
Boolean valid = isValid;
|
||||
String errorField = null;
|
||||
if (!valid) {
|
||||
errorField = randomAlphaOfLength(randomIntBetween(10, 100));
|
||||
}
|
||||
String explanation = randomAlphaOfLength(randomIntBetween(10, 100));
|
||||
return new QueryExplanation(index, shard, valid, explanation, errorField);
|
||||
}
|
||||
|
||||
static QueryExplanation createRandomQueryExplanation() {
|
||||
return createRandomQueryExplanation(randomBoolean());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected QueryExplanation doParseInstance(XContentParser parser) throws IOException {
|
||||
return QueryExplanation.fromXContent(parser);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected QueryExplanation createBlankInstance() {
|
||||
return new QueryExplanation();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected QueryExplanation createTestInstance() {
|
||||
return createRandomQueryExplanation();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,110 @@
|
|||
/*
|
||||
* 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.action.admin.indices.validate.query;
|
||||
|
||||
import org.elasticsearch.ElasticsearchException;
|
||||
import org.elasticsearch.action.support.DefaultShardOperationFailedException;
|
||||
import org.elasticsearch.action.support.broadcast.AbstractBroadcastResponseTestCase;
|
||||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class ValidateQueryResponseTests extends AbstractBroadcastResponseTestCase<ValidateQueryResponse> {
|
||||
|
||||
private static ValidateQueryResponse createRandomValidateQueryResponse(
|
||||
int totalShards, int successfulShards, int failedShards, List<DefaultShardOperationFailedException> failures) {
|
||||
boolean valid = failedShards == 0;
|
||||
List<QueryExplanation> queryExplanations = new ArrayList<>(totalShards);
|
||||
for(DefaultShardOperationFailedException failure: failures) {
|
||||
queryExplanations.add(
|
||||
new QueryExplanation(
|
||||
failure.index(), failure.shardId(), false, failure.reason(), null
|
||||
)
|
||||
);
|
||||
}
|
||||
return new ValidateQueryResponse(
|
||||
valid, queryExplanations, totalShards, successfulShards, failedShards, failures
|
||||
);
|
||||
}
|
||||
|
||||
private static ValidateQueryResponse createRandomValidateQueryResponse() {
|
||||
int totalShards = randomIntBetween(1, 10);
|
||||
int successfulShards = randomIntBetween(0, totalShards);
|
||||
int failedShards = totalShards - successfulShards;
|
||||
boolean valid = failedShards == 0;
|
||||
List<QueryExplanation> queryExplanations = new ArrayList<>(totalShards);
|
||||
List<DefaultShardOperationFailedException> shardFailures = new ArrayList<>(failedShards);
|
||||
for (int i=0; i<successfulShards; i++) {
|
||||
QueryExplanation queryExplanation = QueryExplanationTests.createRandomQueryExplanation(true);
|
||||
queryExplanations.add(queryExplanation);
|
||||
}
|
||||
for (int i=0; i<failedShards; i++) {
|
||||
QueryExplanation queryExplanation = QueryExplanationTests.createRandomQueryExplanation(false);
|
||||
ElasticsearchException exc = new ElasticsearchException("some_error_" + randomInt());
|
||||
shardFailures.add(
|
||||
new DefaultShardOperationFailedException(
|
||||
queryExplanation.getIndex(), queryExplanation.getShard(),
|
||||
exc
|
||||
)
|
||||
);
|
||||
queryExplanations.add(queryExplanation);
|
||||
}
|
||||
Collections.shuffle(queryExplanations, random());
|
||||
return new ValidateQueryResponse(valid, queryExplanations, totalShards, successfulShards, failedShards, shardFailures);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ValidateQueryResponse doParseInstance(XContentParser parser) throws IOException {
|
||||
return ValidateQueryResponse.fromXContent(parser);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ValidateQueryResponse createTestInstance() {
|
||||
return createRandomValidateQueryResponse();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void assertEqualInstances(ValidateQueryResponse response, ValidateQueryResponse parsedResponse) {
|
||||
super.assertEqualInstances(response, parsedResponse);
|
||||
Set<QueryExplanation> queryExplSet = new HashSet<>(response.getQueryExplanation());
|
||||
assertEquals(response.isValid(), parsedResponse.isValid());
|
||||
assertEquals(response.getQueryExplanation().size(), parsedResponse.getQueryExplanation().size());
|
||||
assertTrue(queryExplSet.containsAll(parsedResponse.getQueryExplanation()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ValidateQueryResponse createTestInstance(int totalShards, int successfulShards, int failedShards,
|
||||
List<DefaultShardOperationFailedException> failures) {
|
||||
return createRandomValidateQueryResponse(totalShards, successfulShards, failedShards, failures);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void testToXContent() {
|
||||
ValidateQueryResponse response = createTestInstance(10, 10, 0, new ArrayList<>());
|
||||
String output = Strings.toString(response);
|
||||
assertEquals("{\"_shards\":{\"total\":10,\"successful\":10,\"failed\":0},\"valid\":true}", output);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue