From be6e18cb3652da731d9877af48d77fd261207334 Mon Sep 17 00:00:00 2001 From: Lee Hinman Date: Thu, 22 Dec 2011 16:16:18 -0700 Subject: [PATCH] Add query validation feature --- .../action/TransportActions.java | 2 + .../action/validate/ShardValidateRequest.java | 120 +++++++++ .../validate/ShardValidateResponse.java | 61 +++++ .../validate/TransportValidateAction.java | 141 ++++++++++ .../action/validate/ValidateRequest.java | 254 ++++++++++++++++++ .../action/validate/ValidateResponse.java | 73 +++++ .../action/validate/pacakge-info.java | 23 ++ .../java/org/elasticsearch/client/Client.java | 26 ++ .../validate/ValidateRequestBuilder.java | 75 ++++++ .../elasticsearch/client/node/NodeClient.java | 19 +- .../client/support/AbstractClient.java | 6 + .../client/transport/TransportClient.java | 12 + .../ClientTransportValidateAction.java | 25 ++ .../support/InternalTransportClient.java | 28 +- .../index/shard/service/IndexShard.java | 4 + .../shard/service/InternalIndexShard.java | 23 ++ .../rest/action/RestActionModule.java | 3 + .../action/validate/RestValidateAction.java | 122 +++++++++ .../validate/SimpleValidateTests.java | 84 ++++++ 19 files changed, 1099 insertions(+), 2 deletions(-) create mode 100644 src/main/java/org/elasticsearch/action/validate/ShardValidateRequest.java create mode 100644 src/main/java/org/elasticsearch/action/validate/ShardValidateResponse.java create mode 100644 src/main/java/org/elasticsearch/action/validate/TransportValidateAction.java create mode 100644 src/main/java/org/elasticsearch/action/validate/ValidateRequest.java create mode 100644 src/main/java/org/elasticsearch/action/validate/ValidateResponse.java create mode 100644 src/main/java/org/elasticsearch/action/validate/pacakge-info.java create mode 100644 src/main/java/org/elasticsearch/client/action/validate/ValidateRequestBuilder.java create mode 100644 src/main/java/org/elasticsearch/client/transport/action/validate/ClientTransportValidateAction.java create mode 100644 src/main/java/org/elasticsearch/rest/action/validate/RestValidateAction.java create mode 100644 src/test/java/org/elasticsearch/test/integration/validate/SimpleValidateTests.java diff --git a/src/main/java/org/elasticsearch/action/TransportActions.java b/src/main/java/org/elasticsearch/action/TransportActions.java index a10a445d76b..f3826e17405 100644 --- a/src/main/java/org/elasticsearch/action/TransportActions.java +++ b/src/main/java/org/elasticsearch/action/TransportActions.java @@ -29,6 +29,8 @@ public class TransportActions { public static final String INDEX = "indices/index/shard/index"; public static final String COUNT = "indices/count"; + + public static final String VALIDATE = "indices/validate"; public static final String DELETE = "indices/index/shard/delete"; diff --git a/src/main/java/org/elasticsearch/action/validate/ShardValidateRequest.java b/src/main/java/org/elasticsearch/action/validate/ShardValidateRequest.java new file mode 100644 index 00000000000..d275a43c706 --- /dev/null +++ b/src/main/java/org/elasticsearch/action/validate/ShardValidateRequest.java @@ -0,0 +1,120 @@ +/* + * 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.validate; + +import org.elasticsearch.action.support.broadcast.BroadcastShardOperationRequest; +import org.elasticsearch.common.Nullable; +import org.elasticsearch.common.Strings; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; + +import java.io.IOException; + +/** + * Internal validate request executed directly against a specific index shard. + * + * + */ +class ShardValidateRequest extends BroadcastShardOperationRequest { + + private byte[] querySource; + private int querySourceOffset; + private int querySourceLength; + + private String[] types = Strings.EMPTY_ARRAY; + + @Nullable + private String[] filteringAliases; + + ShardValidateRequest() { + + } + + public ShardValidateRequest(String index, int shardId, @Nullable String[] filteringAliases, ValidateRequest request) { + super(index, shardId); + this.querySource = request.querySource(); + this.querySourceOffset = request.querySourceOffset(); + this.querySourceLength = request.querySourceLength(); + this.types = request.types(); + this.filteringAliases = filteringAliases; + } + + public byte[] querySource() { + return querySource; + } + + public int querySourceOffset() { + return querySourceOffset; + } + + public int querySourceLength() { + return querySourceLength; + } + + public String[] types() { + return this.types; + } + + public String[] filteringAliases() { + return filteringAliases; + } + + @Override + public void readFrom(StreamInput in) throws IOException { + super.readFrom(in); + querySourceLength = in.readVInt(); + querySourceOffset = 0; + querySource = new byte[querySourceLength]; + in.readFully(querySource); + int typesSize = in.readVInt(); + if (typesSize > 0) { + types = new String[typesSize]; + for (int i = 0; i < typesSize; i++) { + types[i] = in.readUTF(); + } + } + int aliasesSize = in.readVInt(); + if (aliasesSize > 0) { + filteringAliases = new String[aliasesSize]; + for (int i = 0; i < aliasesSize; i++) { + filteringAliases[i] = in.readUTF(); + } + } + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + super.writeTo(out); + out.writeVInt(querySourceLength); + out.writeBytes(querySource, querySourceOffset, querySourceLength); + out.writeVInt(types.length); + for (String type : types) { + out.writeUTF(type); + } + if (filteringAliases != null) { + out.writeVInt(filteringAliases.length); + for (String alias : filteringAliases) { + out.writeUTF(alias); + } + } else { + out.writeVInt(0); + } + } +} diff --git a/src/main/java/org/elasticsearch/action/validate/ShardValidateResponse.java b/src/main/java/org/elasticsearch/action/validate/ShardValidateResponse.java new file mode 100644 index 00000000000..31b16cddcc7 --- /dev/null +++ b/src/main/java/org/elasticsearch/action/validate/ShardValidateResponse.java @@ -0,0 +1,61 @@ +/* + * 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.validate; + +import org.elasticsearch.action.support.broadcast.BroadcastShardOperationResponse; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; + +import java.io.IOException; + +/** + * Internal validate response of a shard validate request executed directly against a specific shard. + * + * + */ +class ShardValidateResponse extends BroadcastShardOperationResponse { + + private boolean valid; + + ShardValidateResponse() { + + } + + public ShardValidateResponse(String index, int shardId, boolean valid) { + super(index, shardId); + this.valid = valid; + } + + boolean valid() { + return this.valid; + } + + @Override + public void readFrom(StreamInput in) throws IOException { + super.readFrom(in); + valid = in.readBoolean(); + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + super.writeTo(out); + out.writeBoolean(valid); + } +} diff --git a/src/main/java/org/elasticsearch/action/validate/TransportValidateAction.java b/src/main/java/org/elasticsearch/action/validate/TransportValidateAction.java new file mode 100644 index 00000000000..d17771847ff --- /dev/null +++ b/src/main/java/org/elasticsearch/action/validate/TransportValidateAction.java @@ -0,0 +1,141 @@ +/* + * 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.validate; + +import org.elasticsearch.ElasticSearchException; +import org.elasticsearch.action.ShardOperationFailedException; +import org.elasticsearch.action.TransportActions; +import org.elasticsearch.action.support.DefaultShardOperationFailedException; +import org.elasticsearch.action.support.broadcast.BroadcastShardOperationFailedException; +import org.elasticsearch.action.support.broadcast.TransportBroadcastOperationAction; +import org.elasticsearch.cluster.ClusterService; +import org.elasticsearch.cluster.ClusterState; +import org.elasticsearch.cluster.block.ClusterBlockLevel; +import org.elasticsearch.cluster.routing.GroupShardsIterator; +import org.elasticsearch.cluster.routing.ShardRouting; +import org.elasticsearch.common.inject.Inject; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.index.shard.service.IndexShard; +import org.elasticsearch.indices.IndicesService; +import org.elasticsearch.threadpool.ThreadPool; +import org.elasticsearch.transport.TransportService; + +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicReferenceArray; + +import static com.google.common.collect.Lists.newArrayList; + +/** + * + */ +public class TransportValidateAction extends TransportBroadcastOperationAction { + + private final IndicesService indicesService; + + @Inject + public TransportValidateAction(Settings settings, ThreadPool threadPool, ClusterService clusterService, TransportService transportService, IndicesService indicesService) { + super(settings, threadPool, clusterService, transportService); + this.indicesService = indicesService; + } + + @Override + protected String executor() { + return ThreadPool.Names.SEARCH; + } + + @Override + protected String transportAction() { + return TransportActions.VALIDATE; + } + + @Override + protected String transportShardAction() { + return "indices/validate/shard"; + } + + @Override + protected ValidateRequest newRequest() { + return new ValidateRequest(); + } + + @Override + protected ShardValidateRequest newShardRequest() { + return new ShardValidateRequest(); + } + + @Override + protected ShardValidateRequest newShardRequest(ShardRouting shard, ValidateRequest request) { + String[] filteringAliases = clusterService.state().metaData().filteringAliases(shard.index(), request.indices()); + return new ShardValidateRequest(shard.index(), shard.id(), filteringAliases, request); + } + + @Override + protected ShardValidateResponse newShardResponse() { + return new ShardValidateResponse(); + } + + @Override + protected GroupShardsIterator shards(ValidateRequest request, String[] concreteIndices, ClusterState clusterState) { + // Hard-code routing to limit request to a single shard. + Map> routingMap = clusterState.metaData().resolveSearchRouting("0", request.indices()); + return clusterService.operationRouting().searchShards(clusterState, request.indices(), concreteIndices, null, routingMap, null); + } + + @Override + protected void checkBlock(ValidateRequest request, String[] concreteIndices, ClusterState state) { + for (String index : concreteIndices) { + state.blocks().indexBlocked(ClusterBlockLevel.READ, index); + } + } + + @Override + protected ValidateResponse newResponse(ValidateRequest request, AtomicReferenceArray shardsResponses, ClusterState clusterState) { + int successfulShards = 0; + int failedShards = 0; + boolean valid = true; + List shardFailures = null; + for (int i = 0; i < shardsResponses.length(); i++) { + Object shardResponse = shardsResponses.get(i); + if (shardResponse == null) { + failedShards++; + } else if (shardResponse instanceof BroadcastShardOperationFailedException) { + failedShards++; + if (shardFailures == null) { + shardFailures = newArrayList(); + } + shardFailures.add(new DefaultShardOperationFailedException((BroadcastShardOperationFailedException) shardResponse)); + } else { + valid = valid && ((ShardValidateResponse) shardResponse).valid(); + successfulShards++; + } + } + return new ValidateResponse(valid, shardsResponses.length(), successfulShards, failedShards, shardFailures); + } + + @Override + protected ShardValidateResponse shardOperation(ShardValidateRequest request) throws ElasticSearchException { + IndexShard indexShard = indicesService.indexServiceSafe(request.index()).shardSafe(request.shardId()); + boolean valid = indexShard.validate(request.querySource(), request.querySourceOffset(), request.querySourceLength(), + request.filteringAliases(), request.types()); + return new ShardValidateResponse(request.index(), request.shardId(), valid); + } +} diff --git a/src/main/java/org/elasticsearch/action/validate/ValidateRequest.java b/src/main/java/org/elasticsearch/action/validate/ValidateRequest.java new file mode 100644 index 00000000000..d0f1505c404 --- /dev/null +++ b/src/main/java/org/elasticsearch/action/validate/ValidateRequest.java @@ -0,0 +1,254 @@ +/* + * 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.validate; + +import org.apache.lucene.util.UnicodeUtil; +import org.elasticsearch.ElasticSearchGenerationException; +import org.elasticsearch.action.ActionRequestValidationException; +import org.elasticsearch.action.support.broadcast.BroadcastOperationRequest; +import org.elasticsearch.action.support.broadcast.BroadcastOperationThreading; +import org.elasticsearch.client.Requests; +import org.elasticsearch.common.Nullable; +import org.elasticsearch.common.Required; +import org.elasticsearch.common.Strings; +import org.elasticsearch.common.Unicode; +import org.elasticsearch.common.io.BytesStream; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +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 java.io.IOException; +import java.util.Arrays; +import java.util.Map; + +/** + * A request to validate a specific query. + *

+ *

The request requires the query source to be set either using {@link #query(org.elasticsearch.index.query.QueryBuilder)}, + * or {@link #query(byte[])}. + * + * + */ +public class ValidateRequest extends BroadcastOperationRequest { + + private static final XContentType contentType = Requests.CONTENT_TYPE; + + private byte[] querySource; + private int querySourceOffset; + private int querySourceLength; + private boolean querySourceUnsafe; + + private String[] types = Strings.EMPTY_ARRAY; + + ValidateRequest() { + } + + /** + * Constructs a new validate request against the provided indices. No indices provided means it will + * run against all indices. + */ + public ValidateRequest(String... indices) { + super(indices); + } + + @Override + public ActionRequestValidationException validate() { + ActionRequestValidationException validationException = super.validate(); + return validationException; + } + + /** + * Controls the operation threading model. + */ + @Override + public ValidateRequest operationThreading(BroadcastOperationThreading operationThreading) { + super.operationThreading(operationThreading); + return this; + } + + @Override + protected void beforeStart() { + if (querySourceUnsafe) { + querySource = Arrays.copyOfRange(querySource, querySourceOffset, querySourceOffset + querySourceLength); + querySourceOffset = 0; + querySourceUnsafe = false; + } + } + + /** + * Should the listener be called on a separate thread if needed. + */ + @Override + public ValidateRequest listenerThreaded(boolean threadedListener) { + super.listenerThreaded(threadedListener); + return this; + } + + public ValidateRequest indices(String... indices) { + this.indices = indices; + return this; + } + + /** + * The query source to execute. + */ + byte[] querySource() { + return querySource; + } + + int querySourceOffset() { + return querySourceOffset; + } + + int querySourceLength() { + return querySourceLength; + } + + /** + * The query source to execute. + * + * @see org.elasticsearch.index.query.QueryBuilders + */ + @Required + public ValidateRequest query(QueryBuilder queryBuilder) { + BytesStream bos = queryBuilder.buildAsUnsafeBytes(); + this.querySource = bos.underlyingBytes(); + this.querySourceOffset = 0; + this.querySourceLength = bos.size(); + this.querySourceUnsafe = true; + return this; + } + + /** + * The query source to execute in the form of a map. + */ + @Required + public ValidateRequest query(Map querySource) { + try { + XContentBuilder builder = XContentFactory.contentBuilder(contentType); + builder.map(querySource); + return query(builder); + } catch (IOException e) { + throw new ElasticSearchGenerationException("Failed to generate [" + querySource + "]", e); + } + } + + @Required + public ValidateRequest query(XContentBuilder builder) { + try { + this.querySource = builder.underlyingBytes(); + this.querySourceOffset = 0; + this.querySourceLength = builder.underlyingBytesLength(); + this.querySourceUnsafe = false; + return this; + } catch (IOException e) { + throw new ElasticSearchGenerationException("Failed to generate [" + builder + "]", e); + } + } + + /** + * The query source to validate. It is preferable to use either {@link #query(byte[])} + * or {@link #query(org.elasticsearch.index.query.QueryBuilder)}. + */ + @Required + public ValidateRequest query(String querySource) { + UnicodeUtil.UTF8Result result = Unicode.fromStringAsUtf8(querySource); + this.querySource = result.result; + this.querySourceOffset = 0; + this.querySourceLength = result.length; + this.querySourceUnsafe = true; + return this; + } + + /** + * The query source to validate. + */ + @Required + public ValidateRequest query(byte[] querySource) { + return query(querySource, 0, querySource.length, false); + } + + /** + * The query source to validate. + */ + @Required + public ValidateRequest query(byte[] querySource, int offset, int length, boolean unsafe) { + this.querySource = querySource; + this.querySourceOffset = offset; + this.querySourceLength = length; + this.querySourceUnsafe = unsafe; + return this; + } + + /** + * The types of documents the query will run against. Defaults to all types. + */ + String[] types() { + return this.types; + } + + /** + * The types of documents the query will run against. Defaults to all types. + */ + public ValidateRequest types(String... types) { + this.types = types; + return this; + } + + @Override + public void readFrom(StreamInput in) throws IOException { + super.readFrom(in); + + querySourceUnsafe = false; + querySourceOffset = 0; + querySourceLength = in.readVInt(); + querySource = new byte[querySourceLength]; + in.readFully(querySource); + + int typesSize = in.readVInt(); + if (typesSize > 0) { + types = new String[typesSize]; + for (int i = 0; i < typesSize; i++) { + types[i] = in.readUTF(); + } + } + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + super.writeTo(out); + + out.writeVInt(querySourceLength); + out.writeBytes(querySource, querySourceOffset, querySourceLength); + + out.writeVInt(types.length); + for (String type : types) { + out.writeUTF(type); + } + } + + @Override + public String toString() { + return "[" + Arrays.toString(indices) + "]" + Arrays.toString(types) + ", querySource[" + Unicode.fromBytes(querySource) + "]"; + } +} diff --git a/src/main/java/org/elasticsearch/action/validate/ValidateResponse.java b/src/main/java/org/elasticsearch/action/validate/ValidateResponse.java new file mode 100644 index 00000000000..590b7583100 --- /dev/null +++ b/src/main/java/org/elasticsearch/action/validate/ValidateResponse.java @@ -0,0 +1,73 @@ +/* + * 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.validate; + +import org.elasticsearch.action.ShardOperationFailedException; +import org.elasticsearch.action.support.broadcast.BroadcastOperationResponse; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; + +import java.io.IOException; +import java.util.List; + +/** + * The response of the validate action. + * + * + */ +public class ValidateResponse extends BroadcastOperationResponse { + + private boolean valid; + + ValidateResponse() { + + } + + ValidateResponse(boolean valid, int totalShards, int successfulShards, int failedShards, List shardFailures) { + super(totalShards, successfulShards, failedShards, shardFailures); + this.valid = valid; + } + + /** + * A boolean denoting whether the query is valid. + */ + public boolean valid() { + return valid; + } + + /** + * A boolean denoting whether the query is valid. + */ + public boolean getValid() { + return valid; + } + + @Override + public void readFrom(StreamInput in) throws IOException { + super.readFrom(in); + valid = in.readBoolean(); + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + super.writeTo(out); + out.writeBoolean(valid); + } +} diff --git a/src/main/java/org/elasticsearch/action/validate/pacakge-info.java b/src/main/java/org/elasticsearch/action/validate/pacakge-info.java new file mode 100644 index 00000000000..a6fcb8f9a49 --- /dev/null +++ b/src/main/java/org/elasticsearch/action/validate/pacakge-info.java @@ -0,0 +1,23 @@ +/* + * 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. + */ + +/** + * Validate action. + */ +package org.elasticsearch.action.validate; \ No newline at end of file diff --git a/src/main/java/org/elasticsearch/client/Client.java b/src/main/java/org/elasticsearch/client/Client.java index 18050ca8aa1..3af762d8e9a 100644 --- a/src/main/java/org/elasticsearch/client/Client.java +++ b/src/main/java/org/elasticsearch/client/Client.java @@ -41,6 +41,8 @@ import org.elasticsearch.action.percolate.PercolateResponse; import org.elasticsearch.action.search.SearchRequest; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.action.search.SearchScrollRequest; +import org.elasticsearch.action.validate.ValidateRequest; +import org.elasticsearch.action.validate.ValidateResponse; import org.elasticsearch.client.action.bulk.BulkRequestBuilder; import org.elasticsearch.client.action.count.CountRequestBuilder; import org.elasticsearch.client.action.delete.DeleteRequestBuilder; @@ -52,6 +54,7 @@ import org.elasticsearch.client.action.mlt.MoreLikeThisRequestBuilder; import org.elasticsearch.client.action.percolate.PercolateRequestBuilder; import org.elasticsearch.client.action.search.SearchRequestBuilder; import org.elasticsearch.client.action.search.SearchScrollRequestBuilder; +import org.elasticsearch.client.action.validate.ValidateRequestBuilder; import org.elasticsearch.common.Nullable; /** @@ -274,6 +277,29 @@ public interface Client { */ CountRequestBuilder prepareCount(String... indices); + /** + * A count of all the documents matching a specific query. + * + * @param request The count request + * @return The result future + * @see Requests#countRequest(String...) + */ + ActionFuture validate(ValidateRequest request); + + /** + * A count of all the documents matching a specific query. + * + * @param request The count request + * @param listener A listener to be notified of the result + * @see Requests#countRequest(String...) + */ + void validate(ValidateRequest request, ActionListener listener); + + /** + * A count of all the documents matching a specific query. + */ + ValidateRequestBuilder prepareValidate(String... indices); + /** * Search across one or more indices and one or more types with a query. * diff --git a/src/main/java/org/elasticsearch/client/action/validate/ValidateRequestBuilder.java b/src/main/java/org/elasticsearch/client/action/validate/ValidateRequestBuilder.java new file mode 100644 index 00000000000..57c2a7cdf43 --- /dev/null +++ b/src/main/java/org/elasticsearch/client/action/validate/ValidateRequestBuilder.java @@ -0,0 +1,75 @@ +package org.elasticsearch.client.action.validate; + +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.action.support.broadcast.BroadcastOperationThreading; +import org.elasticsearch.action.validate.ValidateRequest; +import org.elasticsearch.action.validate.ValidateResponse; +import org.elasticsearch.client.Client; +import org.elasticsearch.client.action.support.BaseRequestBuilder; +import org.elasticsearch.index.query.QueryBuilder; + +/** + * + */ +public class ValidateRequestBuilder extends BaseRequestBuilder { + public ValidateRequestBuilder(Client client) { + super(client, new ValidateRequest()); + } + + /** + * Sets the indices the query validation will run against. + */ + public ValidateRequestBuilder setIndices(String... indices) { + request.indices(indices); + return this; + } + + /** + * The types of documents the query will run against. Defaults to all types. + */ + public ValidateRequestBuilder setTypes(String... types) { + request.types(types); + return this; + } + + /** + * The query source to validate. + * + * @see org.elasticsearch.index.query.QueryBuilders + */ + public ValidateRequestBuilder setQuery(QueryBuilder queryBuilder) { + request.query(queryBuilder); + return this; + } + + /** + * The query source to validate. + * + * @see org.elasticsearch.index.query.QueryBuilders + */ + public ValidateRequestBuilder setQuery(byte[] querySource) { + request.query(querySource); + return this; + } + + /** + * Controls the operation threading model. + */ + public ValidateRequestBuilder setOperationThreading(BroadcastOperationThreading operationThreading) { + request.operationThreading(operationThreading); + return this; + } + + /** + * Should the listener be called on a separate thread if needed. + */ + public ValidateRequestBuilder setListenerThreaded(boolean threadedListener) { + request.listenerThreaded(threadedListener); + return this; + } + + @Override + protected void doExecute(ActionListener listener) { + client.validate(request, listener); + } +} diff --git a/src/main/java/org/elasticsearch/client/node/NodeClient.java b/src/main/java/org/elasticsearch/client/node/NodeClient.java index 74812963a95..9fb4acd4789 100644 --- a/src/main/java/org/elasticsearch/client/node/NodeClient.java +++ b/src/main/java/org/elasticsearch/client/node/NodeClient.java @@ -43,7 +43,11 @@ import org.elasticsearch.action.percolate.PercolateRequest; import org.elasticsearch.action.percolate.PercolateResponse; import org.elasticsearch.action.percolate.TransportPercolateAction; import org.elasticsearch.action.search.*; +import org.elasticsearch.action.validate.TransportValidateAction; +import org.elasticsearch.action.validate.ValidateRequest; +import org.elasticsearch.action.validate.ValidateResponse; import org.elasticsearch.client.AdminClient; +import org.elasticsearch.client.action.validate.ValidateRequestBuilder; import org.elasticsearch.client.internal.InternalClient; import org.elasticsearch.client.support.AbstractClient; import org.elasticsearch.common.inject.Inject; @@ -73,6 +77,8 @@ public class NodeClient extends AbstractClient implements InternalClient { private final TransportCountAction countAction; + private final TransportValidateAction validateAction; + private final TransportSearchAction searchAction; private final TransportSearchScrollAction searchScrollAction; @@ -85,7 +91,7 @@ public class NodeClient extends AbstractClient implements InternalClient { public NodeClient(Settings settings, ThreadPool threadPool, NodeAdminClient admin, TransportIndexAction indexAction, TransportDeleteAction deleteAction, TransportBulkAction bulkAction, TransportDeleteByQueryAction deleteByQueryAction, TransportGetAction getAction, TransportMultiGetAction multiGetAction, TransportCountAction countAction, - TransportSearchAction searchAction, TransportSearchScrollAction searchScrollAction, + TransportSearchAction searchAction, TransportValidateAction validateAction, TransportSearchScrollAction searchScrollAction, TransportMoreLikeThisAction moreLikeThisAction, TransportPercolateAction percolateAction) { this.threadPool = threadPool; this.admin = admin; @@ -96,6 +102,7 @@ public class NodeClient extends AbstractClient implements InternalClient { this.getAction = getAction; this.multiGetAction = multiGetAction; this.countAction = countAction; + this.validateAction = validateAction; this.searchAction = searchAction; this.searchScrollAction = searchScrollAction; this.moreLikeThisAction = moreLikeThisAction; @@ -187,6 +194,16 @@ public class NodeClient extends AbstractClient implements InternalClient { countAction.execute(request, listener); } + @Override + public ActionFuture validate(ValidateRequest request) { + return validateAction.execute(request); + } + + @Override + public void validate(ValidateRequest request, ActionListener listener) { + validateAction.execute(request, listener); + } + @Override public ActionFuture search(SearchRequest request) { return searchAction.execute(request); diff --git a/src/main/java/org/elasticsearch/client/support/AbstractClient.java b/src/main/java/org/elasticsearch/client/support/AbstractClient.java index 38340d8f36d..246b848e796 100644 --- a/src/main/java/org/elasticsearch/client/support/AbstractClient.java +++ b/src/main/java/org/elasticsearch/client/support/AbstractClient.java @@ -30,6 +30,7 @@ import org.elasticsearch.client.action.mlt.MoreLikeThisRequestBuilder; import org.elasticsearch.client.action.percolate.PercolateRequestBuilder; import org.elasticsearch.client.action.search.SearchRequestBuilder; import org.elasticsearch.client.action.search.SearchScrollRequestBuilder; +import org.elasticsearch.client.action.validate.ValidateRequestBuilder; import org.elasticsearch.client.internal.InternalClient; import org.elasticsearch.common.Nullable; @@ -103,6 +104,11 @@ public abstract class AbstractClient implements InternalClient { return new CountRequestBuilder(this).setIndices(indices); } + @Override + public ValidateRequestBuilder prepareValidate(String... indices) { + return new ValidateRequestBuilder(this).setIndices(indices); + } + @Override public MoreLikeThisRequestBuilder prepareMoreLikeThis(String index, String type, String id) { return new MoreLikeThisRequestBuilder(this, index, type, id); diff --git a/src/main/java/org/elasticsearch/client/transport/TransportClient.java b/src/main/java/org/elasticsearch/client/transport/TransportClient.java index 890ad1f3b38..569b255422f 100644 --- a/src/main/java/org/elasticsearch/client/transport/TransportClient.java +++ b/src/main/java/org/elasticsearch/client/transport/TransportClient.java @@ -43,6 +43,8 @@ import org.elasticsearch.action.percolate.PercolateResponse; import org.elasticsearch.action.search.SearchRequest; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.action.search.SearchScrollRequest; +import org.elasticsearch.action.validate.ValidateRequest; +import org.elasticsearch.action.validate.ValidateResponse; import org.elasticsearch.client.AdminClient; import org.elasticsearch.client.support.AbstractClient; import org.elasticsearch.client.transport.action.ClientTransportActionModule; @@ -319,6 +321,16 @@ public class TransportClient extends AbstractClient { internalClient.count(request, listener); } + @Override + public ActionFuture validate(ValidateRequest request) { + return internalClient.validate(request); + } + + @Override + public void validate(ValidateRequest request, ActionListener listener) { + internalClient.validate(request, listener); + } + @Override public ActionFuture search(SearchRequest request) { return internalClient.search(request); diff --git a/src/main/java/org/elasticsearch/client/transport/action/validate/ClientTransportValidateAction.java b/src/main/java/org/elasticsearch/client/transport/action/validate/ClientTransportValidateAction.java new file mode 100644 index 00000000000..d449a7e673e --- /dev/null +++ b/src/main/java/org/elasticsearch/client/transport/action/validate/ClientTransportValidateAction.java @@ -0,0 +1,25 @@ +package org.elasticsearch.client.transport.action.validate; + +import org.elasticsearch.action.TransportActions; +import org.elasticsearch.action.validate.ValidateRequest; +import org.elasticsearch.action.validate.ValidateResponse; +import org.elasticsearch.client.transport.action.support.BaseClientTransportAction; +import org.elasticsearch.common.inject.Inject; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.transport.TransportService; + +/** + * + */ +public class ClientTransportValidateAction extends BaseClientTransportAction { + + @Inject + public ClientTransportValidateAction(Settings settings, TransportService transportService) { + super(settings, transportService, ValidateResponse.class); + } + + @Override + protected String action() { + return TransportActions.VALIDATE; + } +} diff --git a/src/main/java/org/elasticsearch/client/transport/support/InternalTransportClient.java b/src/main/java/org/elasticsearch/client/transport/support/InternalTransportClient.java index 6f9370c4c61..96868f6c500 100644 --- a/src/main/java/org/elasticsearch/client/transport/support/InternalTransportClient.java +++ b/src/main/java/org/elasticsearch/client/transport/support/InternalTransportClient.java @@ -42,6 +42,8 @@ import org.elasticsearch.action.percolate.PercolateResponse; import org.elasticsearch.action.search.SearchRequest; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.action.search.SearchScrollRequest; +import org.elasticsearch.action.validate.ValidateRequest; +import org.elasticsearch.action.validate.ValidateResponse; import org.elasticsearch.client.AdminClient; import org.elasticsearch.client.internal.InternalClient; import org.elasticsearch.client.support.AbstractClient; @@ -57,6 +59,7 @@ import org.elasticsearch.client.transport.action.mlt.ClientTransportMoreLikeThis import org.elasticsearch.client.transport.action.percolate.ClientTransportPercolateAction; import org.elasticsearch.client.transport.action.search.ClientTransportSearchAction; import org.elasticsearch.client.transport.action.search.ClientTransportSearchScrollAction; +import org.elasticsearch.client.transport.action.validate.ClientTransportValidateAction; import org.elasticsearch.cluster.node.DiscoveryNode; import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.settings.Settings; @@ -87,6 +90,8 @@ public class InternalTransportClient extends AbstractClient implements InternalC private final ClientTransportCountAction countAction; + private final ClientTransportValidateAction validateAction; + private final ClientTransportSearchAction searchAction; private final ClientTransportSearchScrollAction searchScrollAction; @@ -99,7 +104,7 @@ public class InternalTransportClient extends AbstractClient implements InternalC public InternalTransportClient(Settings settings, ThreadPool threadPool, TransportClientNodesService nodesService, InternalTransportAdminClient adminClient, ClientTransportIndexAction indexAction, ClientTransportDeleteAction deleteAction, ClientTransportBulkAction bulkAction, ClientTransportGetAction getAction, ClientTransportMultiGetAction multiGetAction, - ClientTransportDeleteByQueryAction deleteByQueryAction, ClientTransportCountAction countAction, + ClientTransportDeleteByQueryAction deleteByQueryAction, ClientTransportCountAction countAction, ClientTransportValidateAction validateAction, ClientTransportSearchAction searchAction, ClientTransportSearchScrollAction searchScrollAction, ClientTransportMoreLikeThisAction moreLikeThisAction, ClientTransportPercolateAction percolateAction) { this.threadPool = threadPool; @@ -113,6 +118,7 @@ public class InternalTransportClient extends AbstractClient implements InternalC this.multiGetAction = multiGetAction; this.deleteByQueryAction = deleteByQueryAction; this.countAction = countAction; + this.validateAction = validateAction; this.searchAction = searchAction; this.searchScrollAction = searchScrollAction; this.moreLikeThisAction = moreLikeThisAction; @@ -274,6 +280,26 @@ public class InternalTransportClient extends AbstractClient implements InternalC }, listener); } + @Override + public ActionFuture validate(final ValidateRequest request) { + return nodesService.execute(new TransportClientNodesService.NodeCallback>() { + @Override + public ActionFuture doWithNode(DiscoveryNode node) throws ElasticSearchException { + return validateAction.execute(node, request); + } + }); + } + + @Override + public void validate(final ValidateRequest request, final ActionListener listener) { + nodesService.execute(new TransportClientNodesService.NodeListenerCallback() { + @Override + public void doWithNode(DiscoveryNode node, ActionListener listener) throws ElasticSearchException { + validateAction.execute(node, request, listener); + } + }, listener); + } + @Override public ActionFuture search(final SearchRequest request) { return nodesService.execute(new TransportClientNodesService.NodeCallback>() { diff --git a/src/main/java/org/elasticsearch/index/shard/service/IndexShard.java b/src/main/java/org/elasticsearch/index/shard/service/IndexShard.java index d39c6eb9fd1..c51b6dbe985 100644 --- a/src/main/java/org/elasticsearch/index/shard/service/IndexShard.java +++ b/src/main/java/org/elasticsearch/index/shard/service/IndexShard.java @@ -105,6 +105,10 @@ public interface IndexShard extends IndexShardComponent { Engine.Searcher searcher(); + boolean validate(byte[] querySource, @Nullable String[] filteringAliases, String... types) throws ElasticSearchException; + + boolean validate(byte[] querySource, int querySourceOffset, int querySourceLength, @Nullable String[] filteringAliases, String... types) throws ElasticSearchException; + /** * Returns true if this shard can ignore a recovery attempt made to it (since the already doing/done it) */ diff --git a/src/main/java/org/elasticsearch/index/shard/service/InternalIndexShard.java b/src/main/java/org/elasticsearch/index/shard/service/InternalIndexShard.java index 7a82e0d0673..9a180776d36 100644 --- a/src/main/java/org/elasticsearch/index/shard/service/InternalIndexShard.java +++ b/src/main/java/org/elasticsearch/index/shard/service/InternalIndexShard.java @@ -51,6 +51,7 @@ import org.elasticsearch.index.mapper.*; import org.elasticsearch.index.merge.MergeStats; import org.elasticsearch.index.merge.scheduler.MergeSchedulerProvider; import org.elasticsearch.index.query.IndexQueryParserService; +import org.elasticsearch.index.query.QueryParsingException; import org.elasticsearch.index.refresh.RefreshStats; import org.elasticsearch.index.search.stats.SearchStats; import org.elasticsearch.index.search.stats.ShardSearchService; @@ -518,6 +519,28 @@ public class InternalIndexShard extends AbstractIndexShardComponent implements I return engine.searcher(); } + @Override + public boolean validate(byte[] querySource, @Nullable String[] filteringAliases, String... types) throws ElasticSearchException { + return validate(querySource, 0, querySource.length, filteringAliases, types); + } + + @Override + public boolean validate(byte[] querySource, int querySourceOffset, int querySourceLength, @Nullable String[] filteringAliases, String... types) throws ElasticSearchException { + readAllowed(); + if (querySourceLength == 0) { + return true; + } else { + try { + queryParserService.parse(querySource, querySourceOffset, querySourceLength); + } catch (QueryParsingException e) { + return false; + } catch (AssertionError e) { + return false; + } + } + return true; + } + public void close(String reason) { synchronized (mutex) { indexSettingsService.removeListener(applyRefreshSettings); diff --git a/src/main/java/org/elasticsearch/rest/action/RestActionModule.java b/src/main/java/org/elasticsearch/rest/action/RestActionModule.java index d899a59ad89..0c10ef62803 100644 --- a/src/main/java/org/elasticsearch/rest/action/RestActionModule.java +++ b/src/main/java/org/elasticsearch/rest/action/RestActionModule.java @@ -70,6 +70,7 @@ import org.elasticsearch.rest.action.mlt.RestMoreLikeThisAction; import org.elasticsearch.rest.action.percolate.RestPercolateAction; import org.elasticsearch.rest.action.search.RestSearchAction; import org.elasticsearch.rest.action.search.RestSearchScrollAction; +import org.elasticsearch.rest.action.validate.RestValidateAction; import java.util.List; @@ -149,6 +150,8 @@ public class RestActionModule extends AbstractModule { bind(RestSearchAction.class).asEagerSingleton(); bind(RestSearchScrollAction.class).asEagerSingleton(); + + bind(RestValidateAction.class).asEagerSingleton(); bind(RestMoreLikeThisAction.class).asEagerSingleton(); diff --git a/src/main/java/org/elasticsearch/rest/action/validate/RestValidateAction.java b/src/main/java/org/elasticsearch/rest/action/validate/RestValidateAction.java new file mode 100644 index 00000000000..2bbac22329d --- /dev/null +++ b/src/main/java/org/elasticsearch/rest/action/validate/RestValidateAction.java @@ -0,0 +1,122 @@ +/* + * 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.rest.action.validate; + +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.action.support.broadcast.BroadcastOperationThreading; +import org.elasticsearch.action.validate.ValidateRequest; +import org.elasticsearch.action.validate.ValidateResponse; +import org.elasticsearch.client.Client; +import org.elasticsearch.common.inject.Inject; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.rest.*; +import org.elasticsearch.rest.action.support.RestActions; +import org.elasticsearch.rest.action.support.RestXContentBuilder; + +import java.io.IOException; + +import static org.elasticsearch.rest.RestRequest.Method.GET; +import static org.elasticsearch.rest.RestRequest.Method.POST; +import static org.elasticsearch.rest.RestStatus.BAD_REQUEST; +import static org.elasticsearch.rest.RestStatus.OK; +import static org.elasticsearch.rest.action.support.RestActions.buildBroadcastShardsHeader; +import static org.elasticsearch.rest.action.support.RestActions.splitTypes; + +/** + * + */ +public class RestValidateAction extends BaseRestHandler { + + @Inject + public RestValidateAction(Settings settings, Client client, RestController controller) { + super(settings, client); + controller.registerHandler(GET, "/_validate", this); + controller.registerHandler(POST, "/_validate", this); + controller.registerHandler(GET, "/{index}/_validate", this); + controller.registerHandler(POST, "/{index}/_validate", this); + controller.registerHandler(GET, "/{index}/{type}/_validate", this); + controller.registerHandler(POST, "/{index}/{type}/_validate", this); + } + + @Override + public void handleRequest(final RestRequest request, final RestChannel channel) { + ValidateRequest validateRequest = new ValidateRequest(RestActions.splitIndices(request.param("index"))); + // we just send back a response, no need to fork a listener + validateRequest.listenerThreaded(false); + try { + BroadcastOperationThreading operationThreading = BroadcastOperationThreading.fromString(request.param("operation_threading"), BroadcastOperationThreading.SINGLE_THREAD); + if (operationThreading == BroadcastOperationThreading.NO_THREADS) { + // since we don't spawn, don't allow no_threads, but change it to a single thread + operationThreading = BroadcastOperationThreading.SINGLE_THREAD; + } + validateRequest.operationThreading(operationThreading); + if (request.hasContent()) { + validateRequest.query(request.contentByteArray(), request.contentByteArrayOffset(), request.contentLength(), true); + } else { + String source = request.param("source"); + if (source != null) { + validateRequest.query(source); + } else { + byte[] querySource = RestActions.parseQuerySource(request); + if (querySource != null) { + validateRequest.query(querySource); + } + } + } + validateRequest.types(splitTypes(request.param("type"))); + } catch (Exception e) { + try { + XContentBuilder builder = RestXContentBuilder.restContentBuilder(request); + channel.sendResponse(new XContentRestResponse(request, BAD_REQUEST, builder.startObject().field("error", e.getMessage()).endObject())); + } catch (IOException e1) { + logger.error("Failed to send failure response", e1); + } + return; + } + + client.validate(validateRequest, new ActionListener() { + @Override + public void onResponse(ValidateResponse response) { + try { + XContentBuilder builder = RestXContentBuilder.restContentBuilder(request); + builder.startObject(); + builder.field("valid", response.valid()); + + buildBroadcastShardsHeader(builder, response); + + builder.endObject(); + channel.sendResponse(new XContentRestResponse(request, OK, builder)); + } catch (Exception e) { + onFailure(e); + } + } + + @Override + public void onFailure(Throwable e) { + try { + channel.sendResponse(new XContentThrowableRestResponse(request, e)); + } catch (IOException e1) { + logger.error("Failed to send failure response", e1); + } + } + }); + } +} diff --git a/src/test/java/org/elasticsearch/test/integration/validate/SimpleValidateTests.java b/src/test/java/org/elasticsearch/test/integration/validate/SimpleValidateTests.java new file mode 100644 index 00000000000..b117f3abcfb --- /dev/null +++ b/src/test/java/org/elasticsearch/test/integration/validate/SimpleValidateTests.java @@ -0,0 +1,84 @@ +/* + * Licensed to Elastic Search and Shay Banon under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. Elastic Search 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.test.integration.validate; + +import org.elasticsearch.client.Client; +import org.elasticsearch.common.settings.ImmutableSettings; +import org.elasticsearch.common.xcontent.XContentFactory; +import org.elasticsearch.index.query.QueryBuilders; +import org.elasticsearch.test.integration.AbstractNodesTests; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; + +/** + * + */ +public class SimpleValidateTests extends AbstractNodesTests { + + private Client client; + + @BeforeClass + public void createNodes() throws Exception { + startNode("node1"); + startNode("node2"); + client = getClient(); + } + + @AfterClass + public void closeNodes() { + client.close(); + closeAllNodes(); + } + + protected Client getClient() { + return client("node1"); + } + + @Test + public void simpleValidateQuery() 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() + .endObject().endObject().endObject()) + .execute().actionGet(); + + client.admin().indices().prepareRefresh().execute().actionGet(); + + assertThat(client.prepareValidate("test").setQuery("foo".getBytes()).execute().actionGet().valid(), equalTo(false)); + assertThat(client.prepareValidate("test").setQuery(QueryBuilders.queryString("_id:1")).execute().actionGet().valid(), equalTo(true)); + assertThat(client.prepareValidate("test").setQuery(QueryBuilders.queryString("_i:d:1")).execute().actionGet().valid(), equalTo(false)); + + assertThat(client.prepareValidate("test").setQuery(QueryBuilders.queryString("foo:1")).execute().actionGet().valid(), equalTo(true)); + assertThat(client.prepareValidate("test").setQuery(QueryBuilders.queryString("bar:hey")).execute().actionGet().valid(), equalTo(false)); + + assertThat(client.prepareValidate("test").setQuery(QueryBuilders.queryString("nonexistent:hello")).execute().actionGet().valid(), equalTo(true)); + + assertThat(client.prepareValidate("test").setQuery(QueryBuilders.queryString("foo:1 AND")).execute().actionGet().valid(), equalTo(false)); + } +}