Skip to content

Commit

Permalink
Indices query/filter skip parsing altogether for irrelevant indices w…
Browse files Browse the repository at this point in the history
…hen possible

Closes elastic#2416
  • Loading branch information
Olivier Favre authored and javanna committed Nov 14, 2013
1 parent 3650c34 commit c9daa01
Show file tree
Hide file tree
Showing 7 changed files with 364 additions and 34 deletions.
38 changes: 38 additions & 0 deletions docs/reference/query-dsl/filters/indices-filter.asciidoc
@@ -0,0 +1,38 @@
[[query-dsl-indices-filter]]
=== Indices Filter

The `indices` filter can be used when executed across multiple indices,
allowing to have a filter that executes only when executed on an index
that matches a specific list of indices, and another filter that executes
when it is executed on an index that does not match the listed indices.

[source,js]
--------------------------------------------------
{
"indices" : {
"indices" : ["index1", "index2"],
"filter" : {
"term" : { "tag" : "wow" }
},
"no_match_filter" : {
"term" : { "tag" : "kow" }
}
}
}
--------------------------------------------------

You can use the `index` field to provide a single index.

`no_match_filter` can also have "string" value of `none` (to match no
documents), and `all` (to match all). Defaults to `all`.

`filter` is mandatory. You must provide the indices.
It is forbidden to omit or to give `indices` or `index` multiple times,
or to give both.

Please note that the fields order is important: If the indices are
provided before `filter` or `no_match_filter`, the filter parsing is
skipped altogether.
For instance, this feature is useful to prevent a query that runs
against multiple indices to fail because of a missing type.
See `has_child`, `has_parent`, `top_children` and `nested`.
15 changes: 14 additions & 1 deletion docs/reference/query-dsl/queries/indices-query.asciidoc
Expand Up @@ -21,5 +21,18 @@ when it is executed on an index that does not match the listed indices.
}
--------------------------------------------------

You can use the `index` field to provide a single index.

`no_match_query` can also have "string" value of `none` (to match no
documents), and `all` (to match all).
documents), and `all` (to match all). Defaults to `all`.

`query` is mandatory. You must provide the indices.
It is forbidden to omit or to give `indices` or `index` multiple times,
or to give both.

Please note that the fields order is important: If the indices are
provided before `query` or `no_match_query`, the query parsing is
skipped altogether.
For instance, this feature is useful to prevent a query that runs
against multiple indices to fail because of a missing type.
See `has_child`, `has_parent`, `top_children` and `nested`.
Expand Up @@ -70,9 +70,9 @@ public IndicesFilterBuilder filterName(String filterName) {
@Override
protected void doXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(IndicesFilterParser.NAME);
builder.field("indices", indices);
builder.field("filter");
filterBuilder.toXContent(builder, params);
builder.field("indices", indices);
if (noMatchFilter != null) {
builder.field("no_match_filter");
noMatchFilter.toXContent(builder, params);
Expand Down
Expand Up @@ -19,7 +19,6 @@

package org.elasticsearch.index.query;

import com.google.common.collect.Sets;
import org.apache.lucene.search.Filter;
import org.elasticsearch.action.support.IgnoreIndices;
import org.elasticsearch.cluster.ClusterService;
Expand All @@ -31,7 +30,9 @@
import org.elasticsearch.common.xcontent.XContentParser;

import java.io.IOException;
import java.util.Set;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;

/**
*/
Expand All @@ -57,46 +58,85 @@ public Filter parse(QueryParseContext parseContext) throws IOException, QueryPar
XContentParser parser = parseContext.parser();

Filter filter = null;
Filter noMatchFilter = Queries.MATCH_ALL_FILTER;
Filter chosenFilter = null;
boolean filterFound = false;
Set<String> indices = Sets.newHashSet();
boolean indicesFound = false;
boolean matchesConcreteIndices = false;

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)) {
filterFound = true;
filter = parseContext.parseInnerFilter();
if (indicesFound) {
// Because we know the indices, we can either skip, or parse and use the query
if (matchesConcreteIndices) {
filter = parseContext.parseInnerFilter();
chosenFilter = filter;
} else {
parseContext.parser().skipChildren(); // skip the filter object without parsing it into a Filter
}
} else {
// We do not know the indices, we must parse the query
filter = parseContext.parseInnerFilter();
}
} else if ("no_match_filter".equals(currentFieldName)) {
noMatchFilter = parseContext.parseInnerFilter();
if (indicesFound) {
// Because we know the indices, we can either skip, or parse and use the query
if (!matchesConcreteIndices) {
noMatchFilter = parseContext.parseInnerFilter();
chosenFilter = noMatchFilter;
} else {
parseContext.parser().skipChildren(); // skip the filter object without parsing it into a Filter
}
} else {
// We do not know the indices, we must parse the query
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)) {
if (indicesFound) {
throw new QueryParsingException(parseContext.index(), "[indices] indices already specified");
}
indicesFound = true;
Collection<String> indices = new ArrayList<String>();
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);
}
matchesConcreteIndices = matchesIndices(parseContext, getConcreteIndices(indices));
} else {
throw new QueryParsingException(parseContext.index(), "[indices] filter does not support [" + currentFieldName + "]");
}
} else if (token.isValue()) {
if ("index".equals(currentFieldName)) {
indices.add(parser.text());
if (indicesFound) {
throw new QueryParsingException(parseContext.index(), "[indices] indices already specified");
}
indicesFound = true;
matchesConcreteIndices = matchesIndices(parseContext, getConcreteIndices(Arrays.asList(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;
}
if (indicesFound) {
if (!matchesConcreteIndices) {
chosenFilter = noMatchFilter;
}
}
} else {
throw new QueryParsingException(parseContext.index(), "[indices] filter does not support [" + currentFieldName + "]");
}
Expand All @@ -105,25 +145,38 @@ public Filter parse(QueryParseContext parseContext) throws IOException, QueryPar
if (!filterFound) {
throw new QueryParsingException(parseContext.index(), "[indices] requires 'filter' element");
}
if (indices.isEmpty()) {
if (!indicesFound) {
throw new QueryParsingException(parseContext.index(), "[indices] requires 'indices' element");
}

if (filter == null) {
return null;
if (chosenFilter == null) {
// Indices were not provided before we encountered the queries, which we hence parsed
// We must now make a choice
if (matchesConcreteIndices) {
chosenFilter = filter;
} else {
chosenFilter = noMatchFilter;
}
}

return chosenFilter;
}

protected String[] getConcreteIndices(Collection<String> indices) {
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()]), IgnoreIndices.MISSING, true);
}
return concreteIndices;
}

protected boolean matchesIndices(QueryParseContext parseContext, String[] concreteIndices) {
for (String index : concreteIndices) {
if (Regex.simpleMatch(index, parseContext.index().name())) {
return filter;
return true;
}
}
return noMatchFilter;
return false;
}
}
Expand Up @@ -70,9 +70,9 @@ public IndicesQueryBuilder queryName(String queryName) {
@Override
protected void doXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(IndicesQueryParser.NAME);
builder.field("indices", indices);
builder.field("query");
queryBuilder.toXContent(builder, params);
builder.field("indices", indices);
if (noMatchQuery != null) {
builder.field("no_match_query");
noMatchQuery.toXContent(builder, params);
Expand Down

0 comments on commit c9daa01

Please sign in to comment.