From 6046e7c6eb2328f34d978bff9bb52c0839ef18db Mon Sep 17 00:00:00 2001 From: Shay Banon Date: Sat, 17 Mar 2012 13:24:15 +0200 Subject: [PATCH] Query DSL: indices filter type, closes #1787. --- .../lucene/search/MatchNoDocsFilter.java | 63 +++++++++ .../common/lucene/search/Queries.java | 1 + .../index/query/FilterBuilders.java | 4 + .../index/query/IndicesFilterBuilder.java | 89 +++++++++++++ .../index/query/IndicesFilterParser.java | 122 ++++++++++++++++++ .../indices/query/IndicesQueriesRegistry.java | 1 + 6 files changed, 280 insertions(+) create mode 100644 src/main/java/org/elasticsearch/common/lucene/search/MatchNoDocsFilter.java create mode 100644 src/main/java/org/elasticsearch/index/query/IndicesFilterBuilder.java create mode 100644 src/main/java/org/elasticsearch/index/query/IndicesFilterParser.java diff --git a/src/main/java/org/elasticsearch/common/lucene/search/MatchNoDocsFilter.java b/src/main/java/org/elasticsearch/common/lucene/search/MatchNoDocsFilter.java new file mode 100644 index 00000000000..8c80431c973 --- /dev/null +++ b/src/main/java/org/elasticsearch/common/lucene/search/MatchNoDocsFilter.java @@ -0,0 +1,63 @@ +/* + * 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.common.lucene.search; + +import org.apache.lucene.index.IndexReader; +import org.apache.lucene.search.DocIdSet; +import org.apache.lucene.search.Filter; + +import java.io.IOException; + +/** + * A filter that matches no docs. + */ +public class MatchNoDocsFilter extends Filter { + + @Override + public DocIdSet getDocIdSet(IndexReader reader) throws IOException { + return null; + } + + @Override + public int hashCode() { + return this.getClass().hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + + if (obj == null) { + return false; + } + + if (obj.getClass() == this.getClass()) { + return true; + } + + return false; + } + + @Override + public String toString() { + return "MatchNoDocsFilter"; + } +} diff --git a/src/main/java/org/elasticsearch/common/lucene/search/Queries.java b/src/main/java/org/elasticsearch/common/lucene/search/Queries.java index 2272cab8cde..e964d085838 100644 --- a/src/main/java/org/elasticsearch/common/lucene/search/Queries.java +++ b/src/main/java/org/elasticsearch/common/lucene/search/Queries.java @@ -38,6 +38,7 @@ public class Queries { * A match all docs filter. Note, requires no caching!. */ public final static Filter MATCH_ALL_FILTER = new MatchAllDocsFilter(); + public final static Filter MATCH_NO_FILTER = new MatchNoDocsFilter(); private final static Field disjuncts; diff --git a/src/main/java/org/elasticsearch/index/query/FilterBuilders.java b/src/main/java/org/elasticsearch/index/query/FilterBuilders.java index 724d9c6d8ba..71d41d04dd4 100644 --- a/src/main/java/org/elasticsearch/index/query/FilterBuilders.java +++ b/src/main/java/org/elasticsearch/index/query/FilterBuilders.java @@ -383,6 +383,10 @@ public abstract class FilterBuilders { return new NotFilterBuilder(filter); } + public static IndicesFilterBuilder indicesFilter(FilterBuilder filter, String... indices) { + return new IndicesFilterBuilder(filter, indices); + } + private FilterBuilders() { } diff --git a/src/main/java/org/elasticsearch/index/query/IndicesFilterBuilder.java b/src/main/java/org/elasticsearch/index/query/IndicesFilterBuilder.java new file mode 100644 index 00000000000..30ba5c1a863 --- /dev/null +++ b/src/main/java/org/elasticsearch/index/query/IndicesFilterBuilder.java @@ -0,0 +1,89 @@ +/* + * 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.index.query; + +import org.elasticsearch.common.xcontent.XContentBuilder; + +import java.io.IOException; + +/** + * A filter that will execute the wrapped filter only for the specified indices, and "match_all" when + * it does not match those indices (by default). + */ +public class IndicesFilterBuilder extends BaseFilterBuilder { + + private final FilterBuilder filterBuilder; + + private final String[] indices; + + private String sNoMatchFilter; + private FilterBuilder noMatchFilter; + + private String filterName; + + public IndicesFilterBuilder(FilterBuilder filterBuilder, String... indices) { + this.filterBuilder = filterBuilder; + this.indices = indices; + } + + /** + * Sets the no match filter, can either be all or none. + */ + public IndicesFilterBuilder noMatchFilter(String type) { + this.sNoMatchFilter = type; + return this; + } + + /** + * Sets the filter to use when it executes on an index that does not match the indices provided. + */ + public IndicesFilterBuilder noMatchFilter(FilterBuilder noMatchFilter) { + this.noMatchFilter = noMatchFilter; + return this; + } + + /** + * Sets the filter name for the filter that can be used when searching for matched_filters per hit. + */ + public IndicesFilterBuilder filterName(String filterName) { + this.filterName = filterName; + return this; + } + + @Override + protected void doXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(IndicesFilterParser.NAME); + builder.field("filter"); + filterBuilder.toXContent(builder, params); + builder.field("indices", indices); + if (noMatchFilter != null) { + builder.field("no_match_filter"); + noMatchFilter.toXContent(builder, params); + } else if (sNoMatchFilter != null) { + builder.field("no_match_filter", sNoMatchFilter); + } + + if (filterName != null) { + builder.field("_name", filterName); + } + + builder.endObject(); + } +} \ No newline at end of file diff --git a/src/main/java/org/elasticsearch/index/query/IndicesFilterParser.java b/src/main/java/org/elasticsearch/index/query/IndicesFilterParser.java new file mode 100644 index 00000000000..409304bd82a --- /dev/null +++ b/src/main/java/org/elasticsearch/index/query/IndicesFilterParser.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.index.query; + +import com.google.common.collect.Sets; +import org.apache.lucene.search.Filter; +import org.elasticsearch.cluster.ClusterService; +import org.elasticsearch.cluster.metadata.MetaData; +import org.elasticsearch.common.Nullable; +import org.elasticsearch.common.inject.Inject; +import org.elasticsearch.common.lucene.search.Queries; +import org.elasticsearch.common.regex.Regex; +import org.elasticsearch.common.xcontent.XContentParser; + +import java.io.IOException; +import java.util.Set; + +/** + */ +public class IndicesFilterParser implements FilterParser { + + public static final String NAME = "indices"; + + @Nullable + private final ClusterService clusterService; + + @Inject + public IndicesFilterParser(@Nullable ClusterService clusterService) { + this.clusterService = clusterService; + } + + @Override + public String[] names() { + return new String[]{NAME}; + } + + @Override + public Filter parse(QueryParseContext parseContext) throws IOException, QueryParsingException { + XContentParser parser = parseContext.parser(); + + Filter filter = null; + Set indices = Sets.newHashSet(); + + String currentFieldName = null; + XContentParser.Token token; + Filter noMatchFilter = Queries.MATCH_ALL_FILTER; + while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { + if (token == XContentParser.Token.FIELD_NAME) { + currentFieldName = parser.currentName(); + } else if (token == XContentParser.Token.START_OBJECT) { + if ("filter".equals(currentFieldName)) { + filter = parseContext.parseInnerFilter(); + } else if ("no_match_filter".equals(currentFieldName)) { + noMatchFilter = parseContext.parseInnerFilter(); + } else { + throw new QueryParsingException(parseContext.index(), "[indices] filter does not support [" + currentFieldName + "]"); + } + } else if (token == XContentParser.Token.START_ARRAY) { + if ("indices".equals(currentFieldName)) { + while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) { + String value = parser.textOrNull(); + if (value == null) { + throw new QueryParsingException(parseContext.index(), "No value specified for term filter"); + } + indices.add(value); + } + } else { + throw new QueryParsingException(parseContext.index(), "[indices] filter does not support [" + currentFieldName + "]"); + } + } else if (token.isValue()) { + if ("index".equals(currentFieldName)) { + indices.add(parser.text()); + } else if ("no_match_filter".equals(currentFieldName)) { + String type = parser.text(); + if ("all".equals(type)) { + noMatchFilter = Queries.MATCH_ALL_FILTER; + } else if ("none".equals(type)) { + noMatchFilter = Queries.MATCH_NO_FILTER; + } + } else { + throw new QueryParsingException(parseContext.index(), "[indices] filter does not support [" + currentFieldName + "]"); + } + } + } + if (filter == null) { + throw new QueryParsingException(parseContext.index(), "[indices] requires 'filter' element"); + } + if (indices.isEmpty()) { + throw new QueryParsingException(parseContext.index(), "[indices] requires 'indices' element"); + } + + String[] concreteIndices = indices.toArray(new String[indices.size()]); + if (clusterService != null) { + MetaData metaData = clusterService.state().metaData(); + concreteIndices = metaData.concreteIndices(indices.toArray(new String[indices.size()]), true, true); + } + + for (String index : concreteIndices) { + if (Regex.simpleMatch(index, parseContext.index().name())) { + return filter; + } + } + return noMatchFilter; + } +} diff --git a/src/main/java/org/elasticsearch/indices/query/IndicesQueriesRegistry.java b/src/main/java/org/elasticsearch/indices/query/IndicesQueriesRegistry.java index c9a6dc62da5..852ab2c771f 100644 --- a/src/main/java/org/elasticsearch/indices/query/IndicesQueriesRegistry.java +++ b/src/main/java/org/elasticsearch/indices/query/IndicesQueriesRegistry.java @@ -100,6 +100,7 @@ public class IndicesQueriesRegistry { addFilterParser(filterParsers, new MatchAllFilterParser()); addFilterParser(filterParsers, new ExistsFilterParser()); addFilterParser(filterParsers, new MissingFilterParser()); + addFilterParser(filterParsers, new IndicesFilterParser(clusterService)); this.filterParsers = ImmutableMap.copyOf(filterParsers); }