Browse Source

The forceful no cache behaviour for range filter with now date match expression should only be active if no rounding has been specified for `now` in the date range range expression (for example: `now/d`).

Also the automatic now detection in range filters is overrideable by the `_cache` option.

 Closes #4947
 Relates to #4846
Martijn van Groningen 11 years ago
parent
commit
7e1eed9814

+ 3 - 0
docs/reference/query-dsl/filters/numeric-range-filter.asciidoc

@@ -56,3 +56,6 @@ If caching the *result* of the filter is desired (for example, using the
 same "teen" filter with ages between 10 and 20), then it is advisable to
 simply use the <<query-dsl-range-filter,range>>
 filter.
+
+If the `now` date math expression is used without rounding then a range numeric filter will never be cached even
+if `_cache` is set to `true`. Also any filter that wraps this filter will never be cached.

+ 3 - 0
docs/reference/query-dsl/filters/range-filter.asciidoc

@@ -54,3 +54,6 @@ already faceting or sorting by.
 
 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.
+
+If the `now` date math expression is used without rounding then a range filter will never be cached even if `_cache` is
+set to `true`. Also any filter that wraps this filter will never be cached.

+ 43 - 8
src/main/java/org/elasticsearch/index/mapper/core/DateFieldMapper.java

@@ -347,24 +347,28 @@ public class DateFieldMapper extends NumberFieldMapper<Long> {
 
     @Override
     public Filter rangeFilter(Object lowerTerm, Object upperTerm, boolean includeLower, boolean includeUpper, @Nullable QueryParseContext context) {
-        boolean nowIsUsed = false;
+        return rangeFilter(lowerTerm, upperTerm, includeLower, includeUpper, context, false);
+    }
+
+    public Filter rangeFilter(Object lowerTerm, Object upperTerm, boolean includeLower, boolean includeUpper, @Nullable QueryParseContext context, boolean explicitCaching) {
+        boolean cache = explicitCaching;
         Long lowerVal = null;
         Long upperVal = null;
         if (lowerTerm != null) {
             String value = convertToString(lowerTerm);
-            nowIsUsed = value.contains("now");
+            cache = explicitCaching || !hasNowExpressionWithNoRounding(value);
             lowerVal = parseToMilliseconds(value, context, false);
         }
         if (upperTerm != null) {
             String value = convertToString(upperTerm);
-            nowIsUsed = value.contains("now");
+            cache = explicitCaching || !hasNowExpressionWithNoRounding(value);
             upperVal = parseToMilliseconds(value, context, includeUpper);
         }
 
         Filter filter =  NumericRangeFilter.newLongRange(
             names.indexName(), precisionStep, lowerVal, upperVal, includeLower, includeUpper
         );
-        if (nowIsUsed) {
+        if (!cache) {
             // We don't cache range filter if `now` date expression is used and also when a compound filter wraps
             // a range filter with a `now` date expressions.
             return NoCacheFilter.wrap(filter);
@@ -375,24 +379,28 @@ public class DateFieldMapper extends NumberFieldMapper<Long> {
 
     @Override
     public Filter rangeFilter(IndexFieldDataService fieldData, Object lowerTerm, Object upperTerm, boolean includeLower, boolean includeUpper, @Nullable QueryParseContext context) {
-        boolean nowIsUsed = false;
+        return rangeFilter(fieldData, lowerTerm, upperTerm, includeLower, includeUpper, context, false);
+    }
+
+    public Filter rangeFilter(IndexFieldDataService fieldData, Object lowerTerm, Object upperTerm, boolean includeLower, boolean includeUpper, @Nullable QueryParseContext context, boolean explicitCaching) {
+        boolean cache = explicitCaching;
         Long lowerVal = null;
         Long upperVal = null;
         if (lowerTerm != null) {
             String value = convertToString(lowerTerm);
-            nowIsUsed = value.contains("now");
+            cache = explicitCaching || !hasNowExpressionWithNoRounding(value);
             lowerVal = parseToMilliseconds(value, context, false);
         }
         if (upperTerm != null) {
             String value = convertToString(upperTerm);
-            nowIsUsed = value.contains("now");
+            cache = explicitCaching || !hasNowExpressionWithNoRounding(value);
             upperVal = parseToMilliseconds(value, context, includeUpper);
         }
 
         Filter filter =  NumericRangeFieldDataFilter.newLongRange(
             (IndexNumericFieldData<?>) fieldData.getForField(this), lowerVal,upperVal, includeLower, includeUpper
         );
-        if (nowIsUsed) {
+        if (!cache) {
             // We don't cache range filter if `now` date expression is used and also when a compound filter wraps
             // a range filter with a `now` date expressions.
             return NoCacheFilter.wrap(filter);
@@ -401,6 +409,33 @@ public class DateFieldMapper extends NumberFieldMapper<Long> {
         }
     }
 
+    private boolean hasNowExpressionWithNoRounding(String value) {
+        int index = value.indexOf("now");
+        if (index != -1) {
+            if (value.length() == 3) {
+                return true;
+            } else {
+                int indexOfPotentialRounding = index + 3;
+                if (indexOfPotentialRounding >= value.length()) {
+                    return true;
+                } else {
+                    char potentialRoundingChar;
+                    do {
+                        potentialRoundingChar = value.charAt(indexOfPotentialRounding++);
+                        if (potentialRoundingChar == '/') {
+                            return false; // We found the rounding char, so we shouldn't forcefully disable caching
+                        } else if (potentialRoundingChar == ' ') {
+                            return true; // Next token in the date math expression and no rounding found, so we should not cache.
+                        }
+                    } while (indexOfPotentialRounding < value.length());
+                    return true; // Couldn't find rounding char, so we should not cache
+                }
+            }
+        } else {
+            return false;
+        }
+    }
+
     @Override
     public Filter nullValueFilter() {
         if (nullValue == null) {

+ 13 - 2
src/main/java/org/elasticsearch/index/query/RangeFilterParser.java

@@ -27,6 +27,7 @@ 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.DateFieldMapper;
 import org.elasticsearch.index.mapper.core.NumberFieldMapper;
 
 import java.io.IOException;
@@ -122,11 +123,17 @@ public class RangeFilterParser implements FilterParser {
         MapperService.SmartNameFieldMappers smartNameFieldMappers = parseContext.smartFieldMappers(fieldName);
         if (smartNameFieldMappers != null) {
             if (smartNameFieldMappers.hasMapper()) {
+                boolean explicitlyCached = cache != null && cache;
                 if (execution.equals("index")) {
                     if (cache == null) {
                         cache = true;
                     }
-                    filter = smartNameFieldMappers.mapper().rangeFilter(from, to, includeLower, includeUpper, parseContext);
+                    FieldMapper mapper = smartNameFieldMappers.mapper();
+                    if (mapper instanceof DateFieldMapper) {
+                        filter = ((DateFieldMapper) mapper).rangeFilter(from, to, includeLower, includeUpper, parseContext, explicitlyCached);
+                    } else  {
+                        filter = mapper.rangeFilter(from, to, includeLower, includeUpper, parseContext);
+                    }
                 } else if ("fielddata".equals(execution)) {
                     if (cache == null) {
                         cache = false;
@@ -135,7 +142,11 @@ public class RangeFilterParser implements FilterParser {
                     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);
+                    if (mapper instanceof DateFieldMapper) {
+                        filter = ((DateFieldMapper) mapper).rangeFilter(parseContext.fieldData(), from, to, includeLower, includeUpper, parseContext, explicitlyCached);
+                    } else {
+                        filter = ((NumberFieldMapper) mapper).rangeFilter(parseContext.fieldData(), from, to, includeLower, includeUpper, parseContext);
+                    }
                 } else {
                     throw new QueryParsingException(parseContext.index(), "[range] filter doesn't support [" + execution + "] execution");
                 }

+ 17 - 0
src/test/java/org/elasticsearch/index/query/IndexQueryParserFilterCachingTests.java

@@ -153,11 +153,28 @@ public class IndexQueryParserFilterCachingTests extends ElasticsearchTestCase {
         assertThat(((XBooleanFilter) ((ConstantScoreQuery) parsedQuery).getFilter()).clauses().get(1).getFilter(), instanceOf(NoCacheFilter.class));
         assertThat(((XBooleanFilter) ((ConstantScoreQuery) parsedQuery).getFilter()).clauses().size(), is(2));
 
+        query = copyToStringFromClasspath("/org/elasticsearch/index/query/date_range_in_boolean_cached_complex_now.json");
+        parsedQuery = queryParser.parse(query).query();
+        assertThat(parsedQuery, instanceOf(ConstantScoreQuery.class));
+        assertThat(((ConstantScoreQuery) parsedQuery).getFilter(), instanceOf(XBooleanFilter.class));
+        assertThat(((XBooleanFilter) ((ConstantScoreQuery) parsedQuery).getFilter()).clauses().get(1).getFilter(), instanceOf(NoCacheFilter.class));
+        assertThat(((XBooleanFilter) ((ConstantScoreQuery) parsedQuery).getFilter()).clauses().size(), is(2));
+
         query = copyToStringFromClasspath("/org/elasticsearch/index/query/date_range_in_boolean_cached.json");
         parsedQuery = queryParser.parse(query).query();
         assertThat(parsedQuery, instanceOf(ConstantScoreQuery.class));
         assertThat(((ConstantScoreQuery) parsedQuery).getFilter(), instanceOf(CachedFilter.class));
 
+        query = copyToStringFromClasspath("/org/elasticsearch/index/query/date_range_in_boolean_cached_now_with_rounding.json");
+        parsedQuery = queryParser.parse(query).query();
+        assertThat(parsedQuery, instanceOf(ConstantScoreQuery.class));
+        assertThat(((ConstantScoreQuery) parsedQuery).getFilter(), instanceOf(CachedFilter.class));
+
+        query = copyToStringFromClasspath("/org/elasticsearch/index/query/date_range_in_boolean_cached_complex_now_with_rounding.json");
+        parsedQuery = queryParser.parse(query).query();
+        assertThat(parsedQuery, instanceOf(ConstantScoreQuery.class));
+        assertThat(((ConstantScoreQuery) parsedQuery).getFilter(), instanceOf(CachedFilter.class));
+
         try {
             SearchContext.setCurrent(new TestSearchContext());
             query = copyToStringFromClasspath("/org/elasticsearch/index/query/has-child.json");

+ 26 - 0
src/test/java/org/elasticsearch/index/query/date_range_in_boolean_cached_complex_now.json

@@ -0,0 +1,26 @@
+{
+    "constant_score": {
+        "filter": {
+            "bool": {
+                "_cache" : true,
+                "must": [
+                    {
+                        "term": {
+                            "foo": {
+                                "value": "bar"
+                            }
+                        }
+                    },
+                    {
+                        "range" : {
+                            "born" : {
+                                "gte": "2012-01-01",
+                                "lte": "now+1m+1s"
+                            }
+                        }
+                    }
+                ]
+            }
+        }
+    }
+}

+ 26 - 0
src/test/java/org/elasticsearch/index/query/date_range_in_boolean_cached_complex_now_with_rounding.json

@@ -0,0 +1,26 @@
+{
+    "constant_score": {
+        "filter": {
+            "bool": {
+                "_cache" : true,
+                "must": [
+                    {
+                        "term": {
+                            "foo": {
+                                "value": "bar"
+                            }
+                        }
+                    },
+                    {
+                        "range" : {
+                            "born" : {
+                                "gte": "2012-01-01",
+                                "lte": "now+1m+1s/m"
+                            }
+                        }
+                    }
+                ]
+            }
+        }
+    }
+}

+ 26 - 0
src/test/java/org/elasticsearch/index/query/date_range_in_boolean_cached_now_with_rounding.json

@@ -0,0 +1,26 @@
+{
+    "constant_score": {
+        "filter": {
+            "bool": {
+                "_cache" : true,
+                "must": [
+                    {
+                        "term": {
+                            "foo": {
+                                "value": "bar"
+                            }
+                        }
+                    },
+                    {
+                        "range" : {
+                            "born" : {
+                                "gte": "2012-01-01",
+                                "lte": "now/d"
+                            }
+                        }
+                    }
+                ]
+            }
+        }
+    }
+}

+ 34 - 4
src/test/java/org/elasticsearch/search/query/SimpleQueryTests.java

@@ -2046,7 +2046,7 @@ public class SimpleQueryTests extends ElasticsearchIntegrationTest {
                 .get();
 
         SearchResponse searchResponse = client().prepareSearch("test")
-                .setQuery(QueryBuilders.filteredQuery(matchAllQuery(), FilterBuilders.rangeFilter("date").from("2013-01-01").to("now").cache(true)))
+                .setQuery(QueryBuilders.filteredQuery(matchAllQuery(), FilterBuilders.rangeFilter("date").from("2013-01-01").to("now")))
                 .get();
         assertHitCount(searchResponse, 1l);
 
@@ -2059,7 +2059,7 @@ public class SimpleQueryTests extends ElasticsearchIntegrationTest {
                         matchAllQuery(),
                         FilterBuilders.boolFilter().cache(true)
                                 .must(FilterBuilders.matchAllFilter())
-                                .must(FilterBuilders.rangeFilter("date").from("2013-01-01").to("now").cache(true))
+                                .must(FilterBuilders.rangeFilter("date").from("2013-01-01").to("now"))
                 ))
                 .get();
         assertHitCount(searchResponse, 1l);
@@ -2068,19 +2068,49 @@ public class SimpleQueryTests extends ElasticsearchIntegrationTest {
         statsResponse = client().admin().indices().prepareStats("test").clear().setFilterCache(true).get();
         assertThat(statsResponse.getIndex("test").getTotal().getFilterCache().getMemorySizeInBytes(), equalTo(0l));
 
+
+        searchResponse = client().prepareSearch("test")
+                .setQuery(QueryBuilders.filteredQuery(
+                        matchAllQuery(),
+                        FilterBuilders.boolFilter().cache(true)
+                                .must(FilterBuilders.matchAllFilter())
+                                .must(FilterBuilders.rangeFilter("date").from("2013-01-01").to("now/d").cache(true))
+                ))
+                .get();
+        assertHitCount(searchResponse, 1l);
+        // Now with rounding is used, so we must have something in filter cache
+        statsResponse = client().admin().indices().prepareStats("test").clear().setFilterCache(true).get();
+        long filtercacheSize = statsResponse.getIndex("test").getTotal().getFilterCache().getMemorySizeInBytes();
+        assertThat(filtercacheSize, greaterThan(0l));
+
         searchResponse = client().prepareSearch("test")
                 .setQuery(QueryBuilders.filteredQuery(
                         matchAllQuery(),
                         FilterBuilders.boolFilter().cache(true)
                                 .must(FilterBuilders.termFilter("field", "value").cache(true))
+                                .must(FilterBuilders.rangeFilter("date").from("2013-01-01").to("now"))
+                ))
+                .get();
+        assertHitCount(searchResponse, 1l);
+
+        // and because we use term filter, it is also added to filter cache, so it should contain more than before
+        statsResponse = client().admin().indices().prepareStats("test").clear().setFilterCache(true).get();
+        assertThat(statsResponse.getIndex("test").getTotal().getFilterCache().getMemorySizeInBytes(), greaterThan(filtercacheSize));
+        filtercacheSize = statsResponse.getIndex("test").getTotal().getFilterCache().getMemorySizeInBytes();
+
+        searchResponse = client().prepareSearch("test")
+                .setQuery(QueryBuilders.filteredQuery(
+                        matchAllQuery(),
+                        FilterBuilders.boolFilter().cache(true)
+                                .must(FilterBuilders.matchAllFilter())
                                 .must(FilterBuilders.rangeFilter("date").from("2013-01-01").to("now").cache(true))
                 ))
                 .get();
         assertHitCount(searchResponse, 1l);
 
-        // filter cache only has a cache entry for the term filter
+        // The range filter is now explicitly cached, so it now it is in the filter cache.
         statsResponse = client().admin().indices().prepareStats("test").clear().setFilterCache(true).get();
-        assertThat(statsResponse.getIndex("test").getTotal().getFilterCache().getMemorySizeInBytes(), greaterThan(0l));
+        assertThat(statsResponse.getIndex("test").getTotal().getFilterCache().getMemorySizeInBytes(), greaterThan(filtercacheSize));
     }
 
     @Test