Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix range query on date fields for number inputs #63692

Merged
merged 17 commits into from
Dec 1, 2020
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertNoFailures;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.oneOf;

public class SimpleSearchIT extends ESIntegTestCase {

Expand Down Expand Up @@ -198,6 +200,7 @@ public void testSimpleDateRange() throws Exception {
createIndex("test");
client().prepareIndex("test").setId("1").setSource("field", "2010-01-05T02:00").get();
client().prepareIndex("test").setId("2").setSource("field", "2010-01-06T02:00").get();
client().prepareIndex("test").setId("3").setSource("field", "1967-01-01T00:00").get();
ensureGreen();
refresh();
SearchResponse searchResponse = client().prepareSearch("test").setQuery(QueryBuilders.rangeQuery("field").gte("2010-01-03||+2d")
Expand All @@ -223,6 +226,23 @@ public void testSimpleDateRange() throws Exception {
searchResponse = client().prepareSearch("test").setQuery(
QueryBuilders.queryStringQuery("field:[2010-01-03||+2d TO 2010-01-04||+2d/d]")).get();
assertHitCount(searchResponse, 2L);

// a string value of "1000" should be parsed as the year 1000 and return all three docs
searchResponse = client().prepareSearch("test")
.setQuery(QueryBuilders.rangeQuery("field").gt("1000"))
.get();
assertNoFailures(searchResponse);
assertHitCount(searchResponse, 3L);

// a numeric value of 1000 should be parsed as 1000 millis since epoch and return only docs after 1970
searchResponse = client().prepareSearch("test")
.setQuery(QueryBuilders.rangeQuery("field").gt(1000))
.get();
assertNoFailures(searchResponse);
assertHitCount(searchResponse, 2L);
String[] expectedIds = new String[] {"1", "2"};
assertThat(searchResponse.getHits().getHits()[0].getId(), is(oneOf(expectedIds)));
assertThat(searchResponse.getHits().getHits()[1].getId(), is(oneOf(expectedIds)));
}

public void testSimpleTerminateAfterCount() throws Exception {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ public final class DateFieldMapper extends ParametrizedFieldMapper {
public static final String CONTENT_TYPE = "date";
public static final String DATE_NANOS_CONTENT_TYPE = "date_nanos";
public static final DateFormatter DEFAULT_DATE_TIME_FORMATTER = DateFormatter.forPattern("strict_date_optional_time||epoch_millis");
private static final DateMathParser EPOCH_MILLIS_PARSER = DateFormatter.forPattern("epoch_millis").toDateMathParser();

public enum Resolution {
MILLISECONDS(CONTENT_TYPE, NumericType.DATE) {
Expand Down Expand Up @@ -349,21 +350,29 @@ public Query rangeQuery(Object lowerTerm, Object upperTerm, boolean includeLower
throw new IllegalArgumentException("Field [" + name() + "] of type [" + typeName() +
"] does not support DISJOINT ranges");
}
DateMathParser parser = forcedDateParser == null
? dateMathParser
: forcedDateParser;
return dateRangeQuery(lowerTerm, upperTerm, includeLower, includeUpper, timeZone, parser, context, resolution, (l, u) -> {
Query query = LongPoint.newRangeQuery(name(), l, u);
if (hasDocValues()) {
Query dvQuery = SortedNumericDocValuesField.newSlowRangeQuery(name(), l, u);
query = new IndexOrDocValuesQuery(query, dvQuery);

if (context.indexSortedOnField(name())) {
query = new IndexSortSortedNumericDocValuesRangeQuery(name(), l, u, query);
return dateRangeQuery(
lowerTerm,
upperTerm,
includeLower,
includeUpper,
timeZone,
forcedDateParser,
this.dateMathParser,
context,
resolution,
(l, u) -> {
Query query = LongPoint.newRangeQuery(name(), l, u);
if (hasDocValues()) {
Query dvQuery = SortedNumericDocValuesField.newSlowRangeQuery(name(), l, u);
query = new IndexOrDocValuesQuery(query, dvQuery);

if (context.indexSortedOnField(name())) {
query = new IndexSortSortedNumericDocValuesRangeQuery(name(), l, u, query);
}
}
return query;
}
return query;
});
);
}

public static Query dateRangeQuery(
Expand All @@ -372,25 +381,37 @@ public static Query dateRangeQuery(
boolean includeLower,
boolean includeUpper,
@Nullable ZoneId timeZone,
DateMathParser parser,
DateMathParser forcedDateParser,
DateMathParser fallbackParser,
QueryShardContext context,
Resolution resolution,
BiFunction<Long, Long, Query> builder
) {
return handleNow(context, nowSupplier -> {
long l, u;
DateMathParser parser = forcedDateParser == null ? fallbackParser : forcedDateParser;
if (lowerTerm == null) {
l = Long.MIN_VALUE;
} else {
l = parseToLong(lowerTerm, !includeLower, timeZone, parser, nowSupplier, resolution);
if (lowerTerm instanceof Number && forcedDateParser == null) {
// force epoch_millis
l = resolution.convert(Instant.ofEpochMilli(((Number) lowerTerm).longValue()));
cbuescher marked this conversation as resolved.
Show resolved Hide resolved
} else {
l = parseToLong(lowerTerm, !includeLower, timeZone, parser, nowSupplier, resolution);
}
if (includeLower == false) {
++l;
}
}
if (upperTerm == null) {
u = Long.MAX_VALUE;
} else {
u = parseToLong(upperTerm, includeUpper, timeZone, parser, nowSupplier, resolution);
if (upperTerm instanceof Number && forcedDateParser == null) {
// force epoch_millis
u = resolution.convert(Instant.ofEpochMilli(((Number) upperTerm).longValue()));
} else {
u = parseToLong(upperTerm, includeUpper, timeZone, parser, nowSupplier, resolution);
}
if (includeUpper == false) {
--u;
}
Expand Down Expand Up @@ -442,13 +463,18 @@ public Query distanceFeatureQuery(Object origin, String pivot, float boost, Quer
public Relation isFieldWithinQuery(IndexReader reader,
Object from, Object to, boolean includeLower, boolean includeUpper,
ZoneId timeZone, DateMathParser dateParser, QueryRewriteContext context) throws IOException {
DateMathParser forcedDateParser = dateParser;
if (dateParser == null) {
dateParser = this.dateMathParser;
forcedDateParser = this.dateMathParser;
}

long fromInclusive = Long.MIN_VALUE;
if (from != null) {
fromInclusive = parseToLong(from, !includeLower, timeZone, dateParser, context::nowInMillis, resolution);
if (dateParser == null && from instanceof Number) {
fromInclusive = resolution.convert(Instant.ofEpochMilli(((Number) from).longValue()));
} else {
fromInclusive = parseToLong(from, !includeLower, timeZone, forcedDateParser, context::nowInMillis, resolution);
}
if (includeLower == false) {
if (fromInclusive == Long.MAX_VALUE) {
return Relation.DISJOINT;
Expand All @@ -459,7 +485,11 @@ public Relation isFieldWithinQuery(IndexReader reader,

long toInclusive = Long.MAX_VALUE;
if (to != null) {
toInclusive = parseToLong(to, includeUpper, timeZone, dateParser, context::nowInMillis, resolution);
if (dateParser == null && to instanceof Number) {
toInclusive = resolution.convert(Instant.ofEpochMilli(((Number) to).longValue()));
} else {
toInclusive = parseToLong(to, includeUpper, timeZone, forcedDateParser, context::nowInMillis, resolution);
}
if (includeUpper == false) {
if (toInclusive == Long.MIN_VALUE) {
return Relation.DISJOINT;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import com.carrotsearch.hppc.LongHashSet;
import com.carrotsearch.hppc.LongSet;

import org.apache.lucene.search.Query;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.lucene.search.Queries;
Expand Down Expand Up @@ -37,6 +38,7 @@

public class DateScriptFieldType extends AbstractScriptFieldType<DateFieldScript.LeafFactory> {
private final DateFormatter dateTimeFormatter;
private final DateMathParser dateMathParser;

DateScriptFieldType(
String name,
Expand All @@ -47,6 +49,7 @@ public class DateScriptFieldType extends AbstractScriptFieldType<DateFieldScript
) {
super(name, script, (n, params, ctx) -> scriptFactory.newFactory(n, params, ctx, dateTimeFormatter), meta);
this.dateTimeFormatter = dateTimeFormatter;
this.dateMathParser = dateTimeFormatter.toDateMathParser();
}

@Override
Expand Down Expand Up @@ -84,14 +87,7 @@ public DateScriptFieldData.Builder fielddataBuilder(String fullyQualifiedIndexNa
public Query distanceFeatureQuery(Object origin, String pivot, float boost, QueryShardContext context) {
checkAllowExpensiveQueries(context);
return DateFieldType.handleNow(context, now -> {
long originLong = DateFieldType.parseToLong(
origin,
true,
null,
dateTimeFormatter.toDateMathParser(),
now,
DateFieldMapper.Resolution.MILLISECONDS
);
long originLong = DateFieldType.parseToLong(origin, true, null, dateMathParser, now, DateFieldMapper.Resolution.MILLISECONDS);
TimeValue pivotTime = TimeValue.parseTimeValue(pivot, "distance_feature.pivot");
return new LongScriptFieldDistanceFeatureQuery(
script,
Expand Down Expand Up @@ -120,7 +116,6 @@ public Query rangeQuery(
@Nullable DateMathParser parser,
QueryShardContext context
) {
parser = parser == null ? dateTimeFormatter.toDateMathParser() : parser;
checkAllowExpensiveQueries(context);
return DateFieldType.dateRangeQuery(
lowerTerm,
Expand All @@ -129,6 +124,7 @@ public Query rangeQuery(
includeUpper,
timeZone,
parser,
dateMathParser,
context,
DateFieldMapper.Resolution.MILLISECONDS,
(l, u) -> new LongScriptFieldRangeQuery(script, leafFactory(context)::newInstance, name(), l, u)
Expand All @@ -138,14 +134,7 @@ public Query rangeQuery(
@Override
public Query termQuery(Object value, QueryShardContext context) {
return DateFieldType.handleNow(context, now -> {
long l = DateFieldType.parseToLong(
value,
false,
null,
dateTimeFormatter.toDateMathParser(),
now,
DateFieldMapper.Resolution.MILLISECONDS
);
long l = DateFieldType.parseToLong(value, false, null, dateMathParser, now, DateFieldMapper.Resolution.MILLISECONDS);
checkAllowExpensiveQueries(context);
return new LongScriptFieldTermQuery(script, leafFactory(context)::newInstance, name(), l);
});
Expand All @@ -159,16 +148,7 @@ public Query termsQuery(List<?> values, QueryShardContext context) {
return DateFieldType.handleNow(context, now -> {
LongSet terms = new LongHashSet(values.size());
for (Object value : values) {
terms.add(
DateFieldType.parseToLong(
value,
false,
null,
dateTimeFormatter.toDateMathParser(),
now,
DateFieldMapper.Resolution.MILLISECONDS
)
);
terms.add(DateFieldType.parseToLong(value, false, null, dateMathParser, now, DateFieldMapper.Resolution.MILLISECONDS));
}
checkAllowExpensiveQueries(context);
return new LongScriptFieldTermsQuery(script, leafFactory(context)::newInstance, name(), terms);
Expand Down