Core: Added query/filter wrapper that builds the actual query to be executed on the last possible moment to aid with index aliases and percolator queries using now date expression.

Percolator queries and index alias filters are parsed once and reused as long as they exist on a node. If they contain time based range filters with a `now` expression then the alias filters and percolator queries are going to be incorrect from the moment these are constructed (depending on the date rounding).

 If a range filter or range query is constructed as part of adding a percolator query or a index alias filter then these get wrapped in special query or filter wrappers that defer the resolution of now at last possible moment as apposed during parse time. In the case of the range filter a special Resolvable Filter makes sure that `now` is resolved when the DocIdSet is pulled and in the case of the range query `now` is resolved at query rewrite time. Both occur at the time the range filter or query is used as apposed when the query or filter is constructed during parse time.

Closes #8474
Closes #8534
This commit is contained in:
Martijn van Groningen 2014-11-18 14:38:11 +01:00
parent a7b2bdca4c
commit 7cc2bc8a14
11 changed files with 337 additions and 42 deletions

View File

@ -0,0 +1,36 @@
/*
* Licensed to Elasticsearch 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.search.Query;
/**
* Queries are never cached directly, but a query can be wrapped in a filter that may end being cached.
* Filters that wrap this query either directly or indirectly will never be cached.
*/
public abstract class NoCacheQuery extends Query {
@Override
public final String toString(String s) {
return "no_cache(" + innerToString(s) + ")";
}
public abstract String innerToString(String s);
}

View File

@ -0,0 +1,51 @@
/*
* Licensed to Elasticsearch 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.LeafReaderContext;
import org.apache.lucene.search.DocIdSet;
import org.apache.lucene.search.Filter;
import org.apache.lucene.util.Bits;
import java.io.IOException;
/**
* A filter implementation that resolves details at the last possible moment between filter parsing and execution.
* For example a date filter based on 'now'.
*/
public abstract class ResolvableFilter extends Filter {
/**
* @return The actual filter instance to be executed containing the latest details.
*/
public abstract Filter resolve();
@Override
public DocIdSet getDocIdSet(LeafReaderContext context, Bits acceptDocs) throws IOException {
Filter resolvedFilter = resolve();
if (resolvedFilter != null) {
return resolvedFilter.getDocIdSet(context, acceptDocs);
} else {
return null;
}
}
}

View File

