Added execution option to `range` filter, with the `index` and `fielddata` as values.

Deprecated `numeric_range` filter in favor for the `range` filter with `fielddata` as execution.

Closes #4034
This commit is contained in:
Martijn van Groningen 2013-11-25 15:11:38 +01:00
parent ac03fba9d3
commit a03556daa0
10 changed files with 117 additions and 17 deletions

View File

@ -1,6 +1,8 @@
[[query-dsl-numeric-range-filter]]
=== Numeric Range Filter
deprecated[0.90.8,Use the <<query-dsl-range-filter,range filter>> with the `fielddata` execution mode instead]
Filters documents with fields that have values within a certain numeric
range. Similar to
<<query-dsl-range-filter,range filter>>, except

View File

@ -32,8 +32,25 @@ The `range` filter accepts the following parameters:
deprecated[0.90.4,The `from`, `to`, `include_lower` and `include_upper` parameters have been deprecated in favour of `gt`,`gte`,`lt`,`lte`]
[float]
==== Execution
added[0.90.8]
The `execution` option controls how the range filter internally executes. The `execution` option accepts the following values:
[horizontal]
`index`:: Uses field's inverted in order to determine of documents fall with in the range filter's from and to range
`fielddata`:: Uses field data in order to determine of documents fall with in the range filter's from and to range.
In general for small ranges the `index` execution is faster and for longer ranges the `fielddata` execution is faster.
The `fielddata` execution as the same suggests uses field data and therefor requires more memory, so make you have
sufficient memory on your nodes in order to use this execution mode. It usually makes sense to use it on fields you're
already faceting or sorting by.
[float]
==== Caching
The result of the filter is automatically cached by default. The
The result of the filter is only automatically cached by default if the `execution` is set to `index`. The
`_cache` can be set to `false` to turn it off.

View File

