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());
+ }
}
diff --git a/server/src/main/java/org/elasticsearch/action/admin/indices/validate/query/ValidateQueryRequest.java b/server/src/main/java/org/elasticsearch/action/admin/indices/validate/query/ValidateQueryRequest.java
index 5953a5548c4..7694e7583c8 100644
--- a/server/src/main/java/org/elasticsearch/action/admin/indices/validate/query/ValidateQueryRequest.java
+++ b/server/src/main/java/org/elasticsearch/action/admin/indices/validate/query/ValidateQueryRequest.java
@@ -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;
*
* The request requires the query to be set using {@link #query(QueryBuilder)}
*/
-public class ValidateQueryRequest extends BroadcastRequest {
+public class ValidateQueryRequest extends BroadcastRequest implements ToXContentObject {
private QueryBuilder query = new MatchAllQueryBuilder();
@@ -179,4 +181,12 @@ public class ValidateQueryRequest extends BroadcastRequest
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();
+ }
}
diff --git a/server/src/main/java/org/elasticsearch/action/admin/indices/validate/query/ValidateQueryResponse.java b/server/src/main/java/org/elasticsearch/action/admin/indices/validate/query/ValidateQueryResponse.java
index 5bb11dd56e0..f766e1d9c6a 100644
--- a/server/src/main/java/org/elasticsearch/action/admin/indices/validate/query/ValidateQueryResponse.java
+++ b/server/src/main/java/org/elasticsearch/action/admin/indices/validate/query/ValidateQueryResponse.java
@@ -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 PARSER = new ConstructingObjectParser<>(
+ "validate_query",
+ true,
+ arg -> {
+ BroadcastResponse response = (BroadcastResponse) arg[0];
+ return
+ new ValidateQueryResponse(
+ (boolean)arg[1],
+ (List)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);
+ }
}
diff --git a/server/src/main/java/org/elasticsearch/rest/action/admin/indices/RestValidateQueryAction.java b/server/src/main/java/org/elasticsearch/rest/action/admin/indices/RestValidateQueryAction.java
index 57486396f91..d1a97d74d04 100644
--- a/server/src/main/java/org/elasticsearch/rest/action/admin/indices/RestValidateQueryAction.java
+++ b/server/src/main/java/org/elasticsearch/rest/action/admin/indices/RestValidateQueryAction.java
@@ -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);
diff --git a/server/src/test/java/org/elasticsearch/action/admin/indices/validate/query/QueryExplanationTests.java b/server/src/test/java/org/elasticsearch/action/admin/indices/validate/query/QueryExplanationTests.java
new file mode 100644
index 00000000000..db167e0c766
--- /dev/null
+++ b/server/src/test/java/org/elasticsearch/action/admin/indices/validate/query/QueryExplanationTests.java
@@ -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 {
+
+ 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();
+ }
+}
diff --git a/server/src/test/java/org/elasticsearch/action/admin/indices/validate/query/ValidateQueryResponseTests.java b/server/src/test/java/org/elasticsearch/action/admin/indices/validate/query/ValidateQueryResponseTests.java
new file mode 100644
index 00000000000..d72aae8fa2b
--- /dev/null
+++ b/server/src/test/java/org/elasticsearch/action/admin/indices/validate/query/ValidateQueryResponseTests.java
@@ -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 {
+
+ private static ValidateQueryResponse createRandomValidateQueryResponse(
+ int totalShards, int successfulShards, int failedShards, List failures) {
+ boolean valid = failedShards == 0;
+ List 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 queryExplanations = new ArrayList<>(totalShards);
+ List shardFailures = new ArrayList<>(failedShards);
+ for (int i=0; i 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 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);
+ }
+}