@ -22,6 +22,7 @@ package org.elasticsearch.index.mapper.core;
import org.apache.lucene.document.Field; import org.apache.lucene.document.Field;
import org.apache.lucene.document.FieldType; import org.apache.lucene.document.FieldType;
import org.apache.lucene.index.IndexOptions; import org.apache.lucene.index.IndexOptions;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.search.Filter; import org.apache.lucene.search.Filter;
import org.apache.lucene.search.NumericRangeFilter; import org.apache.lucene.search.NumericRangeFilter;
import org.apache.lucene.search.NumericRangeQuery; import org.apache.lucene.search.NumericRangeQuery;
@ -29,6 +30,7 @@ import org.apache.lucene.search.Query;
import org.apache.lucene.util.BytesRef; import org.apache.lucene.util.BytesRef;
import org.apache.lucene.util.BytesRefBuilder; import org.apache.lucene.util.BytesRefBuilder;
import org.apache.lucene.util.NumericUtils; import org.apache.lucene.util.NumericUtils;
import org.apache.lucene.util.ToStringUtils;
import org.elasticsearch.ElasticsearchIllegalArgumentException; import org.elasticsearch.ElasticsearchIllegalArgumentException;
import org.elasticsearch.common.Explicit; import org.elasticsearch.common.Explicit;
import org.elasticsearch.common.Nullable; import org.elasticsearch.common.Nullable;
@ -38,6 +40,8 @@ import org.elasticsearch.common.joda.DateMathParser;
import org.elasticsearch.common.joda.FormatDateTimeFormatter; import org.elasticsearch.common.joda.FormatDateTimeFormatter;
import org.elasticsearch.common.joda.Joda; import org.elasticsearch.common.joda.Joda;
import org.elasticsearch.common.lucene.search.NoCacheFilter; import org.elasticsearch.common.lucene.search.NoCacheFilter;
import org.elasticsearch.common.lucene.search.NoCacheQuery;
import org.elasticsearch.common.lucene.search.ResolvableFilter;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.Fuzziness; import org.elasticsearch.common.unit.Fuzziness;
import org.elasticsearch.common.util.LocaleUtils; import org.elasticsearch.common.util.LocaleUtils;
@ -53,6 +57,7 @@ import org.elasticsearch.index.mapper.core.LongFieldMapper.CustomLongNumericFiel
import org.elasticsearch.index.query.QueryParseContext; import org.elasticsearch.index.query.QueryParseContext;
import org.elasticsearch.index.search.NumericRangeFieldDataFilter; import org.elasticsearch.index.search.NumericRangeFieldDataFilter;
import org.elasticsearch.index.similarity.SimilarityProvider; import org.elasticsearch.index.similarity.SimilarityProvider;
import org.elasticsearch.search.internal.SearchContext;
import org.joda.time.DateTimeZone; import org.joda.time.DateTimeZone;
import java.io.IOException; import java.io.IOException;
@ -300,39 +305,35 @@ public class DateFieldMapper extends NumberFieldMapper<Long> {
@Override @Override
public Query termQuery(Object value, @Nullable QueryParseContext context) { public Query termQuery(Object value, @Nullable QueryParseContext context) {
long lValue = parseToMilliseconds(value, context); long lValue = parseToMilliseconds(value);
return NumericRangeQuery.newLongRange(names.indexName(), precisionStep, return NumericRangeQuery.newLongRange(names.indexName(), precisionStep,
lValue, lValue, true, true); lValue, lValue, true, true);
} }
public long parseToMilliseconds(Object value, @Nullable QueryParseContext context) { public long parseToMilliseconds(Object value) {
return parseToMilliseconds(value, context, false); return parseToMilliseconds(value, false, null, dateMathParser);
} }
public long parseToMilliseconds(Object value, @Nullable QueryParseContext context, boolean includeUpper) { public long parseToMilliseconds(Object value, boolean includeUpper, @Nullable DateTimeZone zone, @Nullable DateMathParser forcedDateParser) {
return parseToMilliseconds(value, context, includeUpper, null, dateMathParser);
}
public long parseToMilliseconds(Object value, @Nullable QueryParseContext context, boolean includeUpper, @Nullable DateTimeZone zone, @Nullable DateMathParser forcedDateParser) {
if (value instanceof Number) { if (value instanceof Number) {
return ((Number) value).longValue(); return ((Number) value).longValue();
} }
return parseToMilliseconds(convertToString(value), context, includeUpper, zone, forcedDateParser); return parseToMilliseconds(convertToString(value), includeUpper, zone, forcedDateParser);
} }
public long parseToMilliseconds(String value, @Nullable QueryParseContext context, boolean includeUpper, @Nullable DateTimeZone zone, @Nullable DateMathParser forcedDateParser) { public long parseToMilliseconds(String value, boolean includeUpper, @Nullable DateTimeZone zone, @Nullable DateMathParser forcedDateParser) {
long now = context == null ? System.currentTimeMillis() : context.nowInMillis(); SearchContext sc = SearchContext.current();
long now = sc == null ? System.currentTimeMillis() : sc.nowInMillis();
DateMathParser dateParser = dateMathParser; DateMathParser dateParser = dateMathParser;
if (forcedDateParser != null) { if (forcedDateParser != null) {
dateParser = forcedDateParser; dateParser = forcedDateParser;
} }
long time = includeUpper && roundCeil ? dateParser.parseRoundCeil(value, now, zone) : dateParser.parse(value, now, zone); return includeUpper && roundCeil ? dateParser.parseRoundCeil(value, now, zone) : dateParser.parse(value, now, zone);
return time;
} }
@Override @Override
public Filter termFilter(Object value, @Nullable QueryParseContext context) { public Filter termFilter(Object value, @Nullable QueryParseContext context) {
final long lValue = parseToMilliseconds(value, context); final long lValue = parseToMilliseconds(value);
return NumericRangeFilter.newLongRange(names.indexName(), precisionStep, return NumericRangeFilter.newLongRange(names.indexName(), precisionStep,
lValue, lValue, true, true); lValue, lValue, true, true);
} }
@ -343,9 +344,18 @@ public class DateFieldMapper extends NumberFieldMapper<Long> {
} }
public Query rangeQuery(Object lowerTerm, Object upperTerm, boolean includeLower, boolean includeUpper, @Nullable DateTimeZone timeZone, @Nullable DateMathParser forcedDateParser, @Nullable QueryParseContext context) { public Query rangeQuery(Object lowerTerm, Object upperTerm, boolean includeLower, boolean includeUpper, @Nullable DateTimeZone timeZone, @Nullable DateMathParser forcedDateParser, @Nullable QueryParseContext context) {
// If the current search context is null we're parsing percolator query or a index alias filter.
if (SearchContext.current() == null) {
return new LateParsingQuery(lowerTerm, upperTerm, includeLower, includeUpper, timeZone, forcedDateParser);
} else {
return innerRangeQuery(lowerTerm, upperTerm, includeLower, includeUpper, timeZone, forcedDateParser);
}
}
private Query innerRangeQuery(Object lowerTerm, Object upperTerm, boolean includeLower, boolean includeUpper, @Nullable DateTimeZone timeZone, @Nullable DateMathParser forcedDateParser) {
return NumericRangeQuery.newLongRange(names.indexName(), precisionStep, return NumericRangeQuery.newLongRange(names.indexName(), precisionStep,
lowerTerm == null ? null : parseToMilliseconds(lowerTerm, context, false, timeZone, forcedDateParser == null ? dateMathParser : forcedDateParser), lowerTerm == null ? null : parseToMilliseconds(lowerTerm, false, timeZone, forcedDateParser == null ? dateMathParser : forcedDateParser),
upperTerm == null ? null : parseToMilliseconds(upperTerm, context, includeUpper, timeZone, forcedDateParser == null ? dateMathParser : forcedDateParser), upperTerm == null ? null : parseToMilliseconds(upperTerm, includeUpper, timeZone, forcedDateParser == null ? dateMathParser : forcedDateParser),
includeLower, includeUpper); includeLower, includeUpper);
} }
@ -370,6 +380,16 @@ public class DateFieldMapper extends NumberFieldMapper<Long> {
* - the String to parse does not have already a timezone defined (ie. `2014-01-01T00:00:00+03:00`) * - the String to parse does not have already a timezone defined (ie. `2014-01-01T00:00:00+03:00`)
*/ */
public Filter rangeFilter(QueryParseContext parseContext, Object lowerTerm, Object upperTerm, boolean includeLower, boolean includeUpper, @Nullable DateTimeZone timeZone, @Nullable DateMathParser forcedDateParser, @Nullable QueryParseContext context, @Nullable Boolean explicitCaching) { public Filter rangeFilter(QueryParseContext parseContext, Object lowerTerm, Object upperTerm, boolean includeLower, boolean includeUpper, @Nullable DateTimeZone timeZone, @Nullable DateMathParser forcedDateParser, @Nullable QueryParseContext context, @Nullable Boolean explicitCaching) {
IndexNumericFieldData fieldData = parseContext != null ? (IndexNumericFieldData) parseContext.getForField(this) : null;
// If the current search context is null we're parsing percolator query or a index alias filter.
if (SearchContext.current() == null) {
return new LateParsingFilter(fieldData, lowerTerm, upperTerm, includeLower, includeUpper, timeZone, forcedDateParser, explicitCaching);
} else {
return innerRangeFilter(fieldData, lowerTerm, upperTerm, includeLower, includeUpper, timeZone, forcedDateParser, explicitCaching);
}
}
private Filter innerRangeFilter(IndexNumericFieldData fieldData, Object lowerTerm, Object upperTerm, boolean includeLower, boolean includeUpper, @Nullable DateTimeZone timeZone, @Nullable DateMathParser forcedDateParser, @Nullable Boolean explicitCaching) {
boolean cache; boolean cache;
boolean cacheable = true; boolean cacheable = true;
Long lowerVal = null; Long lowerVal = null;
@ -380,7 +400,7 @@ public class DateFieldMapper extends NumberFieldMapper<Long> {
} else { } else {
String value = convertToString(lowerTerm); String value = convertToString(lowerTerm);
cacheable = !hasDateExpressionWithNoRounding(value); cacheable = !hasDateExpressionWithNoRounding(value);
lowerVal = parseToMilliseconds(value, context, false, timeZone, forcedDateParser); lowerVal = parseToMilliseconds(value, false, timeZone, forcedDateParser);
} }
} }
if (upperTerm != null) { if (upperTerm != null) {
@ -389,7 +409,7 @@ public class DateFieldMapper extends NumberFieldMapper<Long> {
} else { } else {
String value = convertToString(upperTerm); String value = convertToString(upperTerm);
cacheable = cacheable && !hasDateExpressionWithNoRounding(value); cacheable = cacheable && !hasDateExpressionWithNoRounding(value);
upperVal = parseToMilliseconds(value, context, includeUpper, timeZone, forcedDateParser); upperVal = parseToMilliseconds(value, includeUpper, timeZone, forcedDateParser);
} }
} }
@ -404,12 +424,10 @@ public class DateFieldMapper extends NumberFieldMapper<Long> {
} }
Filter filter; Filter filter;
if (parseContext != null) { if (fieldData != null) {
filter = NumericRangeFieldDataFilter.newLongRange( filter = NumericRangeFieldDataFilter.newLongRange(fieldData, lowerVal,upperVal, includeLower, includeUpper);
(IndexNumericFieldData) parseContext.getForField(this), lowerVal,upperVal, includeLower, includeUpper
);
} else { } else {
filter = NumericRangeFilter.newLongRange( filter = NumericRangeFilter.newLongRange(
names.indexName(), precisionStep, lowerVal, upperVal, includeLower, includeUpper names.indexName(), precisionStep, lowerVal, upperVal, includeLower, includeUpper
); );
} }
@ -597,4 +615,70 @@ public class DateFieldMapper extends NumberFieldMapper<Long> {
} }
} }
} }
private final class LateParsingFilter extends ResolvableFilter {
final IndexNumericFieldData fieldData;
final Object lowerTerm;
final Object upperTerm;
final boolean includeLower;
final boolean includeUpper;
final DateTimeZone timeZone;
final DateMathParser forcedDateParser;
final Boolean explicitCaching;
public LateParsingFilter(IndexNumericFieldData fieldData, Object lowerTerm, Object upperTerm, boolean includeLower, boolean includeUpper, DateTimeZone timeZone, DateMathParser forcedDateParser, Boolean explicitCaching) {
this.fieldData = fieldData;
this.lowerTerm = lowerTerm;
this.upperTerm = upperTerm;
this.includeLower = includeLower;
this.includeUpper = includeUpper;
this.timeZone = timeZone;
this.forcedDateParser = forcedDateParser;
this.explicitCaching = explicitCaching;
}
@Override
public Filter resolve() {
return innerRangeFilter(fieldData, lowerTerm, upperTerm, includeLower, includeUpper, timeZone, forcedDateParser, explicitCaching);
}
}
public final class LateParsingQuery extends NoCacheQuery {
final Object lowerTerm;
final Object upperTerm;
final boolean includeLower;
final boolean includeUpper;
final DateTimeZone timeZone;
final DateMathParser forcedDateParser;
public LateParsingQuery(Object lowerTerm, Object upperTerm, boolean includeLower, boolean includeUpper, DateTimeZone timeZone, DateMathParser forcedDateParser) {
this.lowerTerm = lowerTerm;
this.upperTerm = upperTerm;
this.includeLower = includeLower;
this.includeUpper = includeUpper;
this.timeZone = timeZone;
this.forcedDateParser = forcedDateParser;
}
@Override
public Query rewrite(IndexReader reader) throws IOException {
Query query = innerRangeQuery(lowerTerm, upperTerm, includeLower, includeUpper, timeZone, forcedDateParser);
return query.rewrite(reader);
}
@Override
public String innerToString(String s) {
final StringBuilder sb = new StringBuilder();
return sb.append(names.indexName()).append(':')
.append(includeLower ? '[' : '{')
.append((lowerTerm == null) ? "*" : lowerTerm.toString())
.append(" TO ")
.append((upperTerm == null) ? "*" : upperTerm.toString())
.append(includeUpper ? ']' : '}')
.append(ToStringUtils.boost(getBoost()))
.toString();
}
}
} }