@ -300,7 +300,10 @@ public abstract class FilterBuilders {
* field data cache (loading all the values for the specified field into memory)
*
* @param name The field name
* @deprecated The numeric_range filter will be removed at some point in time in favor for the range filter with
* the execution mode <code>fielddata</code>.
*/
@Deprecated
public static NumericRangeFilterBuilder numericRangeFilter(String name) {
return new NumericRangeFilterBuilder(name);
}

View File

@ -28,8 +28,10 @@ import java.io.IOException;
* <p/>
* <p>Uses the field data cache (loading all the values for the specified field into memory).
*
*
* @deprecated This filter will be removed at some point in time in favor for the range filter with the execution
* mode <code>fielddata</code>.
*/
@Deprecated
public class NumericRangeFilterBuilder extends BaseFilterBuilder {
private final String name;

View File

@ -34,6 +34,7 @@ import static org.elasticsearch.index.query.support.QueryParsers.wrapSmartNameFi
/**
*
*/
@Deprecated
public class NumericRangeFilterParser implements FilterParser {
public static final String NAME = "numeric_range";

View File

@ -45,6 +45,8 @@ public class RangeFilterBuilder extends BaseFilterBuilder {
private String filterName;
private String execution;
/**
* A filter that restricts search results to values that are within the given range.
*
@ -351,6 +353,24 @@ public class RangeFilterBuilder extends BaseFilterBuilder {
return this;
}
/**
* Sets the execution mode that controls how the range filter is executed. Valid values are: "index" and "fielddata".
* <ol>
* <li> The <code>index</code> execution uses the field's inverted in order to determine of documents fall with in
* the range filter's from and to range.
* <li> The <code>fielddata</code> execution uses field data in order to determine of documents fall with in the
* range filter's from and to range. Since field data is an in memory data structure, you need to have
* sufficient memory on your nodes in order to use this execution mode.
* </ol>
*
* In general for small ranges the <code>index</code> execution is faster and for longer ranges the
* <code>fielddata</code> execution is faster.
*/
public RangeFilterBuilder setExecution(String execution) {
this.execution = execution;
return this;
}
@Override
protected void doXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(RangeFilterParser.NAME);
@ -371,6 +391,9 @@ public class RangeFilterBuilder extends BaseFilterBuilder {
if (cacheKey != null) {
builder.field("_cache_key", cacheKey);
}
if (execution != null) {
builder.field("execution", execution);
}
builder.endObject();
}

View File

@ -25,7 +25,9 @@ import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.lucene.BytesRefs;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.cache.filter.support.CacheKeyFilter;
import org.elasticsearch.index.mapper.FieldMapper;
import org.elasticsearch.index.mapper.MapperService;
import org.elasticsearch.index.mapper.core.NumberFieldMapper;
import java.io.IOException;
@ -51,13 +53,14 @@ public class RangeFilterParser implements FilterParser {
public Filter parse(QueryParseContext parseContext) throws IOException, QueryParsingException {
XContentParser parser = parseContext.parser();
boolean cache = true;
Boolean cache = null;
CacheKeyFilter.Key cacheKey = null;
String fieldName = null;
Object from = null;
Object to = null;
boolean includeLower = true;
boolean includeUpper = true;
String execution = "index";
String filterName = null;
String currentFieldName = null;
@ -103,6 +106,8 @@ public class RangeFilterParser implements FilterParser {
cache = parser.booleanValue();
} else if ("_cache_key".equals(currentFieldName) || "_cacheKey".equals(currentFieldName)) {
cacheKey = new CacheKeyFilter.Key(parser.text());
} else if ("execution".equals(currentFieldName)) {
execution = parser.text();
} else {
throw new QueryParsingException(parseContext.index(), "[range] filter does not support [" + currentFieldName + "]");
}
@ -110,18 +115,37 @@ public class RangeFilterParser implements FilterParser {
}
if (fieldName == null) {
throw new QueryParsingException(parseContext.index(), "No field specified for range filter");
throw new QueryParsingException(parseContext.index(), "[range] filter no field specified for range filter");
}
Filter filter = null;
MapperService.SmartNameFieldMappers smartNameFieldMappers = parseContext.smartFieldMappers(fieldName);
if (smartNameFieldMappers != null) {
if (smartNameFieldMappers.hasMapper()) {
filter = smartNameFieldMappers.mapper().rangeFilter(from, to, includeLower, includeUpper, parseContext);
if (execution.equals("index")) {
if (cache == null) {
cache = true;
}
filter = smartNameFieldMappers.mapper().rangeFilter(from, to, includeLower, includeUpper, parseContext);
} else if ("fielddata".equals(execution)) {
if (cache == null) {
cache = false;
}
FieldMapper mapper = smartNameFieldMappers.mapper();
if (!(mapper instanceof NumberFieldMapper)) {
throw new QueryParsingException(parseContext.index(), "[range] filter field [" + fieldName + "] is not a numeric type");
}
filter = ((NumberFieldMapper) mapper).rangeFilter(parseContext.fieldData(), from, to, includeLower, includeUpper, parseContext);
} else {
throw new QueryParsingException(parseContext.index(), "[range] filter doesn't support [" + execution + "] execution");
}
}
}
if (filter == null) {
if (cache == null) {
cache = true;
}
filter = new TermRangeFilter(fieldName, BytesRefs.toBytesRef(from), BytesRefs.toBytesRef(to), includeLower, includeUpper);
}

View File

@ -1031,16 +1031,16 @@ public class SimpleQueryTests extends ElasticsearchIntegrationTest {
client().admin().indices().prepareRefresh().execute().actionGet();
CountResponse response = client().prepareCount("test").setQuery(
filteredQuery(matchAllQuery(), FilterBuilders.boolFilter()
.should(FilterBuilders.rangeFilter("num_long").from(1).to(2))
.should(FilterBuilders.rangeFilter("num_long").from(3).to(4)))
.should(rangeFilter("num_long", 1, 2))
.should(rangeFilter("num_long", 3, 4)))
).execute().actionGet();
assertHitCount(response, 4l);
// This made 2826 fail! (only with bit based filters)
response = client().prepareCount("test").setQuery(
filteredQuery(matchAllQuery(), FilterBuilders.boolFilter()
.should(FilterBuilders.numericRangeFilter("num_long").from(1).to(2))
.should(FilterBuilders.numericRangeFilter("num_long").from(3).to(4)))
.should(rangeFilter("num_long", 1, 2))
.should(rangeFilter("num_long", 3, 4)))
).execute().actionGet();
assertHitCount(response, 4l);
@ -1049,8 +1049,8 @@ public class SimpleQueryTests extends ElasticsearchIntegrationTest {
response = client().prepareCount("test").setQuery(
filteredQuery(matchAllQuery(), FilterBuilders.boolFilter()
.must(FilterBuilders.termFilter("field1", "test1"))
.should(FilterBuilders.rangeFilter("num_long").from(1).to(2))
.should(FilterBuilders.rangeFilter("num_long").from(3).to(4)))
.should(rangeFilter("num_long", 1, 2))
.should(rangeFilter("num_long", 3, 4)))
).execute().actionGet();
assertHitCount(response, 2l);
@ -1108,4 +1108,16 @@ public class SimpleQueryTests extends ElasticsearchIntegrationTest {
assertNoFailures(response);
assertHitCount(response, 3l);
}
public static FilterBuilder rangeFilter(String field, Object from, Object to) {
if (randomBoolean()) {
if (randomBoolean()) {
return FilterBuilders.rangeFilter(field).from(from).to(to);
} else {
return FilterBuilders.rangeFilter(field).from(from).to(to).setExecution("fielddata");
}
} else {
return FilterBuilders.numericRangeFilter(field).from(from).to(to);
}
}
}

View File

@ -922,6 +922,21 @@ public class SimpleIndexQueryParserTests extends ElasticsearchTestCase {
assertThat(rangeFilter.isIncludeUpper(), equalTo(false));
}
@Test
public void testRangeFilteredQueryBuilder_executionFieldData() throws IOException {
IndexQueryParserService queryParser = queryParser();
Query parsedQuery = queryParser.parse(filteredQuery(termQuery("name.first", "shay"), rangeFilter("age").from(23).to(54).includeLower(true).includeUpper(false).setExecution("fielddata"))).query();
assertThat(parsedQuery, instanceOf(XFilteredQuery.class));
Filter filter = ((XFilteredQuery) parsedQuery).getFilter();
assertThat(filter, instanceOf(NumericRangeFieldDataFilter.class));
NumericRangeFieldDataFilter<Number> rangeFilter = (NumericRangeFieldDataFilter<Number>) filter;
assertThat(rangeFilter.getField(), equalTo("age"));
assertThat(rangeFilter.getLowerVal().intValue(), equalTo(23));
assertThat(rangeFilter.getUpperVal().intValue(), equalTo(54));
assertThat(rangeFilter.isIncludeLower(), equalTo(true));
assertThat(rangeFilter.isIncludeUpper(), equalTo(false));
}
@Test
public void testNumericRangeFilteredQuery() throws IOException {
IndexQueryParserService queryParser = queryParser();

View File

@ -51,6 +51,7 @@ import static org.elasticsearch.cluster.metadata.IndexMetaData.SETTING_NUMBER_OF
import static org.elasticsearch.cluster.metadata.IndexMetaData.SETTING_NUMBER_OF_SHARDS;
import static org.elasticsearch.common.settings.ImmutableSettings.settingsBuilder;
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
import static org.elasticsearch.count.query.SimpleQueryTests.rangeFilter;
import static org.elasticsearch.index.query.FilterBuilders.*;
import static org.elasticsearch.index.query.QueryBuilders.*;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.*;
@ -1480,16 +1481,16 @@ public class SimpleQueryTests extends ElasticsearchIntegrationTest {
client().admin().indices().prepareRefresh().execute().actionGet();
SearchResponse response = client().prepareSearch("test").setFilter(
FilterBuilders.boolFilter()
.should(FilterBuilders.rangeFilter("num_long").from(1).to(2))
.should(FilterBuilders.rangeFilter("num_long").from(3).to(4))
.should(rangeFilter("num_long", 1, 2))
.should(rangeFilter("num_long", 3, 4))
).execute().actionGet();
assertThat(response.getHits().totalHits(), equalTo(4l));
// This made 2826 fail! (only with bit based filters)
response = client().prepareSearch("test").setFilter(
FilterBuilders.boolFilter()
.should(FilterBuilders.numericRangeFilter("num_long").from(1).to(2))
.should(FilterBuilders.numericRangeFilter("num_long").from(3).to(4))
.should(rangeFilter("num_long", 1, 2))
.should(rangeFilter("num_long", 3, 4))
).execute().actionGet();
assertThat(response.getHits().totalHits(), equalTo(4l));
@ -1498,8 +1499,8 @@ public class SimpleQueryTests extends ElasticsearchIntegrationTest {
response = client().prepareSearch("test").setFilter(
FilterBuilders.boolFilter()
.must(FilterBuilders.termFilter("field1", "test1"))
.should(FilterBuilders.rangeFilter("num_long").from(1).to(2))
.should(FilterBuilders.rangeFilter("num_long").from(3).to(4))
.should(rangeFilter("num_long", 1, 2))
.should(rangeFilter("num_long", 3, 4))
).execute().actionGet();
assertThat(response.getHits().totalHits(), equalTo(2l));