diff --git a/src/main/java/org/apache/lucene/search/PublicTermsFilter.java b/src/main/java/org/apache/lucene/search/PublicTermsFilter.java index 4cf98881b94..f7cc68bf914 100644 --- a/src/main/java/org/apache/lucene/search/PublicTermsFilter.java +++ b/src/main/java/org/apache/lucene/search/PublicTermsFilter.java @@ -93,4 +93,17 @@ public class PublicTermsFilter extends Filter { } return result; } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + for(Term term: terms) { + if(builder.length() > 0) { + builder.append(' '); + } + builder.append(term); + } + return builder.toString(); + } + } diff --git a/src/main/java/org/elasticsearch/action/admin/indices/validate/query/QueryExplanation.java b/src/main/java/org/elasticsearch/action/admin/indices/validate/query/QueryExplanation.java new file mode 100644 index 00000000000..743c0ff25bf --- /dev/null +++ b/src/main/java/org/elasticsearch/action/admin/indices/validate/query/QueryExplanation.java @@ -0,0 +1,105 @@ +/* + * Licensed to ElasticSearch and Shay Banon 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.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.common.io.stream.Streamable; + +import java.io.IOException; + +/** + * + */ +public class QueryExplanation implements Streamable { + + private String index; + + private boolean valid; + + private String explanation; + + private String error; + + QueryExplanation() { + + } + + public QueryExplanation(String index, boolean valid, String explanation, String error) { + this.index = index; + this.valid = valid; + this.explanation = explanation; + this.error = error; + } + + public String index() { + return this.index; + } + + public String getIndex() { + return index(); + } + + public boolean valid() { + return this.valid; + } + + public boolean getValid() { + return valid(); + } + + public String error() { + return this.error; + } + + public String getError() { + return error(); + } + + public String explanation() { + return this.explanation; + } + + public String getExplanation() { + return explanation(); + } + + @Override + public void readFrom(StreamInput in) throws IOException { + index = in.readUTF(); + valid = in.readBoolean(); + explanation = in.readOptionalUTF(); + error = in.readOptionalUTF(); + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeUTF(index); + out.writeBoolean(valid); + out.writeOptionalUTF(explanation); + out.writeOptionalUTF(error); + } + + public static QueryExplanation readQueryExplanation(StreamInput in) throws IOException { + QueryExplanation exp = new QueryExplanation(); + exp.readFrom(in); + return exp; + } +} diff --git a/src/main/java/org/elasticsearch/action/admin/indices/validate/query/ShardValidateQueryRequest.java b/src/main/java/org/elasticsearch/action/admin/indices/validate/query/ShardValidateQueryRequest.java index e725b1dd98d..3e97d36ba44 100644 --- a/src/main/java/org/elasticsearch/action/admin/indices/validate/query/ShardValidateQueryRequest.java +++ b/src/main/java/org/elasticsearch/action/admin/indices/validate/query/ShardValidateQueryRequest.java @@ -36,6 +36,8 @@ class ShardValidateQueryRequest extends BroadcastShardOperationRequest { private BytesHolder querySource; private String[] types = Strings.EMPTY_ARRAY; + + private boolean explain; @Nullable private String[] filteringAliases; @@ -48,6 +50,7 @@ class ShardValidateQueryRequest extends BroadcastShardOperationRequest { super(index, shardId); this.querySource = request.querySource(); this.types = request.types(); + this.explain = request.explain(); this.filteringAliases = filteringAliases; } @@ -58,6 +61,10 @@ class ShardValidateQueryRequest extends BroadcastShardOperationRequest { public String[] types() { return this.types; } + + public boolean explain() { + return this.explain; + } public String[] filteringAliases() { return filteringAliases; @@ -82,6 +89,8 @@ class ShardValidateQueryRequest extends BroadcastShardOperationRequest { filteringAliases[i] = in.readUTF(); } } + + explain = in.readBoolean(); } @Override @@ -101,5 +110,7 @@ class ShardValidateQueryRequest extends BroadcastShardOperationRequest { } else { out.writeVInt(0); } + + out.writeBoolean(explain); } } diff --git a/src/main/java/org/elasticsearch/action/admin/indices/validate/query/ShardValidateQueryResponse.java b/src/main/java/org/elasticsearch/action/admin/indices/validate/query/ShardValidateQueryResponse.java index e1f00448bc7..a514613828b 100644 --- a/src/main/java/org/elasticsearch/action/admin/indices/validate/query/ShardValidateQueryResponse.java +++ b/src/main/java/org/elasticsearch/action/admin/indices/validate/query/ShardValidateQueryResponse.java @@ -33,19 +33,33 @@ import java.io.IOException; class ShardValidateQueryResponse extends BroadcastShardOperationResponse { private boolean valid; + + private String explanation; + private String error; + ShardValidateQueryResponse() { } - public ShardValidateQueryResponse(String index, int shardId, boolean valid) { + public ShardValidateQueryResponse(String index, int shardId, boolean valid, String explanation, String error) { super(index, shardId); this.valid = valid; + this.explanation = explanation; + this.error = error; } - boolean valid() { + public boolean valid() { return this.valid; } + + public String explanation() { + return explanation; + } + + public String error() { + return error; + } @Override public void readFrom(StreamInput in) throws IOException { diff --git a/src/main/java/org/elasticsearch/action/admin/indices/validate/query/TransportValidateQueryAction.java b/src/main/java/org/elasticsearch/action/admin/indices/validate/query/TransportValidateQueryAction.java index 47081e5eb09..ad3a9acc7d1 100644 --- a/src/main/java/org/elasticsearch/action/admin/indices/validate/query/TransportValidateQueryAction.java +++ b/src/main/java/org/elasticsearch/action/admin/indices/validate/query/TransportValidateQueryAction.java @@ -34,6 +34,7 @@ import org.elasticsearch.cluster.routing.ShardRouting; import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.index.query.IndexQueryParserService; +import org.elasticsearch.index.query.ParsedQuery; import org.elasticsearch.index.query.QueryParsingException; import org.elasticsearch.indices.IndicesService; import org.elasticsearch.threadpool.ThreadPool; @@ -113,6 +114,7 @@ public class TransportValidateQueryAction extends TransportBroadcastOperationAct int failedShards = 0; boolean valid = true; List shardFailures = null; + List queryExplanations = null; for (int i = 0; i < shardsResponses.length(); i++) { Object shardResponse = shardsResponses.get(i); if (shardResponse == null) { @@ -124,29 +126,48 @@ public class TransportValidateQueryAction extends TransportBroadcastOperationAct } shardFailures.add(new DefaultShardOperationFailedException((BroadcastShardOperationFailedException) shardResponse)); } else { - valid = valid && ((ShardValidateQueryResponse) shardResponse).valid(); + ShardValidateQueryResponse validateQueryResponse = (ShardValidateQueryResponse) shardResponse; + valid = valid && validateQueryResponse.valid(); + if(request.explain()) { + if(queryExplanations == null) { + queryExplanations = newArrayList(); + } + queryExplanations.add(new QueryExplanation( + validateQueryResponse.index(), + validateQueryResponse.valid(), + validateQueryResponse.explanation(), + validateQueryResponse.error() + )); + } successfulShards++; } } - return new ValidateQueryResponse(valid, shardsResponses.length(), successfulShards, failedShards, shardFailures); + return new ValidateQueryResponse(valid, queryExplanations, shardsResponses.length(), successfulShards, failedShards, shardFailures); } @Override protected ShardValidateQueryResponse shardOperation(ShardValidateQueryRequest request) throws ElasticSearchException { IndexQueryParserService queryParserService = indicesService.indexServiceSafe(request.index()).queryParserService(); boolean valid; + String explanation = null; + String error = null; if (request.querySource().length() == 0) { valid = true; } else { try { - queryParserService.parse(request.querySource().bytes(), request.querySource().offset(), request.querySource().length()); + ParsedQuery parsedQuery = queryParserService.parse(request.querySource().bytes(), request.querySource().offset(), request.querySource().length()); valid = true; + if(request.explain()) { + explanation = parsedQuery.query().toString(); + } } catch (QueryParsingException e) { valid = false; + error = e.getDetailedMessage(); } catch (AssertionError e) { valid = false; + error = e.getMessage(); } } - return new ShardValidateQueryResponse(request.index(), request.shardId(), valid); + return new ShardValidateQueryResponse(request.index(), request.shardId(), valid, explanation, error); } } diff --git a/src/main/java/org/elasticsearch/action/admin/indices/validate/query/ValidateQueryRequest.java b/src/main/java/org/elasticsearch/action/admin/indices/validate/query/ValidateQueryRequest.java index bf38df36092..cb937599db4 100644 --- a/src/main/java/org/elasticsearch/action/admin/indices/validate/query/ValidateQueryRequest.java +++ b/src/main/java/org/elasticsearch/action/admin/indices/validate/query/ValidateQueryRequest.java @@ -55,6 +55,8 @@ public class ValidateQueryRequest extends BroadcastOperationRequest { private int querySourceOffset; private int querySourceLength; private boolean querySourceUnsafe; + + private boolean explain; private String[] types = Strings.EMPTY_ARRAY; @@ -205,6 +207,20 @@ public class ValidateQueryRequest extends BroadcastOperationRequest { return this; } + /** + * Indicate if detailed information about query is requested + */ + public void explain(boolean explain) { + this.explain = explain; + } + + /** + * Indicates if detailed information about query is requested + */ + public boolean explain() { + return explain; + } + @Override public void readFrom(StreamInput in) throws IOException { super.readFrom(in); @@ -222,6 +238,9 @@ public class ValidateQueryRequest extends BroadcastOperationRequest { types[i] = in.readUTF(); } } + + explain = in.readBoolean(); + } @Override @@ -234,10 +253,12 @@ public class ValidateQueryRequest extends BroadcastOperationRequest { for (String type : types) { out.writeUTF(type); } + + out.writeBoolean(explain); } @Override public String toString() { - return "[" + Arrays.toString(indices) + "]" + Arrays.toString(types) + ", querySource[" + Unicode.fromBytes(querySource, querySourceOffset, querySourceLength) + "]"; + return "[" + Arrays.toString(indices) + "]" + Arrays.toString(types) + ", querySource[" + Unicode.fromBytes(querySource, querySourceOffset, querySourceLength) + "], explain:" + explain; } } diff --git a/src/main/java/org/elasticsearch/action/admin/indices/validate/query/ValidateQueryRequestBuilder.java b/src/main/java/org/elasticsearch/action/admin/indices/validate/query/ValidateQueryRequestBuilder.java index 02982e685fe..80c7265c6b5 100644 --- a/src/main/java/org/elasticsearch/action/admin/indices/validate/query/ValidateQueryRequestBuilder.java +++ b/src/main/java/org/elasticsearch/action/admin/indices/validate/query/ValidateQueryRequestBuilder.java @@ -50,6 +50,16 @@ public class ValidateQueryRequestBuilder extends BaseIndicesRequestBuilder queryExplanations; ValidateQueryResponse() { } - ValidateQueryResponse(boolean valid, int totalShards, int successfulShards, int failedShards, List shardFailures) { + ValidateQueryResponse(boolean valid, List queryExplanations, int totalShards, int successfulShards, int failedShards, List shardFailures) { super(totalShards, successfulShards, failedShards, shardFailures); this.valid = valid; + this.queryExplanations = queryExplanations; + if (queryExplanations == null) { + this.queryExplanations = ImmutableList.of(); + } } /** @@ -59,15 +69,44 @@ public class ValidateQueryResponse extends BroadcastOperationResponse { return valid; } + /** + * The list of query explanations. + */ + public List queryExplanations() { + if (queryExplanations == null) { + return ImmutableList.of(); + } + return queryExplanations; + } + + /** + * The list of query explanations. + */ + public List getQueryExplanation() { + return queryExplanations; + } + @Override public void readFrom(StreamInput in) throws IOException { super.readFrom(in); valid = in.readBoolean(); + int size = in.readVInt(); + if (size > 0) { + queryExplanations = new ArrayList(size); + for (int i = 0; i < size; i++) { + queryExplanations.add(readQueryExplanation(in)); + } + } } @Override public void writeTo(StreamOutput out) throws IOException { super.writeTo(out); out.writeBoolean(valid); + out.writeVInt(queryExplanations.size()); + for (QueryExplanation exp : queryExplanations) { + exp.writeTo(out); + } + } } diff --git a/src/main/java/org/elasticsearch/common/lucene/search/AndFilter.java b/src/main/java/org/elasticsearch/common/lucene/search/AndFilter.java index ae11d1bbee8..704643baea3 100644 --- a/src/main/java/org/elasticsearch/common/lucene/search/AndFilter.java +++ b/src/main/java/org/elasticsearch/common/lucene/search/AndFilter.java @@ -87,6 +87,19 @@ public class AndFilter extends Filter { return equalFilters(filters, other.filters); } + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + for(Filter filter: filters) { + if(builder.length() > 0) { + builder.append(' '); + } + builder.append('+'); + builder.append(filter); + } + return builder.toString(); + } + private boolean equalFilters(List filters1, List filters2) { return (filters1 == filters2) || ((filters1 != null) && filters1.equals(filters2)); } diff --git a/src/main/java/org/elasticsearch/common/lucene/search/NotFilter.java b/src/main/java/org/elasticsearch/common/lucene/search/NotFilter.java index c1e6b39a510..865a4c8287d 100644 --- a/src/main/java/org/elasticsearch/common/lucene/search/NotFilter.java +++ b/src/main/java/org/elasticsearch/common/lucene/search/NotFilter.java @@ -65,6 +65,11 @@ public class NotFilter extends Filter { return !(filter != null ? !filter.equals(notFilter.filter) : notFilter.filter != null); } + @Override + public String toString() { + return "NotFilter(" + filter + ")"; + } + @Override public int hashCode() { return filter != null ? filter.hashCode() : 0; diff --git a/src/main/java/org/elasticsearch/common/lucene/search/OrFilter.java b/src/main/java/org/elasticsearch/common/lucene/search/OrFilter.java index 6933b37bcec..b6d12b03e89 100644 --- a/src/main/java/org/elasticsearch/common/lucene/search/OrFilter.java +++ b/src/main/java/org/elasticsearch/common/lucene/search/OrFilter.java @@ -93,6 +93,18 @@ public class OrFilter extends Filter { return equalFilters(filters, other.filters); } + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + for(Filter filter: filters) { + if(builder.length() > 0) { + builder.append(' '); + } + builder.append(filter); + } + return builder.toString(); + } + private boolean equalFilters(List filters1, List filters2) { return (filters1 == filters2) || ((filters1 != null) && filters1.equals(filters2)); } diff --git a/src/main/java/org/elasticsearch/index/search/UidFilter.java b/src/main/java/org/elasticsearch/index/search/UidFilter.java index d84bbef6902..68401779622 100644 --- a/src/main/java/org/elasticsearch/index/search/UidFilter.java +++ b/src/main/java/org/elasticsearch/index/search/UidFilter.java @@ -33,10 +33,7 @@ import org.elasticsearch.index.mapper.Uid; import org.elasticsearch.index.mapper.internal.UidFieldMapper; import java.io.IOException; -import java.util.Collection; -import java.util.List; -import java.util.Set; -import java.util.TreeSet; +import java.util.*; public class UidFilter extends Filter { @@ -95,6 +92,11 @@ public class UidFilter extends Filter { return !uids.equals(uidFilter.uids); } + @Override + public String toString() { + return "UidFilter(" + uids + ")"; + } + @Override public int hashCode() { return uids.hashCode(); diff --git a/src/main/java/org/elasticsearch/index/search/geo/GeoDistanceFilter.java b/src/main/java/org/elasticsearch/index/search/geo/GeoDistanceFilter.java index 2d68f5276eb..961a342096c 100644 --- a/src/main/java/org/elasticsearch/index/search/geo/GeoDistanceFilter.java +++ b/src/main/java/org/elasticsearch/index/search/geo/GeoDistanceFilter.java @@ -137,6 +137,11 @@ public class GeoDistanceFilter extends Filter { return true; } + @Override + public String toString() { + return "GeoDistanceFilter(" + fieldName + ", " + geoDistance + ", " + distance + ", " + lat + ", " + lon + ")"; + } + @Override public int hashCode() { int result; diff --git a/src/main/java/org/elasticsearch/index/search/geo/GeoDistanceRangeFilter.java b/src/main/java/org/elasticsearch/index/search/geo/GeoDistanceRangeFilter.java index 0e28eb636db..07c9e2a88fb 100644 --- a/src/main/java/org/elasticsearch/index/search/geo/GeoDistanceRangeFilter.java +++ b/src/main/java/org/elasticsearch/index/search/geo/GeoDistanceRangeFilter.java @@ -151,6 +151,11 @@ public class GeoDistanceRangeFilter extends Filter { return true; } + @Override + public String toString() { + return "GeoDistanceRangeFilter(" + fieldName + ", " + geoDistance + ", [" + inclusiveLowerPoint + " - " + inclusiveUpperPoint + "], " + lat + ", " + lon + ")"; + } + @Override public int hashCode() { int result; diff --git a/src/main/java/org/elasticsearch/index/search/geo/GeoPolygonFilter.java b/src/main/java/org/elasticsearch/index/search/geo/GeoPolygonFilter.java index 192ce50e741..cb920a9c471 100644 --- a/src/main/java/org/elasticsearch/index/search/geo/GeoPolygonFilter.java +++ b/src/main/java/org/elasticsearch/index/search/geo/GeoPolygonFilter.java @@ -28,6 +28,7 @@ import org.elasticsearch.index.mapper.geo.GeoPointFieldData; import org.elasticsearch.index.mapper.geo.GeoPointFieldDataType; import java.io.IOException; +import java.util.Arrays; /** * @@ -60,6 +61,11 @@ public class GeoPolygonFilter extends Filter { return new GeoPolygonDocSet(reader.maxDoc(), fieldData, points); } + @Override + public String toString() { + return "GeoPolygonFilter(" + fieldName + ", " + Arrays.toString(points) + ")"; + } + public static class GeoPolygonDocSet extends GetDocSet { private final GeoPointFieldData fieldData; private final Point[] points; diff --git a/src/main/java/org/elasticsearch/index/search/geo/InMemoryGeoBoundingBoxFilter.java b/src/main/java/org/elasticsearch/index/search/geo/InMemoryGeoBoundingBoxFilter.java index 2a03366117e..aa98e8d7203 100644 --- a/src/main/java/org/elasticsearch/index/search/geo/InMemoryGeoBoundingBoxFilter.java +++ b/src/main/java/org/elasticsearch/index/search/geo/InMemoryGeoBoundingBoxFilter.java @@ -73,6 +73,11 @@ public class InMemoryGeoBoundingBoxFilter extends Filter { } } + @Override + public String toString() { + return "GeoBoundingBoxFilter(" + fieldName + ", " + topLeft + ", " + bottomRight + ")"; + } + public static class Meridian180GeoBoundingBoxDocSet extends GetDocSet { private final GeoPointFieldData fieldData; private final Point topLeft; diff --git a/src/main/java/org/elasticsearch/rest/action/admin/indices/validate/query/RestValidateQueryAction.java b/src/main/java/org/elasticsearch/rest/action/admin/indices/validate/query/RestValidateQueryAction.java index f41225c2b5a..9d5058814e0 100644 --- a/src/main/java/org/elasticsearch/rest/action/admin/indices/validate/query/RestValidateQueryAction.java +++ b/src/main/java/org/elasticsearch/rest/action/admin/indices/validate/query/RestValidateQueryAction.java @@ -20,6 +20,7 @@ package org.elasticsearch.rest.action.admin.indices.validate.query; import org.elasticsearch.action.ActionListener; +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.broadcast.BroadcastOperationThreading; @@ -83,6 +84,11 @@ public class RestValidateQueryAction extends BaseRestHandler { } } validateQueryRequest.types(splitTypes(request.param("type"))); + if (request.paramAsBoolean("explain", false)) { + validateQueryRequest.explain(true); + } else { + validateQueryRequest.explain(false); + } } catch (Exception e) { try { XContentBuilder builder = RestXContentBuilder.restContentBuilder(request); @@ -103,6 +109,24 @@ public class RestValidateQueryAction extends BaseRestHandler { buildBroadcastShardsHeader(builder, response); + if(response.queryExplanations() != null && !response.queryExplanations().isEmpty()) { + builder.startArray("explanations"); + for (QueryExplanation explanation : response.queryExplanations()) { + builder.startObject(); + if (explanation.index() != null) { + builder.field("index", explanation.index(), XContentBuilder.FieldCaseConversion.NONE); + } + builder.field("valid", explanation.valid()); + if (explanation.error() != null) { + builder.field("error", explanation.error()); + } + if (explanation.explanation() != null) { + builder.field("explanation", explanation.explanation()); + } + builder.endObject(); + } + builder.endArray(); + } builder.endObject(); channel.sendResponse(new XContentRestResponse(request, OK, builder)); } catch (Exception e) { diff --git a/src/test/java/org/elasticsearch/test/integration/validate/SimpleValidateQueryTests.java b/src/test/java/org/elasticsearch/test/integration/validate/SimpleValidateQueryTests.java index 680374a183f..28c0702c381 100644 --- a/src/test/java/org/elasticsearch/test/integration/validate/SimpleValidateQueryTests.java +++ b/src/test/java/org/elasticsearch/test/integration/validate/SimpleValidateQueryTests.java @@ -19,11 +19,17 @@ package org.elasticsearch.test.integration.validate; +import org.elasticsearch.action.admin.indices.validate.query.ValidateQueryResponse; import org.elasticsearch.client.Client; import org.elasticsearch.common.settings.ImmutableSettings; +import org.elasticsearch.common.unit.DistanceUnit; import org.elasticsearch.common.xcontent.XContentFactory; +import org.elasticsearch.index.query.FilterBuilders; +import org.elasticsearch.index.query.QueryBuilder; import org.elasticsearch.index.query.QueryBuilders; +import org.elasticsearch.index.search.geo.GeoDistance; import org.elasticsearch.test.integration.AbstractNodesTests; +import org.hamcrest.Matcher; import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; @@ -81,4 +87,107 @@ public class SimpleValidateQueryTests extends AbstractNodesTests { assertThat(client.admin().indices().prepareValidateQuery("test").setQuery(QueryBuilders.queryString("foo:1 AND")).execute().actionGet().valid(), equalTo(false)); } + + @Test + public void explainValidateQuery() throws Exception { + client.admin().indices().prepareDelete().execute().actionGet(); + + client.admin().indices().prepareCreate("test").setSettings(ImmutableSettings.settingsBuilder().put("index.number_of_shards", 1)).execute().actionGet(); + client.admin().cluster().prepareHealth().setWaitForGreenStatus().execute().actionGet(); + client.admin().indices().preparePutMapping("test").setType("type1") + .setSource(XContentFactory.jsonBuilder().startObject().startObject("type1").startObject("properties") + .startObject("foo").field("type", "string").endObject() + .startObject("bar").field("type", "integer").endObject() + .startObject("baz").field("type", "string").field("analyzer", "snowball").endObject() + .startObject("pin").startObject("properties").startObject("location").field("type", "geo_point").endObject().endObject().endObject() + .endObject().endObject().endObject()) + .execute().actionGet(); + + client.admin().indices().prepareRefresh().execute().actionGet(); + + + ValidateQueryResponse response; + response = client.admin().indices().prepareValidateQuery("test") + .setQuery("foo".getBytes()) + .setExplain(true) + .execute().actionGet(); + assertThat(response.valid(), equalTo(false)); + assertThat(response.queryExplanations().size(), equalTo(1)); + assertThat(response.queryExplanations().get(0).error(), containsString("Failed to parse")); + assertThat(response.queryExplanations().get(0).explanation(), nullValue()); + + assertExplanation(QueryBuilders.queryString("_id:1"), equalTo("ConstantScore(UidFilter([_uid:type1#1]))")); + + assertExplanation(QueryBuilders.idsQuery("type1").addIds("1").addIds("2"), + equalTo("ConstantScore(UidFilter([_uid:type1#1, _uid:type1#2]))")); + + assertExplanation(QueryBuilders.queryString("foo"), equalTo("_all:foo")); + + assertExplanation(QueryBuilders.filteredQuery( + QueryBuilders.termQuery("foo", "1"), + FilterBuilders.orFilter( + FilterBuilders.termFilter("bar", "2"), + FilterBuilders.termFilter("baz", "3") + ) + ), equalTo("filtered(foo:1)->cache(bar:[2 TO 2]) cache(baz:3)")); + + assertExplanation(QueryBuilders.filteredQuery( + QueryBuilders.termQuery("foo", "1"), + FilterBuilders.orFilter( + FilterBuilders.termFilter("bar", "2") + ) + ), equalTo("filtered(foo:1)->cache(bar:[2 TO 2])")); + + assertExplanation(QueryBuilders.filteredQuery( + QueryBuilders.matchAllQuery(), + FilterBuilders.geoPolygonFilter("pin.location") + .addPoint(40, -70) + .addPoint(30, -80) + .addPoint(20, -90) + ), equalTo("ConstantScore(NotDeleted(GeoPolygonFilter(pin.location, [[40.0, -70.0], [30.0, -80.0], [20.0, -90.0]])))")); + + assertExplanation(QueryBuilders.constantScoreQuery(FilterBuilders.geoBoundingBoxFilter("pin.location") + .topLeft(40, -80) + .bottomRight(20, -70) + ), equalTo("ConstantScore(NotDeleted(GeoBoundingBoxFilter(pin.location, [40.0, -80.0], [20.0, -70.0])))")); + + assertExplanation(QueryBuilders.constantScoreQuery(FilterBuilders.geoDistanceFilter("pin.location") + .lat(10).lon(20).distance(15, DistanceUnit.MILES).geoDistance(GeoDistance.PLANE) + ), equalTo("ConstantScore(NotDeleted(GeoDistanceFilter(pin.location, PLANE, 15.0, 10.0, 20.0)))")); + + assertExplanation(QueryBuilders.constantScoreQuery(FilterBuilders.geoDistanceFilter("pin.location") + .lat(10).lon(20).distance(15, DistanceUnit.MILES).geoDistance(GeoDistance.PLANE) + ), equalTo("ConstantScore(NotDeleted(GeoDistanceFilter(pin.location, PLANE, 15.0, 10.0, 20.0)))")); + + assertExplanation(QueryBuilders.constantScoreQuery(FilterBuilders.geoDistanceRangeFilter("pin.location") + .lat(10).lon(20).from("15miles").to("25miles").geoDistance(GeoDistance.PLANE) + ), equalTo("ConstantScore(NotDeleted(GeoDistanceRangeFilter(pin.location, PLANE, [15.0 - 25.0], 10.0, 20.0)))")); + + assertExplanation(QueryBuilders.filteredQuery( + QueryBuilders.termQuery("foo", "1"), + FilterBuilders.andFilter( + FilterBuilders.termFilter("bar", "2"), + FilterBuilders.termFilter("baz", "3") + ) + ), equalTo("filtered(foo:1)->+cache(bar:[2 TO 2]) +cache(baz:3)")); + + assertExplanation(QueryBuilders.constantScoreQuery(FilterBuilders.termsFilter("foo", "1", "2", "3")), + equalTo("ConstantScore(NotDeleted(cache(foo:1 foo:2 foo:3)))")); + + assertExplanation(QueryBuilders.constantScoreQuery(FilterBuilders.notFilter(FilterBuilders.termFilter("foo", "bar"))), + equalTo("ConstantScore(NotDeleted(NotFilter(cache(foo:bar))))")); + + } + + private void assertExplanation(QueryBuilder queryBuilder, Matcher matcher) { + ValidateQueryResponse response = client.admin().indices().prepareValidateQuery("test") + .setQuery(queryBuilder) + .setExplain(true) + .execute().actionGet(); + assertThat(response.queryExplanations().size(), equalTo(1)); + assertThat(response.queryExplanations().get(0).error(), nullValue()); + assertThat(response.queryExplanations().get(0).explanation(), matcher); + assertThat(response.valid(), equalTo(true)); + } + }