View File

@ -21,17 +21,22 @@ package org.elasticsearch.index.query;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.queryparser.classic.MapperQueryParser; import org.apache.lucene.queryparser.classic.MapperQueryParser;
import org.apache.lucene.queryparser.classic.QueryParserSettings; import org.apache.lucene.queryparser.classic.QueryParserSettings;
import org.apache.lucene.search.DocIdSet;
import org.apache.lucene.search.Filter; import org.apache.lucene.search.Filter;
import org.apache.lucene.search.Query; import org.apache.lucene.search.Query;
import org.apache.lucene.search.join.BitDocIdSetFilter; import org.apache.lucene.search.join.BitDocIdSetFilter;
import org.apache.lucene.search.similarities.Similarity; import org.apache.lucene.search.similarities.Similarity;
import org.apache.lucene.util.Bits;
import org.elasticsearch.Version; import org.elasticsearch.Version;
import org.elasticsearch.common.Nullable; import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.ParseField; import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.lucene.search.NoCacheFilter; import org.elasticsearch.common.lucene.search.NoCacheFilter;
import org.elasticsearch.common.lucene.search.NoCacheQuery;
import org.elasticsearch.common.lucene.search.Queries; import org.elasticsearch.common.lucene.search.Queries;
import org.elasticsearch.common.lucene.search.ResolvableFilter;
import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.Index; import org.elasticsearch.index.Index;
import org.elasticsearch.index.analysis.AnalysisService; import org.elasticsearch.index.analysis.AnalysisService;
@ -187,17 +192,37 @@ public class QueryParseContext {
return indexQueryParser.bitsetFilterCache.getBitDocIdSetFilter(filter); return indexQueryParser.bitsetFilterCache.getBitDocIdSetFilter(filter);
} }
public Filter cacheFilter(Filter filter, @Nullable CacheKeyFilter.Key cacheKey) { public Filter cacheFilter(Filter filter, @Nullable final CacheKeyFilter.Key cacheKey) {
if (filter == null) { if (filter == null) {
return null; return null;
} }
if (this.disableFilterCaching || this.propagateNoCache || filter instanceof NoCacheFilter) { if (this.disableFilterCaching || this.propagateNoCache || filter instanceof NoCacheFilter) {
return filter; return filter;
} }
if (cacheKey != null) { if (filter instanceof ResolvableFilter) {
filter = new CacheKeyFilter.Wrapper(filter, cacheKey); final ResolvableFilter resolvableFilter = (ResolvableFilter) filter;
// We need to wrap it another filter, because this method is invoked at query parse time, which
// may not be during search execution time. (for example index alias filter and percolator)
return new Filter() {
@Override
public DocIdSet getDocIdSet(LeafReaderContext atomicReaderContext, Bits bits) throws IOException {
Filter filter = resolvableFilter.resolve();
if (filter == null) {
return null;
}
if (cacheKey != null) {
filter = new CacheKeyFilter.Wrapper(filter, cacheKey);
}
filter = indexQueryParser.indexCache.filter().cache(filter);
return filter.getDocIdSet(atomicReaderContext, bits);
}
};
} else {
if (cacheKey != null) {
filter = new CacheKeyFilter.Wrapper(filter, cacheKey);
}
return indexQueryParser.indexCache.filter().cache(filter);
} }
return indexQueryParser.indexCache.filter().cache(filter);
} }
public <IFD extends IndexFieldData<?>> IFD getForField(FieldMapper<?> mapper) { public <IFD extends IndexFieldData<?>> IFD getForField(FieldMapper<?> mapper) {
@ -249,6 +274,9 @@ public class QueryParseContext {
// if we are at END_OBJECT, move to the next one... // if we are at END_OBJECT, move to the next one...
parser.nextToken(); parser.nextToken();
} }
if (result instanceof NoCacheQuery) {
propagateNoCache = true;
}
if (CustomQueryWrappingFilter.shouldUseCustomQueryWrappingFilter(result)) { if (CustomQueryWrappingFilter.shouldUseCustomQueryWrappingFilter(result)) {
requireCustomQueryWrappingFilter = true; requireCustomQueryWrappingFilter = true;
// If later on, either directly or indirectly this query gets wrapped in a query filter it must never // If later on, either directly or indirectly this query gets wrapped in a query filter it must never

View File

@ -265,7 +265,7 @@ public abstract class DecayFunctionParser implements ScoreFunctionParser {
} }
long origin = SearchContext.current().nowInMillis(); long origin = SearchContext.current().nowInMillis();
if (originString != null) { if (originString != null) {
origin = dateFieldMapper.parseToMilliseconds(originString, parseContext); origin = dateFieldMapper.parseToMilliseconds(originString);
} }
if (scaleString == null) { if (scaleString == null) {

View File

@ -38,7 +38,6 @@ import org.elasticsearch.index.query.FilterBuilder;
import org.elasticsearch.index.query.FilterBuilders; import org.elasticsearch.index.query.FilterBuilders;
import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.QueryParsingException; import org.elasticsearch.index.query.QueryParsingException;
import org.elasticsearch.indices.IndexMissingException;
import org.elasticsearch.rest.action.admin.indices.alias.delete.AliasesMissingException; import org.elasticsearch.rest.action.admin.indices.alias.delete.AliasesMissingException;
import org.elasticsearch.search.SearchHit; import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits; import org.elasticsearch.search.SearchHits;
@ -58,10 +57,12 @@ import static com.google.common.collect.Sets.newHashSet;
import static org.elasticsearch.client.Requests.createIndexRequest; import static org.elasticsearch.client.Requests.createIndexRequest;
import static org.elasticsearch.client.Requests.indexRequest; import static org.elasticsearch.client.Requests.indexRequest;
import static org.elasticsearch.common.settings.ImmutableSettings.settingsBuilder; import static org.elasticsearch.common.settings.ImmutableSettings.settingsBuilder;
import static org.elasticsearch.index.query.FilterBuilders.termFilter; import static org.elasticsearch.index.query.FilterBuilders.*;
import static org.elasticsearch.index.query.QueryBuilders.rangeQuery;
import static org.elasticsearch.test.hamcrest.CollectionAssertions.hasKey; import static org.elasticsearch.test.hamcrest.CollectionAssertions.hasKey;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertSearchResponse; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertSearchResponse;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertHitCount;
import static org.hamcrest.Matchers.*; import static org.hamcrest.Matchers.*;
/** /**
@ -964,6 +965,26 @@ public class IndexAliasesTests extends ElasticsearchIntegrationTest {
.addAlias("test", "a", FilterBuilders.matchAllFilter()) // <-- no fail, b/c no field mentioned .addAlias("test", "a", FilterBuilders.matchAllFilter()) // <-- no fail, b/c no field mentioned
.get(); .get();
} }
@Test
public void testAliasFilterWithNowInRangeFilterAndQuery() throws Exception {
assertAcked(prepareCreate("my-index").addMapping("my-type", "_timestamp", "enabled=true"));
assertAcked(admin().indices().prepareAliases().addAlias("my-index", "filter1", rangeFilter("_timestamp").cache(randomBoolean()).from("now-1d").to("now")));
assertAcked(admin().indices().prepareAliases().addAlias("my-index", "filter2", queryFilter(rangeQuery("_timestamp").from("now-1d").to("now"))));
final int numDocs = scaledRandomIntBetween(5, 52);
for (int i = 1; i <= numDocs; i++) {
client().prepareIndex("my-index", "my-type").setCreate(true).setSource("{}").get();
if (i % 2 == 0) {
refresh();
SearchResponse response = client().prepareSearch("filter1").get();
assertHitCount(response, i);
response = client().prepareSearch("filter2").get();
assertHitCount(response, i);
}
}
}
private void checkAliases() { private void checkAliases() {
GetAliasesResponse getAliasesResponse = admin().indices().prepareGetAliases("alias1").get(); GetAliasesResponse getAliasesResponse = admin().indices().prepareGetAliases("alias1").get();

View File

@ -36,7 +36,9 @@ import org.elasticsearch.index.mapper.*;
import org.elasticsearch.index.mapper.core.DateFieldMapper; import org.elasticsearch.index.mapper.core.DateFieldMapper;
import org.elasticsearch.index.mapper.core.LongFieldMapper; import org.elasticsearch.index.mapper.core.LongFieldMapper;
import org.elasticsearch.index.mapper.core.StringFieldMapper; import org.elasticsearch.index.mapper.core.StringFieldMapper;
import org.elasticsearch.search.internal.SearchContext;
import org.elasticsearch.test.ElasticsearchSingleNodeTest; import org.elasticsearch.test.ElasticsearchSingleNodeTest;
import org.elasticsearch.test.TestSearchContext;
import org.joda.time.DateTime; import org.joda.time.DateTime;
import org.joda.time.DateTimeZone; import org.joda.time.DateTimeZone;
import org.junit.Test; import org.junit.Test;
@ -221,7 +223,13 @@ public class SimpleDateMappingTests extends ElasticsearchSingleNodeTest {
.bytes()); .bytes());
assertThat(((LongFieldMapper.CustomLongNumericField) doc.rootDoc().getField("date_field")).numericAsString(), equalTo(Long.toString(new DateTime(TimeValue.timeValueHours(10).millis(), DateTimeZone.UTC).getMillis()))); assertThat(((LongFieldMapper.CustomLongNumericField) doc.rootDoc().getField("date_field")).numericAsString(), equalTo(Long.toString(new DateTime(TimeValue.timeValueHours(10).millis(), DateTimeZone.UTC).getMillis())));
Filter filter = defaultMapper.mappers().smartNameFieldMapper("date_field").rangeFilter("10:00:00", "11:00:00", true, true, null); Filter filter;
try {
SearchContext.setCurrent(new TestSearchContext());
filter = defaultMapper.mappers().smartNameFieldMapper("date_field").rangeFilter("10:00:00", "11:00:00", true, true, null);
} finally {
SearchContext.removeCurrent();
}
assertThat(filter, instanceOf(NumericRangeFilter.class)); assertThat(filter, instanceOf(NumericRangeFilter.class));
NumericRangeFilter<Long> rangeFilter = (NumericRangeFilter<Long>) filter; NumericRangeFilter<Long> rangeFilter = (NumericRangeFilter<Long>) filter;
assertThat(rangeFilter.getMax(), equalTo(new DateTime(TimeValue.timeValueHours(11).millis() + 999).getMillis())); // +999 to include the 00-01 minute assertThat(rangeFilter.getMax(), equalTo(new DateTime(TimeValue.timeValueHours(11).millis() + 999).getMillis())); // +999 to include the 00-01 minute
@ -245,7 +253,13 @@ public class SimpleDateMappingTests extends ElasticsearchSingleNodeTest {
.bytes()); .bytes());
assertThat(((LongFieldMapper.CustomLongNumericField) doc.rootDoc().getField("date_field")).numericAsString(), equalTo(Long.toString(new DateTime(TimeValue.timeValueHours(34).millis(), DateTimeZone.UTC).getMillis()))); assertThat(((LongFieldMapper.CustomLongNumericField) doc.rootDoc().getField("date_field")).numericAsString(), equalTo(Long.toString(new DateTime(TimeValue.timeValueHours(34).millis(), DateTimeZone.UTC).getMillis())));
Filter filter = defaultMapper.mappers().smartNameFieldMapper("date_field").rangeFilter("Jan 02 10:00:00", "Jan 02 11:00:00", true, true, null); Filter filter;
try {
SearchContext.setCurrent(new TestSearchContext());
filter = defaultMapper.mappers().smartNameFieldMapper("date_field").rangeFilter("Jan 02 10:00:00", "Jan 02 11:00:00", true, true, null);
} finally {
SearchContext.removeCurrent();
}
assertThat(filter, instanceOf(NumericRangeFilter.class)); assertThat(filter, instanceOf(NumericRangeFilter.class));
NumericRangeFilter<Long> rangeFilter = (NumericRangeFilter<Long>) filter; NumericRangeFilter<Long> rangeFilter = (NumericRangeFilter<Long>) filter;
assertThat(rangeFilter.getMax(), equalTo(new DateTime(TimeValue.timeValueHours(35).millis() + 999).getMillis())); // +999 to include the 00-01 minute assertThat(rangeFilter.getMax(), equalTo(new DateTime(TimeValue.timeValueHours(35).millis() + 999).getMillis())); // +999 to include the 00-01 minute

View File

@ -25,6 +25,7 @@ import org.apache.lucene.search.Query;
import org.elasticsearch.common.bytes.BytesArray; import org.elasticsearch.common.bytes.BytesArray;
import org.elasticsearch.common.compress.CompressedString; import org.elasticsearch.common.compress.CompressedString;
import org.elasticsearch.common.inject.Injector; import org.elasticsearch.common.inject.Injector;
import org.elasticsearch.common.lease.Releasables;
import org.elasticsearch.common.lucene.search.AndFilter; import org.elasticsearch.common.lucene.search.AndFilter;
import org.elasticsearch.common.lucene.search.CachedFilter; import org.elasticsearch.common.lucene.search.CachedFilter;
import org.elasticsearch.common.lucene.search.NoCacheFilter; import org.elasticsearch.common.lucene.search.NoCacheFilter;
@ -36,6 +37,7 @@ import org.elasticsearch.index.service.IndexService;
import org.elasticsearch.search.internal.SearchContext; import org.elasticsearch.search.internal.SearchContext;
import org.elasticsearch.test.ElasticsearchSingleNodeTest; import org.elasticsearch.test.ElasticsearchSingleNodeTest;
import org.elasticsearch.test.TestSearchContext; import org.elasticsearch.test.TestSearchContext;
import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@ -70,6 +72,14 @@ public class IndexQueryParserFilterCachingTests extends ElasticsearchSingleNodeT
mapperService.merge("child", new CompressedString(childMapping), true); mapperService.merge("child", new CompressedString(childMapping), true);
mapperService.documentMapper("person").parse(new BytesArray(copyToBytesFromClasspath("/org/elasticsearch/index/query/data.json"))); mapperService.documentMapper("person").parse(new BytesArray(copyToBytesFromClasspath("/org/elasticsearch/index/query/data.json")));
queryParser = injector.getInstance(IndexQueryParserService.class); queryParser = injector.getInstance(IndexQueryParserService.class);
SearchContext.setCurrent(new TestSearchContext());
}
@After
public void removeSearchContext() {
SearchContext current = SearchContext.current();
SearchContext.removeCurrent();
Releasables.close(current);
} }
private IndexQueryParserService queryParser() throws IOException { private IndexQueryParserService queryParser() throws IOException {

View File

@ -27,7 +27,9 @@ import org.elasticsearch.common.compress.CompressedString;
import org.elasticsearch.common.inject.Injector; import org.elasticsearch.common.inject.Injector;
import org.elasticsearch.index.mapper.MapperService; import org.elasticsearch.index.mapper.MapperService;
import org.elasticsearch.index.service.IndexService; import org.elasticsearch.index.service.IndexService;
import org.elasticsearch.search.internal.SearchContext;
import org.elasticsearch.test.ElasticsearchSingleNodeTest; import org.elasticsearch.test.ElasticsearchSingleNodeTest;
import org.elasticsearch.test.TestSearchContext;
import org.joda.time.DateTime; import org.joda.time.DateTime;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@ -36,7 +38,8 @@ import java.io.IOException;
import static org.elasticsearch.common.io.Streams.copyToBytesFromClasspath; import static org.elasticsearch.common.io.Streams.copyToBytesFromClasspath;
import static org.elasticsearch.common.io.Streams.copyToStringFromClasspath; import static org.elasticsearch.common.io.Streams.copyToStringFromClasspath;
import static org.hamcrest.Matchers.*; import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
/** /**
* *
@ -73,10 +76,13 @@ public class IndexQueryParserFilterDateRangeFormatTests extends ElasticsearchSin
// Test Invalid format // Test Invalid format
query = copyToStringFromClasspath("/org/elasticsearch/index/query/date_range_filter_format_invalid.json"); query = copyToStringFromClasspath("/org/elasticsearch/index/query/date_range_filter_format_invalid.json");
try { try {
SearchContext.setCurrent(new TestSearchContext());
queryParser.parse(query).query(); queryParser.parse(query).query();
fail("A Range Filter with a specific format but with an unexpected date should raise a QueryParsingException"); fail("A Range Filter with a specific format but with an unexpected date should raise a QueryParsingException");
} catch (QueryParsingException e) { } catch (QueryParsingException e) {
// We expect it // We expect it
} finally {
SearchContext.removeCurrent();
} }
} }
@ -85,7 +91,13 @@ public class IndexQueryParserFilterDateRangeFormatTests extends ElasticsearchSin
IndexQueryParserService queryParser = queryParser(); IndexQueryParserService queryParser = queryParser();
// We test 01/01/2012 from gte and 2030 for lt // We test 01/01/2012 from gte and 2030 for lt
String query = copyToStringFromClasspath("/org/elasticsearch/index/query/date_range_query_format.json"); String query = copyToStringFromClasspath("/org/elasticsearch/index/query/date_range_query_format.json");
Query parsedQuery = queryParser.parse(query).query(); Query parsedQuery;
try {
SearchContext.setCurrent(new TestSearchContext());
parsedQuery = queryParser.parse(query).query();
} finally {
SearchContext.removeCurrent();;
}
assertThat(parsedQuery, instanceOf(NumericRangeQuery.class)); assertThat(parsedQuery, instanceOf(NumericRangeQuery.class));
// Min value was 01/01/2012 (dd/MM/yyyy) // Min value was 01/01/2012 (dd/MM/yyyy)
@ -99,10 +111,13 @@ public class IndexQueryParserFilterDateRangeFormatTests extends ElasticsearchSin
// Test Invalid format // Test Invalid format
query = copyToStringFromClasspath("/org/elasticsearch/index/query/date_range_query_format_invalid.json"); query = copyToStringFromClasspath("/org/elasticsearch/index/query/date_range_query_format_invalid.json");
try { try {
SearchContext.setCurrent(new TestSearchContext());
queryParser.parse(query).query(); queryParser.parse(query).query();
fail("A Range Query with a specific format but with an unexpected date should raise a QueryParsingException"); fail("A Range Query with a specific format but with an unexpected date should raise a QueryParsingException");
} catch (QueryParsingException e) { } catch (QueryParsingException e) {
// We expect it // We expect it
} finally {
SearchContext.removeCurrent();
} }
} }
} }

View File

@ -27,7 +27,9 @@ import org.elasticsearch.common.compress.CompressedString;
import org.elasticsearch.common.inject.Injector; import org.elasticsearch.common.inject.Injector;
import org.elasticsearch.index.mapper.MapperService; import org.elasticsearch.index.mapper.MapperService;
import org.elasticsearch.index.service.IndexService; import org.elasticsearch.index.service.IndexService;
import org.elasticsearch.search.internal.SearchContext;
import org.elasticsearch.test.ElasticsearchSingleNodeTest; import org.elasticsearch.test.ElasticsearchSingleNodeTest;
import org.elasticsearch.test.TestSearchContext;
import org.joda.time.DateTime; import org.joda.time.DateTime;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@ -36,9 +38,7 @@ import java.io.IOException;
import static org.elasticsearch.common.io.Streams.copyToBytesFromClasspath; import static org.elasticsearch.common.io.Streams.copyToBytesFromClasspath;
import static org.elasticsearch.common.io.Streams.copyToStringFromClasspath; import static org.elasticsearch.common.io.Streams.copyToStringFromClasspath;
import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.*;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.lessThanOrEqualTo;
/** /**
* *
@ -74,10 +74,13 @@ public class IndexQueryParserFilterDateRangeTimezoneTests extends ElasticsearchS
query = copyToStringFromClasspath("/org/elasticsearch/index/query/date_range_filter_timezone_numeric_field.json"); query = copyToStringFromClasspath("/org/elasticsearch/index/query/date_range_filter_timezone_numeric_field.json");
try { try {
SearchContext.setCurrent(new TestSearchContext());
queryParser.parse(query).query(); queryParser.parse(query).query();
fail("A Range Filter on a numeric field with a TimeZone should raise a QueryParsingException"); fail("A Range Filter on a numeric field with a TimeZone should raise a QueryParsingException");
} catch (QueryParsingException e) { } catch (QueryParsingException e) {
// We expect it // We expect it
} finally {
SearchContext.removeCurrent();
} }
} }
@ -87,7 +90,13 @@ public class IndexQueryParserFilterDateRangeTimezoneTests extends ElasticsearchS
IndexQueryParserService queryParser = queryParser(); IndexQueryParserService queryParser = queryParser();
String query = copyToStringFromClasspath("/org/elasticsearch/index/query/date_range_query_timezone.json"); String query = copyToStringFromClasspath("/org/elasticsearch/index/query/date_range_query_timezone.json");
Query parsedQuery = queryParser.parse(query).query(); Query parsedQuery;
try {
SearchContext.setCurrent(new TestSearchContext());
parsedQuery = queryParser.parse(query).query();
} finally {
SearchContext.removeCurrent();
}
assertThat(parsedQuery, instanceOf(NumericRangeQuery.class)); assertThat(parsedQuery, instanceOf(NumericRangeQuery.class));
// Min value was 2012-01-01 (UTC) so we need to remove one hour // Min value was 2012-01-01 (UTC) so we need to remove one hour
@ -102,10 +111,13 @@ public class IndexQueryParserFilterDateRangeTimezoneTests extends ElasticsearchS
query = copyToStringFromClasspath("/org/elasticsearch/index/query/date_range_query_timezone_numeric_field.json"); query = copyToStringFromClasspath("/org/elasticsearch/index/query/date_range_query_timezone_numeric_field.json");
try { try {
SearchContext.setCurrent(new TestSearchContext());
queryParser.parse(query).query(); queryParser.parse(query).query();
fail("A Range Query on a numeric field with a TimeZone should raise a QueryParsingException"); fail("A Range Query on a numeric field with a TimeZone should raise a QueryParsingException");
} catch (QueryParsingException e) { } catch (QueryParsingException e) {
// We expect it // We expect it
} finally {
SearchContext.removeCurrent();
} }
} }
} }

View File

@ -71,6 +71,7 @@ import static org.elasticsearch.common.settings.ImmutableSettings.settingsBuilde
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
import static org.elasticsearch.common.xcontent.XContentFactory.smileBuilder; import static org.elasticsearch.common.xcontent.XContentFactory.smileBuilder;
import static org.elasticsearch.common.xcontent.XContentFactory.yamlBuilder; import static org.elasticsearch.common.xcontent.XContentFactory.yamlBuilder;
import static org.elasticsearch.index.query.FilterBuilders.rangeFilter;
import static org.elasticsearch.index.query.FilterBuilders.termFilter; import static org.elasticsearch.index.query.FilterBuilders.termFilter;
import static org.elasticsearch.index.query.QueryBuilders.boolQuery; import static org.elasticsearch.index.query.QueryBuilders.boolQuery;
import static org.elasticsearch.index.query.QueryBuilders.constantScoreQuery; import static org.elasticsearch.index.query.QueryBuilders.constantScoreQuery;
@ -240,7 +241,7 @@ public class PercolatorTests extends ElasticsearchIntegrationTest {
client().prepareIndex("test", PercolatorService.TYPE_NAME, "test3") client().prepareIndex("test", PercolatorService.TYPE_NAME, "test3")
.setSource( .setSource(
XContentFactory.jsonBuilder().startObject().field("query", XContentFactory.jsonBuilder().startObject().field("query",
constantScoreQuery(FilterBuilders.rangeFilter("field1").from(1).to(5).includeLower(true).setExecution("fielddata")) constantScoreQuery(rangeFilter("field1").from(1).to(5).includeLower(true).setExecution("fielddata"))
).endObject() ).endObject()
) )
.execute().actionGet(); .execute().actionGet();
@ -263,7 +264,7 @@ public class PercolatorTests extends ElasticsearchIntegrationTest {
client().prepareIndex("test", PercolatorService.TYPE_NAME, "1") client().prepareIndex("test", PercolatorService.TYPE_NAME, "1")
.setSource( .setSource(
XContentFactory.jsonBuilder().startObject().field("query", XContentFactory.jsonBuilder().startObject().field("query",
constantScoreQuery(FilterBuilders.rangeFilter("field1").from(1).to(5).setExecution("fielddata")) constantScoreQuery(rangeFilter("field1").from(1).to(5).setExecution("fielddata"))
).endObject() ).endObject()
).get(); ).get();
@ -558,8 +559,7 @@ public class PercolatorTests extends ElasticsearchIntegrationTest {
@Test @Test
public void percolateWithSizeField() throws Exception { public void percolateWithSizeField() throws Exception {
String mapping = XContentFactory.jsonBuilder().startObject().startObject("type1") String mapping = XContentFactory.jsonBuilder().startObject().startObject("type1")
.startObject("_size").field("enabled", true) .startObject("_size").field("enabled", true).field("stored", "yes").endObject()
.field("store", "yes").endObject()
.startObject("properties").startObject("field1").field("type", "string").endObject().endObject() .startObject("properties").startObject("field1").field("type", "string").endObject().endObject()
.endObject().endObject().string(); .endObject().endObject().string();
@ -1839,6 +1839,30 @@ public class PercolatorTests extends ElasticsearchIntegrationTest {
} }
} }
@Test
public void testPercolatorQueryWithNowRange() throws Exception {
client().admin().indices().prepareCreate("test")
.addMapping("my-type", "timestamp", "type=date")
.get();
ensureGreen();
client().prepareIndex("test", PercolatorService.TYPE_NAME, "1")
.setSource(jsonBuilder().startObject().field("query", rangeQuery("timestamp").from("now-1d").to("now")).endObject())
.get();
client().prepareIndex("test", PercolatorService.TYPE_NAME, "2")
.setSource(jsonBuilder().startObject().field("query", constantScoreQuery(rangeFilter("timestamp").from("now-1d").to("now"))).endObject())
.get();
logger.info("--> Percolate doc with field1=b");
PercolateResponse response = client().preparePercolate()
.setIndices("test").setDocumentType("my-type")
.setPercolateDoc(docBuilder().setDoc("timestamp", System.currentTimeMillis()))
.get();
assertMatchCount(response, 2l);
assertThat(response.getMatches(), arrayWithSize(2));
assertThat(convertFromTextArray(response.getMatches(), "test"), arrayContainingInAnyOrder("1", "2"));
}
void initNestedIndexAndPercolation() throws IOException { void initNestedIndexAndPercolation() throws IOException {
XContentBuilder mapping = XContentFactory.jsonBuilder(); XContentBuilder mapping = XContentFactory.jsonBuilder();
mapping.startObject().startObject("properties").startObject("companyname").field("type", "string").endObject() mapping.startObject().startObject("properties").startObject("companyname").field("type", "string").endObject()