diff --git a/docs/reference/query-dsl/filters/numeric-range-filter.asciidoc b/docs/reference/query-dsl/filters/numeric-range-filter.asciidoc index 64a4d225040..aea15a51084 100644 --- a/docs/reference/query-dsl/filters/numeric-range-filter.asciidoc +++ b/docs/reference/query-dsl/filters/numeric-range-filter.asciidoc @@ -1,6 +1,8 @@ [[query-dsl-numeric-range-filter]] === Numeric Range Filter +deprecated[0.90.8,Use the <> with the `fielddata` execution mode instead] + Filters documents with fields that have values within a certain numeric range. Similar to <>, except diff --git a/docs/reference/query-dsl/filters/range-filter.asciidoc b/docs/reference/query-dsl/filters/range-filter.asciidoc index 0196c49afcc..938fbf80951 100644 --- a/docs/reference/query-dsl/filters/range-filter.asciidoc +++ b/docs/reference/query-dsl/filters/range-filter.asciidoc @@ -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. diff --git a/src/main/java/org/elasticsearch/index/query/FilterBuilders.java b/src/main/java/org/elasticsearch/index/query/FilterBuilders.java index 186f676d8cf..857f8abfdf5 100644 --- a/src/main/java/org/elasticsearch/index/query/FilterBuilders.java +++ b/src/main/java/org/elasticsearch/index/query/FilterBuilders.java @@ -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 fielddata. */ + @Deprecated public static NumericRangeFilterBuilder numericRangeFilter(String name) { return new NumericRangeFilterBuilder(name); } diff --git a/src/main/java/org/elasticsearch/index/query/NumericRangeFilterBuilder.java b/src/main/java/org/elasticsearch/index/query/NumericRangeFilterBuilder.java index 538cc6ce36e..f1ff148aa09 100644 --- a/src/main/java/org/elasticsearch/index/query/NumericRangeFilterBuilder.java +++ b/src/main/java/org/elasticsearch/index/query/NumericRangeFilterBuilder.java @@ -28,8 +28,10 @@ import java.io.IOException; *

*

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 fielddata. */ +@Deprecated public class NumericRangeFilterBuilder extends BaseFilterBuilder { private final String name; diff --git a/src/main/java/org/elasticsearch/index/query/NumericRangeFilterParser.java b/src/main/java/org/elasticsearch/index/query/NumericRangeFilterParser.java index 1729e14a1e9..19b65508ff1 100644 --- a/src/main/java/org/elasticsearch/index/query/NumericRangeFilterParser.java +++ b/src/main/java/org/elasticsearch/index/query/NumericRangeFilterParser.java @@ -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"; diff --git a/src/main/java/org/elasticsearch/index/query/RangeFilterBuilder.java b/src/main/java/org/elasticsearch/index/query/RangeFilterBuilder.java index 2f639293cfd..3e561f60507 100644 --- a/src/main/java/org/elasticsearch/index/query/RangeFilterBuilder.java +++ b/src/main/java/org/elasticsearch/index/query/RangeFilterBuilder.java @@ -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". + *

    + *
  1. The index execution uses the field's inverted in order to determine of documents fall with in + * the range filter's from and to range. + *
  2. The fielddata 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. + *
+ * + * In general for small ranges the index execution is faster and for longer ranges the + * fielddata 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(); } diff --git a/src/main/java/org/elasticsearch/index/query/RangeFilterParser.java b/src/main/java/org/elasticsearch/index/query/RangeFilterParser.java index b19b436e3a5..19598b86439 100644 --- a/src/main/java/org/elasticsearch/index/query/RangeFilterParser.java +++ b/src/main/java/org/elasticsearch/index/query/RangeFilterParser.java @@ -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); } diff --git a/src/test/java/org/elasticsearch/count/query/SimpleQueryTests.java b/src/test/java/org/elasticsearch/count/query/SimpleQueryTests.java index 9f994b6a6bb..34d60914bae 100644 --- a/src/test/java/org/elasticsearch/count/query/SimpleQueryTests.java +++ b/src/test/java/org/elasticsearch/count/query/SimpleQueryTests.java @@ -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); + } + } } diff --git a/src/test/java/org/elasticsearch/index/query/SimpleIndexQueryParserTests.java b/src/test/java/org/elasticsearch/index/query/SimpleIndexQueryParserTests.java index 7d5c96d9ed6..6f3e15945dd 100644 --- a/src/test/java/org/elasticsearch/index/query/SimpleIndexQueryParserTests.java +++ b/src/test/java/org/elasticsearch/index/query/SimpleIndexQueryParserTests.java @@ -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 rangeFilter = (NumericRangeFieldDataFilter) 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(); diff --git a/src/test/java/org/elasticsearch/search/query/SimpleQueryTests.java b/src/test/java/org/elasticsearch/search/query/SimpleQueryTests.java index 35d2122f68f..def0031ef49 100644 --- a/src/test/java/org/elasticsearch/search/query/SimpleQueryTests.java +++ b/src/test/java/org/elasticsearch/search/query/SimpleQueryTests.java @@ -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));