Add query validation feature

This commit is contained in:
Lee Hinman 2011-12-22 16:16:18 -07:00
parent 73b74847aa
commit be6e18cb36
19 changed files with 1099 additions and 2 deletions

View File

@ -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";

View File

@ -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);
}
}
}

View File

@ -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);
}
}

View File

@ -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<ValidateRequest, ValidateResponse, ShardValidateRequest, ShardValidateResponse> {
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<String, Set<String>> 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<ShardOperationFailedException> 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);
}
}

View File

@ -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.
* <p/>
* <p>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) + "]";
}
}

View File

@ -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<ShardOperationFailedException> 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);
}
}

View File

@ -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;

View File

@ -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<ValidateResponse> 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<ValidateResponse> 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.
*

View File

@ -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<ValidateRequest, ValidateResponse> {
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<ValidateResponse> listener) {
client.validate(request, listener);
}
}

View File

@ -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<ValidateResponse> validate(ValidateRequest request) {
return validateAction.execute(request);
}
@Override
public void validate(ValidateRequest request, ActionListener<ValidateResponse> listener) {
validateAction.execute(request, listener);
}
@Override
public ActionFuture<SearchResponse> search(SearchRequest request) {
return searchAction.execute(request);

View File

@ -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);

View File

@ -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<ValidateResponse> validate(ValidateRequest request) {
return internalClient.validate(request);
}
@Override
public void validate(ValidateRequest request, ActionListener<ValidateResponse> listener) {
internalClient.validate(request, listener);
}
@Override
public ActionFuture<SearchResponse> search(SearchRequest request) {
return internalClient.search(request);

View File

@ -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<ValidateRequest, ValidateResponse> {
@Inject
public ClientTransportValidateAction(Settings settings, TransportService transportService) {
super(settings, transportService, ValidateResponse.class);
}
@Override
protected String action() {
return TransportActions.VALIDATE;
}
}

View File

@ -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<ValidateResponse> validate(final ValidateRequest request) {
return nodesService.execute(new TransportClientNodesService.NodeCallback<ActionFuture<ValidateResponse>>() {
@Override
public ActionFuture<ValidateResponse> doWithNode(DiscoveryNode node) throws ElasticSearchException {
return validateAction.execute(node, request);
}
});
}
@Override
public void validate(final ValidateRequest request, final ActionListener<ValidateResponse> listener) {
nodesService.execute(new TransportClientNodesService.NodeListenerCallback<ValidateResponse>() {
@Override
public void doWithNode(DiscoveryNode node, ActionListener<ValidateResponse> listener) throws ElasticSearchException {
validateAction.execute(node, request, listener);
}
}, listener);
}
@Override
public ActionFuture<SearchResponse> search(final SearchRequest request) {
return nodesService.execute(new TransportClientNodesService.NodeCallback<ActionFuture<SearchResponse>>() {

View File

@ -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 <tt>true</tt> if this shard can ignore a recovery attempt made to it (since the already doing/done it)
*/

View File

@ -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);

View File

@ -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();

View File

@ -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<ValidateResponse>() {
@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);
}
}
});
}
}

View File

@ -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));
}
}