Settings: Remove mapping.date.round_ceil setting for date math parsing

The setting `mapping.date.round_ceil` (and the undocumented setting
`index.mapping.date.parse_upper_inclusive`) affect how date ranges using
`lte` are parsed.  In #8556 the semantics of date rounding were
solidified, eliminating the need to have different parsing functions
whether the date is inclusive or exclusive.

This change removes these legacy settings and improves the tests
for the date math parser (now at 100% coverage!). It also removes the
unnecessary function `DateMathParser.parseTimeZone` for which
the existing `DateTimeZone.forID` handles all use cases.

Any user previously using these settings can refer to the changed
semantics and change their query accordingly. This is a breaking change
because even dates without datemath previously used the different
parsing functions depending on context.

closes #8598
closes #8889
This commit is contained in:
Ryan Ernst 2014-12-10 16:28:19 -08:00
parent ceafde41e9
commit 37287284e6
19 changed files with 175 additions and 253 deletions

View File

@ -35,13 +35,15 @@ then follow by a math expression, supporting `+`, `-` and `/`
Here are some samples: `now+1h`, `now+1h+1m`, `now+1h/d`,
`2012-01-01||+1M/d`.
Note, when doing `range` type searches, and the upper value is
inclusive, the rounding will properly be rounded to the ceiling instead
of flooring it.
To change this behavior, set
`"mapping.date.round_ceil": false`.
When doing `range` type searches with rounding, the value parsed
depends on whether the end of the range is inclusive or exclusive, and
whether the beginning or end of the range. Rounding up moves to the
last millisecond of the rounding scope, and rounding down to the
first millisecond of the rounding scope. The semantics work as follows:
* `gt` - round up, and use > that value (`2014-11-18||/M` becomes `2014-11-30T23:59:59.999`, ie excluding the entire month)
* `gte` - round D down, and use >= that value (`2014-11-18||/M` becomes `2014-11-01`, ie including the entire month)
* `lt` - round D down, and use < that value (`2014-11-18||/M` becomes `2014-11-01`, ie excluding the entire month)
* `lte` - round D up, and use <= that value(`2014-11-18||/M` becomes `2014-11-30T23:59:59.999`, ie including the entire month)
[float]
[[built-in]]

View File

@ -19,23 +19,29 @@
package org.elasticsearch.common.joda;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.ElasticsearchParseException;
import org.joda.time.DateTimeZone;
import org.joda.time.MutableDateTime;
import org.joda.time.format.DateTimeFormatter;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
/**
* A parser for date/time formatted text with optional date math.
*
* The format of the datetime is configurable, and unix timestamps can also be used. Datemath
* is appended to a datetime with the following syntax:
* <code>||[+-/](\d+)?[yMwdhHms]</code>.
*/
public class DateMathParser {
private final FormatDateTimeFormatter dateTimeFormatter;
private final TimeUnit timeUnit;
public DateMathParser(FormatDateTimeFormatter dateTimeFormatter, TimeUnit timeUnit) {
if (dateTimeFormatter == null) throw new NullPointerException();
if (timeUnit == null) throw new NullPointerException();
this.dateTimeFormatter = dateTimeFormatter;
this.timeUnit = timeUnit;
}
@ -44,7 +50,7 @@ public class DateMathParser {
return parse(text, now, false, null);
}
public long parse(String text, long now, boolean roundCeil, DateTimeZone timeZone) {
public long parse(String text, long now, boolean roundUp, DateTimeZone timeZone) {
long time;
String mathString;
if (text.startsWith("now")) {
@ -52,26 +58,17 @@ public class DateMathParser {
mathString = text.substring("now".length());
} else {
int index = text.indexOf("||");
String parseString;
if (index == -1) {
parseString = text;
mathString = ""; // nothing else
} else {
parseString = text.substring(0, index);
mathString = text.substring(index + 2);
return parseDateTime(text, timeZone);
}
if (roundCeil) {
time = parseRoundCeilStringValue(parseString, timeZone);
} else {
time = parseStringValue(parseString, timeZone);
time = parseDateTime(text.substring(0, index), timeZone);
mathString = text.substring(index + 2);
if (mathString.isEmpty()) {
return time;
}
}
if (mathString.isEmpty()) {
return time;
}
return parseMath(mathString, time, roundCeil);
return parseMath(mathString, time, roundUp);
}
private long parseMath(String mathString, long time, boolean roundUp) throws ElasticsearchParseException {
@ -174,7 +171,9 @@ public class DateMathParser {
}
if (propertyToRound != null) {
if (roundUp) {
propertyToRound.roundCeiling();
// we want to go up to the next whole value, even if we are already on a rounded value
propertyToRound.add(1);
propertyToRound.roundFloor();
dateTime.addMillis(-1); // subtract 1 millisecond to get the largest inclusive value
} else {
propertyToRound.roundFloor();
@ -184,83 +183,27 @@ public class DateMathParser {
return dateTime.getMillis();
}
/**
* Get a DateTimeFormatter parser applying timezone if any.
*/
public static DateTimeFormatter getDateTimeFormatterParser(FormatDateTimeFormatter dateTimeFormatter, DateTimeZone timeZone) {
if (dateTimeFormatter == null) {
return null;
private long parseDateTime(String value, DateTimeZone timeZone) {
// first check for timestamp
if (value.length() > 4 && StringUtils.isNumeric(value)) {
try {
long time = Long.parseLong(value);
return timeUnit.toMillis(time);
} catch (NumberFormatException e) {
throw new ElasticsearchParseException("failed to parse date field [" + value + "] as timestamp", e);
}
}
DateTimeFormatter parser = dateTimeFormatter.parser();
if (timeZone != null) {
parser = parser.withZone(timeZone);
}
return parser;
}
private long parseStringValue(String value, DateTimeZone timeZone) {
try {
DateTimeFormatter parser = getDateTimeFormatterParser(dateTimeFormatter, timeZone);
return parser.parseMillis(value);
} catch (RuntimeException e) {
try {
// When date is given as a numeric value, it's a date in ms since epoch
// By definition, it's a UTC date.
long time = Long.parseLong(value);
return timeUnit.toMillis(time);
} catch (NumberFormatException e1) {
throw new ElasticsearchParseException("failed to parse date field [" + value + "], tried both date format [" + dateTimeFormatter.format() + "], and timestamp number", e);
}
}
}
} catch (IllegalArgumentException e) {
private long parseRoundCeilStringValue(String value, DateTimeZone timeZone) {
try {
// we create a date time for inclusive upper range, we "include" by default the day level data
// so something like 2011-01-01 will include the full first day of 2011.
// we also use 1970-01-01 as the base for it so we can handle searches like 10:12:55 (just time)
// since when we index those, the base is 1970-01-01
MutableDateTime dateTime = new MutableDateTime(1970, 1, 1, 23, 59, 59, 999, DateTimeZone.UTC);
DateTimeFormatter parser = getDateTimeFormatterParser(dateTimeFormatter, timeZone);
int location = parser.parseInto(dateTime, value, 0);
// if we parsed all the string value, we are good
if (location == value.length()) {
return dateTime.getMillis();
}
// if we did not manage to parse, or the year is really high year which is unreasonable
// see if its a number
if (location <= 0 || dateTime.getYear() > 5000) {
try {
long time = Long.parseLong(value);
return timeUnit.toMillis(time);
} catch (NumberFormatException e1) {
throw new ElasticsearchParseException("failed to parse date field [" + value + "], tried both date format [" + dateTimeFormatter.format() + "], and timestamp number");
}
}
return dateTime.getMillis();
} catch (RuntimeException e) {
try {
long time = Long.parseLong(value);
return timeUnit.toMillis(time);
} catch (NumberFormatException e1) {
throw new ElasticsearchParseException("failed to parse date field [" + value + "], tried both date format [" + dateTimeFormatter.format() + "], and timestamp number", e);
}
}
}
public static DateTimeZone parseZone(String text) throws IOException {
int index = text.indexOf(':');
if (index != -1) {
int beginIndex = text.charAt(0) == '+' ? 1 : 0;
// format like -02:30
return DateTimeZone.forOffsetHoursMinutes(
Integer.parseInt(text.substring(beginIndex, index)),
Integer.parseInt(text.substring(index + 1))
);
} else {
// id, listed here: http://joda-time.sourceforge.net/timezones.html
return DateTimeZone.forID(text);
throw new ElasticsearchParseException("failed to parse date field [" + value + "] with format [" + dateTimeFormatter.format() + "]", e);
}
}

View File

@ -71,9 +71,6 @@ import static org.elasticsearch.index.mapper.MapperBuilders.dateField;
import static org.elasticsearch.index.mapper.core.TypeParsers.parseDateTimeFormatter;
import static org.elasticsearch.index.mapper.core.TypeParsers.parseNumberField;
/**
*
*/
public class DateFieldMapper extends NumberFieldMapper<Long> {
public static final String CONTENT_TYPE = "date";
@ -90,7 +87,6 @@ public class DateFieldMapper extends NumberFieldMapper<Long> {
public static final String NULL_VALUE = null;
public static final TimeUnit TIME_UNIT = TimeUnit.MILLISECONDS;
public static final boolean ROUND_CEIL = true;
}
public static class Builder extends NumberFieldMapper.Builder<Builder, DateFieldMapper> {
@ -127,17 +123,12 @@ public class DateFieldMapper extends NumberFieldMapper<Long> {
@Override
public DateFieldMapper build(BuilderContext context) {
boolean roundCeil = Defaults.ROUND_CEIL;
if (context.indexSettings() != null) {
Settings settings = context.indexSettings();
roundCeil = settings.getAsBoolean("index.mapping.date.round_ceil", settings.getAsBoolean("index.mapping.date.parse_upper_inclusive", Defaults.ROUND_CEIL));
}
fieldType.setOmitNorms(fieldType.omitNorms() && boost == 1.0f);
if (!locale.equals(dateTimeFormatter.locale())) {
dateTimeFormatter = new FormatDateTimeFormatter(dateTimeFormatter.format(), dateTimeFormatter.parser(), dateTimeFormatter.printer(), locale);
}
DateFieldMapper fieldMapper = new DateFieldMapper(buildNames(context), dateTimeFormatter,
fieldType.numericPrecisionStep(), boost, fieldType, docValues, nullValue, timeUnit, roundCeil, ignoreMalformed(context), coerce(context),
fieldType.numericPrecisionStep(), boost, fieldType, docValues, nullValue, timeUnit, ignoreMalformed(context), coerce(context),
postingsProvider, docValuesProvider, similarity, normsLoading, fieldDataSettings, context.indexSettings(),
multiFieldsBuilder.build(this, context), copyTo);
fieldMapper.includeInAll(includeInAll);
@ -182,15 +173,6 @@ public class DateFieldMapper extends NumberFieldMapper<Long> {
protected FormatDateTimeFormatter dateTimeFormatter;
// Triggers rounding up of the upper bound for range queries and filters if
// set to true.
// Rounding up a date here has the following meaning: If a date is not
// defined with full precision, for example, no milliseconds given, the date
// will be filled up to the next larger date with that precision.
// Example: An upper bound given as "2000-01-01", will be converted to
// "2000-01-01T23.59.59.999"
private final boolean roundCeil;
private final DateMathParser dateMathParser;
private String nullValue;
@ -198,7 +180,7 @@ public class DateFieldMapper extends NumberFieldMapper<Long> {
protected final TimeUnit timeUnit;
protected DateFieldMapper(Names names, FormatDateTimeFormatter dateTimeFormatter, int precisionStep, float boost, FieldType fieldType, Boolean docValues,
String nullValue, TimeUnit timeUnit, boolean roundCeil, Explicit<Boolean> ignoreMalformed,Explicit<Boolean> coerce,
String nullValue, TimeUnit timeUnit, Explicit<Boolean> ignoreMalformed,Explicit<Boolean> coerce,
PostingsFormatProvider postingsProvider, DocValuesFormatProvider docValuesProvider, SimilarityProvider similarity,
Loading normsLoading, @Nullable Settings fieldDataSettings, Settings indexSettings, MultiFields multiFields, CopyTo copyTo) {
@ -208,7 +190,6 @@ public class DateFieldMapper extends NumberFieldMapper<Long> {
this.dateTimeFormatter = dateTimeFormatter;
this.nullValue = nullValue;
this.timeUnit = timeUnit;
this.roundCeil = roundCeil;
this.dateMathParser = new DateMathParser(dateTimeFormatter, timeUnit);
}
@ -328,8 +309,7 @@ public class DateFieldMapper extends NumberFieldMapper<Long> {
if (forcedDateParser != null) {
dateParser = forcedDateParser;
}
boolean roundUp = inclusive && roundCeil;
return dateParser.parse(value, now, roundUp, zone);
return dateParser.parse(value, now, inclusive, zone);
}
@Override

View File

@ -49,8 +49,6 @@ import static org.elasticsearch.index.mapper.MapperBuilders.timestamp;
import static org.elasticsearch.index.mapper.core.TypeParsers.parseDateTimeFormatter;
import static org.elasticsearch.index.mapper.core.TypeParsers.parseField;
/**
*/
public class TimestampFieldMapper extends DateFieldMapper implements InternalMapper, RootMapper {
public static final String NAME = "_timestamp";
@ -123,12 +121,7 @@ public class TimestampFieldMapper extends DateFieldMapper implements InternalMap
assert fieldType.stored();
fieldType.setStored(false);
}
boolean roundCeil = Defaults.ROUND_CEIL;
if (context.indexSettings() != null) {
Settings settings = context.indexSettings();
roundCeil = settings.getAsBoolean("index.mapping.date.round_ceil", settings.getAsBoolean("index.mapping.date.parse_upper_inclusive", Defaults.ROUND_CEIL));
}
return new TimestampFieldMapper(fieldType, docValues, enabledState, path, dateTimeFormatter, defaultTimestamp, roundCeil,
return new TimestampFieldMapper(fieldType, docValues, enabledState, path, dateTimeFormatter, defaultTimestamp,
ignoreMalformed(context), coerce(context), postingsProvider, docValuesProvider, normsLoading, fieldDataSettings, context.indexSettings());
}
}
@ -173,18 +166,18 @@ public class TimestampFieldMapper extends DateFieldMapper implements InternalMap
public TimestampFieldMapper(Settings indexSettings) {
this(new FieldType(defaultFieldType(indexSettings)), null, Defaults.ENABLED, Defaults.PATH, Defaults.DATE_TIME_FORMATTER, Defaults.DEFAULT_TIMESTAMP,
Defaults.ROUND_CEIL, Defaults.IGNORE_MALFORMED, Defaults.COERCE, null, null, null, null, indexSettings);
Defaults.IGNORE_MALFORMED, Defaults.COERCE, null, null, null, null, indexSettings);
}
protected TimestampFieldMapper(FieldType fieldType, Boolean docValues, EnabledAttributeMapper enabledState, String path,
FormatDateTimeFormatter dateTimeFormatter, String defaultTimestamp, boolean roundCeil,
FormatDateTimeFormatter dateTimeFormatter, String defaultTimestamp,
Explicit<Boolean> ignoreMalformed, Explicit<Boolean> coerce, PostingsFormatProvider postingsProvider,
DocValuesFormatProvider docValuesProvider, Loading normsLoading,
@Nullable Settings fieldDataSettings, Settings indexSettings) {
super(new Names(Defaults.NAME, Defaults.NAME, Defaults.NAME, Defaults.NAME), dateTimeFormatter,
Defaults.PRECISION_STEP_64_BIT, Defaults.BOOST, fieldType, docValues,
Defaults.NULL_VALUE, TimeUnit.MILLISECONDS /*always milliseconds*/,
roundCeil, ignoreMalformed, coerce, postingsProvider, docValuesProvider, null, normsLoading, fieldDataSettings,
ignoreMalformed, coerce, postingsProvider, docValuesProvider, null, normsLoading, fieldDataSettings,
indexSettings, MultiFields.empty(), null);
this.enabledState = enabledState;
this.path = path;

View File

@ -29,7 +29,6 @@ import org.apache.lucene.search.Query;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.joda.DateMathParser;
import org.elasticsearch.common.lucene.search.Queries;
import org.elasticsearch.common.regex.Regex;
import org.elasticsearch.common.settings.Settings;
@ -38,6 +37,7 @@ import org.elasticsearch.common.util.LocaleUtils;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.analysis.NamedAnalyzer;
import org.elasticsearch.index.query.support.QueryParsers;
import org.joda.time.DateTimeZone;
import java.io.IOException;
import java.util.Locale;
@ -197,7 +197,7 @@ public class QueryStringQueryParser implements QueryParser {
qpSettings.locale(LocaleUtils.parse(localeStr));
} else if ("time_zone".equals(currentFieldName)) {
try {
qpSettings.timeZone(DateMathParser.parseZone(parser.text()));
qpSettings.timeZone(DateTimeZone.forID(parser.text()));
} catch (IllegalArgumentException e) {
throw new QueryParsingException(parseContext.index(), "[query_string] time_zone [" + parser.text() + "] is unknown");
}

View File

@ -101,7 +101,7 @@ public class RangeFilterParser implements FilterParser {
to = parser.objectBytes();
includeUpper = true;
} else if ("time_zone".equals(currentFieldName) || "timeZone".equals(currentFieldName)) {
timeZone = DateMathParser.parseZone(parser.text());
timeZone = DateTimeZone.forID(parser.text());
} else if ("format".equals(currentFieldName)) {
forcedDateParser = new DateMathParser(Joda.forPattern(parser.text()), DateFieldMapper.Defaults.TIME_UNIT);
} else {

View File

@ -102,7 +102,7 @@ public class RangeQueryParser implements QueryParser {
to = parser.objectBytes();
includeUpper = true;
} else if ("time_zone".equals(currentFieldName) || "timeZone".equals(currentFieldName)) {
timeZone = DateMathParser.parseZone(parser.text());
timeZone = DateTimeZone.forID(parser.text());
} else if ("_name".equals(currentFieldName)) {
queryName = parser.text();
} else if ("format".equals(currentFieldName)) {

View File

@ -100,11 +100,11 @@ public class DateHistogramParser implements Aggregator.Parser {
continue;
} else if (token == XContentParser.Token.VALUE_STRING) {
if ("time_zone".equals(currentFieldName) || "timeZone".equals(currentFieldName)) {
preZone = DateMathParser.parseZone(parser.text());
preZone = DateTimeZone.forID(parser.text());
} else if ("pre_zone".equals(currentFieldName) || "preZone".equals(currentFieldName)) {
preZone = DateMathParser.parseZone(parser.text());
preZone = DateTimeZone.forID(parser.text());
} else if ("post_zone".equals(currentFieldName) || "postZone".equals(currentFieldName)) {
postZone = DateMathParser.parseZone(parser.text());
postZone = DateTimeZone.forID(parser.text());
} else if ("pre_offset".equals(currentFieldName) || "preOffset".equals(currentFieldName)) {
preOffset = parseOffset(parser.text());
} else if ("post_offset".equals(currentFieldName) || "postOffset".equals(currentFieldName)) {

View File

@ -20,11 +20,14 @@
package org.elasticsearch.common.joda;
import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.ExceptionsHelper;
import org.elasticsearch.test.ElasticsearchTestCase;
import org.joda.time.DateTimeZone;
import java.util.concurrent.TimeUnit;
import static org.hamcrest.Matchers.equalTo;
public class DateMathParserTests extends ElasticsearchTestCase {
FormatDateTimeFormatter formatter = Joda.forPattern("dateOptionalTime");
DateMathParser parser = new DateMathParser(formatter, TimeUnit.MILLISECONDS);
@ -34,16 +37,19 @@ public class DateMathParserTests extends ElasticsearchTestCase {
}
void assertDateMathEquals(String toTest, String expected, long now, boolean roundUp, DateTimeZone timeZone) {
DateMathParser parser = new DateMathParser(Joda.forPattern("dateOptionalTime"), TimeUnit.MILLISECONDS);
long gotMillis = parser.parse(toTest, now, roundUp, null);
long gotMillis = parser.parse(toTest, now, roundUp, timeZone);
assertDateEquals(gotMillis, toTest, expected);
}
void assertDateEquals(long gotMillis, String original, String expected) {
long expectedMillis = parser.parse(expected, 0);
if (gotMillis != expectedMillis) {
fail("Date math not equal\n" +
"Original : " + toTest + "\n" +
"Parsed : " + formatter.printer().print(gotMillis) + "\n" +
"Expected : " + expected + "\n" +
"Expected milliseconds : " + expectedMillis + "\n" +
"Actual milliseconds : " + gotMillis + "\n");
"Original : " + original + "\n" +
"Parsed : " + formatter.printer().print(gotMillis) + "\n" +
"Expected : " + expected + "\n" +
"Expected milliseconds : " + expectedMillis + "\n" +
"Actual milliseconds : " + gotMillis + "\n");
}
}
@ -57,6 +63,23 @@ public class DateMathParserTests extends ElasticsearchTestCase {
assertDateMathEquals("2014-05-30T20:21:35.123", "2014-05-30T20:21:35.123");
}
public void testRoundingDoesNotAffectExactDate() {
assertDateMathEquals("2014-11-12T22:55:00Z", "2014-11-12T22:55:00Z", 0, true, null);
assertDateMathEquals("2014-11-12T22:55:00Z", "2014-11-12T22:55:00Z", 0, false, null);
}
public void testTimezone() {
// timezone works within date format
assertDateMathEquals("2014-05-30T20:21+02:00", "2014-05-30T18:21:00.000");
// but also externally
assertDateMathEquals("2014-05-30T20:21", "2014-05-30T18:21:00.000", 0, false, DateTimeZone.forID("+02:00"));
// and timezone in the date has priority
assertDateMathEquals("2014-05-30T20:21+03:00", "2014-05-30T17:21:00.000", 0, false, DateTimeZone.forID("-08:00"));
assertDateMathEquals("2014-05-30T20:21Z", "2014-05-30T20:21:00.000", 0, false, DateTimeZone.forID("-08:00"));
}
public void testBasicMath() {
assertDateMathEquals("2014-11-18||+y", "2015-11-18");
assertDateMathEquals("2014-11-18||-2y", "2012-11-18");
@ -82,6 +105,10 @@ public class DateMathParserTests extends ElasticsearchTestCase {
assertDateMathEquals("2014-11-18T14:27:32||-3600s", "2014-11-18T13:27:32");
}
public void testLenientEmptyMath() {
assertDateMathEquals("2014-05-30T20:21||", "2014-05-30T20:21:00.000");
}
public void testMultipleAdjustments() {
assertDateMathEquals("2014-11-18||+1M-1M", "2014-11-18");
assertDateMathEquals("2014-11-18||+1M-1m", "2014-12-17T23:59");
@ -97,13 +124,16 @@ public class DateMathParserTests extends ElasticsearchTestCase {
assertDateMathEquals("now+M", "2014-12-18T14:27:32", now, false, null);
assertDateMathEquals("now-2d", "2014-11-16T14:27:32", now, false, null);
assertDateMathEquals("now/m", "2014-11-18T14:27", now, false, null);
// timezone does not affect now
assertDateMathEquals("now/m", "2014-11-18T14:27", now, false, DateTimeZone.forID("+02:00"));
}
public void testRounding() {
assertDateMathEquals("2014-11-18||/y", "2014-01-01", 0, false, null);
assertDateMathEquals("2014-11-18||/y", "2014-12-31T23:59:59.999", 0, true, null);
assertDateMathEquals("2014||/y", "2014-01-01", 0, false, null);
assertDateMathEquals("2014||/y", "2014-12-31T23:59:59.999", 0, true, null);
assertDateMathEquals("2014-01-01T00:00:00.001||/y", "2014-12-31T23:59:59.999", 0, true, null);
assertDateMathEquals("2014-11-18||/M", "2014-11-01", 0, false, null);
assertDateMathEquals("2014-11-18||/M", "2014-11-30T23:59:59.999", 0, true, null);
@ -140,20 +170,47 @@ public class DateMathParserTests extends ElasticsearchTestCase {
assertDateMathEquals("2014-11-18T14:27:32||/s", "2014-11-18T14:27:32.999", 0, true, null);
}
void assertParseException(String msg, String date) {
public void testTimestamps() {
assertDateMathEquals("1418248078000", "2014-12-10T21:47:58.000");
// timezone does not affect timestamps
assertDateMathEquals("1418248078000", "2014-12-10T21:47:58.000", 0, false, DateTimeZone.forID("-08:00"));
// datemath still works on timestamps
assertDateMathEquals("1418248078000||/m", "2014-12-10T21:47:00.000");
// also check other time units
DateMathParser parser = new DateMathParser(Joda.forPattern("dateOptionalTime"), TimeUnit.SECONDS);
long datetime = parser.parse("1418248078", 0);
assertDateEquals(datetime, "1418248078", "2014-12-10T21:47:58.000");
// a timestamp before 10000 is a year
assertDateMathEquals("9999", "9999-01-01T00:00:00.000");
// 10000 is the first timestamp
assertDateMathEquals("10000", "1970-01-01T00:00:10.000");
// but 10000 with T is still a date format
assertDateMathEquals("10000T", "10000-01-01T00:00:00.000");
}
void assertParseException(String msg, String date, String exc) {
try {
parser.parse(date, 0);
fail("Date: " + date + "\n" + msg);
} catch (ElasticsearchParseException e) {
// expected
assertThat(ExceptionsHelper.detailedMessage(e).contains(exc), equalTo(true));
}
}
public void testIllegalMathFormat() {
assertParseException("Expected date math unsupported operator exception", "2014-11-18||*5");
assertParseException("Expected date math incompatible rounding exception", "2014-11-18||/2m");
assertParseException("Expected date math illegal unit type exception", "2014-11-18||+2a");
assertParseException("Expected date math truncation exception", "2014-11-18||+12");
assertParseException("Expected date math truncation exception", "2014-11-18||-");
assertParseException("Expected date math unsupported operator exception", "2014-11-18||*5", "operator not supported");
assertParseException("Expected date math incompatible rounding exception", "2014-11-18||/2m", "rounding");
assertParseException("Expected date math illegal unit type exception", "2014-11-18||+2a", "unit [a] not supported");
assertParseException("Expected date math truncation exception", "2014-11-18||+12", "truncated");
assertParseException("Expected date math truncation exception", "2014-11-18||-", "truncated");
}
public void testIllegalDateFormat() {
assertParseException("Expected bad timestamp exception", Long.toString(Long.MAX_VALUE) + "0", "timestamp");
assertParseException("Expected bad date format exception", "123bogus", "with format");
}
}

View File

@ -104,20 +104,6 @@ public class SimpleCountTests extends ElasticsearchIntegrationTest {
assertHitCount(countResponse, 1l);
}
@Test
public void simpleDateMathTests() throws Exception {
createIndex("test");
client().prepareIndex("test", "type1", "1").setSource("field", "2010-01-05T02:00").execute().actionGet();
client().prepareIndex("test", "type1", "2").setSource("field", "2010-01-06T02:00").execute().actionGet();
ensureGreen();
refresh();
CountResponse countResponse = client().prepareCount("test").setQuery(QueryBuilders.rangeQuery("field").gte("2010-01-03||+2d").lte("2010-01-04||+2d")).execute().actionGet();
assertHitCount(countResponse, 2l);
countResponse = client().prepareCount("test").setQuery(QueryBuilders.queryStringQuery("field:[2010-01-03||+2d TO 2010-01-04||+2d]")).execute().actionGet();
assertHitCount(countResponse, 2l);
}
@Test
public void simpleCountEarlyTerminationTests() throws Exception {
// set up one shard only to test early termination
@ -130,29 +116,26 @@ public class SimpleCountTests extends ElasticsearchIntegrationTest {
for (int i = 1; i <= max; i++) {
String id = String.valueOf(i);
docbuilders.add(client().prepareIndex("test", "type1", id).setSource("field", "2010-01-"+ id +"T02:00"));
docbuilders.add(client().prepareIndex("test", "type1", id).setSource("field", i));
}
indexRandom(true, docbuilders);
ensureGreen();
refresh();
String upperBound = "2010-01-" + String.valueOf(max+1) + "||+2d";
String lowerBound = "2009-12-01||+2d";
// sanity check
CountResponse countResponse = client().prepareCount("test").setQuery(QueryBuilders.rangeQuery("field").gte(lowerBound).lte(upperBound)).execute().actionGet();
CountResponse countResponse = client().prepareCount("test").setQuery(QueryBuilders.rangeQuery("field").gte(1).lte(max)).execute().actionGet();
assertHitCount(countResponse, max);
// threshold <= actual count
for (int i = 1; i <= max; i++) {
countResponse = client().prepareCount("test").setQuery(QueryBuilders.rangeQuery("field").gte(lowerBound).lte(upperBound)).setTerminateAfter(i).execute().actionGet();
countResponse = client().prepareCount("test").setQuery(QueryBuilders.rangeQuery("field").gte(1).lte(max)).setTerminateAfter(i).execute().actionGet();
assertHitCount(countResponse, i);
assertTrue(countResponse.terminatedEarly());
}
// threshold > actual count
countResponse = client().prepareCount("test").setQuery(QueryBuilders.rangeQuery("field").gte(lowerBound).lte(upperBound)).setTerminateAfter(max + randomIntBetween(1, max)).execute().actionGet();
countResponse = client().prepareCount("test").setQuery(QueryBuilders.rangeQuery("field").gte(1).lte(max)).setTerminateAfter(max + randomIntBetween(1, max)).execute().actionGet();
assertHitCount(countResponse, max);
assertFalse(countResponse.terminatedEarly());
}

View File

@ -99,29 +99,15 @@ public class SimpleExistsTests extends ElasticsearchIntegrationTest {
assertExists(existsResponse, true);
}
@Test
public void simpleDateMathTests() throws Exception {
createIndex("test");
client().prepareIndex("test", "type1", "1").setSource("field", "2010-01-05T02:00").execute().actionGet();
client().prepareIndex("test", "type1", "2").setSource("field", "2010-01-06T02:00").execute().actionGet();
ensureGreen();
refresh();
ExistsResponse existsResponse = client().prepareExists("test").setQuery(QueryBuilders.rangeQuery("field").gte("2010-01-03||+2d").lte("2010-01-04||+2d")).execute().actionGet();
assertExists(existsResponse, true);
existsResponse = client().prepareExists("test").setQuery(QueryBuilders.queryStringQuery("field:[2010-01-03||+2d TO 2010-01-04||+2d]")).execute().actionGet();
assertExists(existsResponse, true);
}
@Test
public void simpleNonExistenceTests() throws Exception {
createIndex("test");
client().prepareIndex("test", "type1", "1").setSource("field", "2010-01-05T02:00").execute().actionGet();
client().prepareIndex("test", "type1", "2").setSource("field", "2010-01-06T02:00").execute().actionGet();
client().prepareIndex("test", "type1", "1").setSource("field", 2).execute().actionGet();
client().prepareIndex("test", "type1", "2").setSource("field", 5).execute().actionGet();
client().prepareIndex("test", "type", "XXX1").setSource("field", "value").execute().actionGet();
ensureGreen();
refresh();
ExistsResponse existsResponse = client().prepareExists("test").setQuery(QueryBuilders.rangeQuery("field").gte("2010-01-07||+2d").lte("2010-01-21||+2d")).execute().actionGet();
ExistsResponse existsResponse = client().prepareExists("test").setQuery(QueryBuilders.rangeQuery("field").gte(6).lte(8)).execute().actionGet();
assertExists(existsResponse, false);
existsResponse = client().prepareExists("test").setQuery(QueryBuilders.queryStringQuery("_id:XXY*").lowercaseExpandedTerms(false)).execute().actionGet();

View File

@ -232,7 +232,7 @@ public class SimpleDateMappingTests extends ElasticsearchSingleNodeTest {
}
assertThat(filter, instanceOf(NumericRangeFilter.class));
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()).getMillis()));
assertThat(rangeFilter.getMin(), equalTo(new DateTime(TimeValue.timeValueHours(10).millis()).getMillis()));
}
@ -262,7 +262,7 @@ public class SimpleDateMappingTests extends ElasticsearchSingleNodeTest {
}
assertThat(filter, instanceOf(NumericRangeFilter.class));
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()).getMillis()));
assertThat(rangeFilter.getMin(), equalTo(new DateTime(TimeValue.timeValueHours(34).millis()).getMillis()));
}

View File

@ -5,7 +5,7 @@
"born" : {
"gte": "2012-01-01",
"lte": "now",
"time_zone": "+1:00"
"time_zone": "+01:00"
}
}
}

View File

@ -5,7 +5,7 @@
"age" : {
"gte": "0",
"lte": "100",
"time_zone": "-1:00"
"time_zone": "-01:00"
}
}
}

View File

@ -3,7 +3,7 @@
"born" : {
"gte": "2012-01-01",
"lte": "now",
"time_zone": "+1:00"
"time_zone": "+01:00"
}
}
}

View File

@ -3,7 +3,7 @@
"age" : {
"gte": "0",
"lte": "100",
"time_zone": "-1:00"
"time_zone": "-01:00"
}
}
}

View File

@ -1047,7 +1047,7 @@ public class DateHistogramTests extends ElasticsearchIntegrationTest {
.setQuery(matchAllQuery())
.addAggregation(dateHistogram("date_histo")
.field("date")
.preZone("-2:00")
.preZone("-02:00")
.interval(DateHistogram.Interval.DAY)
.format("yyyy-MM-dd"))
.execute().actionGet();
@ -1082,7 +1082,7 @@ public class DateHistogramTests extends ElasticsearchIntegrationTest {
.setQuery(matchAllQuery())
.addAggregation(dateHistogram("date_histo")
.field("date")
.preZone("-2:00")
.preZone("-02:00")
.interval(DateHistogram.Interval.DAY)
.preZoneAdjustLargeInterval(true)
.format("yyyy-MM-dd'T'HH:mm:ss"))

View File

@ -2252,17 +2252,17 @@ public class SimpleQueryTests extends ElasticsearchIntegrationTest {
// We define a time zone to be applied to the filter and from/to have no time zone
searchResponse = client().prepareSearch("test")
.setQuery(QueryBuilders.filteredQuery(matchAllQuery(), FilterBuilders.rangeFilter("date").from("2014-01-01T03:00:00").to("2014-01-01T03:59:00").timeZone("+3:00")))
.setQuery(QueryBuilders.filteredQuery(matchAllQuery(), FilterBuilders.rangeFilter("date").from("2014-01-01T03:00:00").to("2014-01-01T03:59:00").timeZone("+03:00")))
.get();
assertHitCount(searchResponse, 1l);
assertThat(searchResponse.getHits().getAt(0).getId(), is("1"));
searchResponse = client().prepareSearch("test")
.setQuery(QueryBuilders.filteredQuery(matchAllQuery(), FilterBuilders.rangeFilter("date").from("2014-01-01T02:00:00").to("2014-01-01T02:59:00").timeZone("+3:00")))
.setQuery(QueryBuilders.filteredQuery(matchAllQuery(), FilterBuilders.rangeFilter("date").from("2014-01-01T02:00:00").to("2014-01-01T02:59:00").timeZone("+03:00")))
.get();
assertHitCount(searchResponse, 1l);
assertThat(searchResponse.getHits().getAt(0).getId(), is("2"));
searchResponse = client().prepareSearch("test")
.setQuery(QueryBuilders.filteredQuery(matchAllQuery(), FilterBuilders.rangeFilter("date").from("2014-01-01T04:00:00").to("2014-01-01T04:59:00").timeZone("+3:00")))
.setQuery(QueryBuilders.filteredQuery(matchAllQuery(), FilterBuilders.rangeFilter("date").from("2014-01-01T04:00:00").to("2014-01-01T04:59:00").timeZone("+03:00")))
.get();
assertHitCount(searchResponse, 1l);
assertThat(searchResponse.getHits().getAt(0).getId(), is("3"));
@ -2270,7 +2270,7 @@ public class SimpleQueryTests extends ElasticsearchIntegrationTest {
// When we use long values, it means we have ms since epoch UTC based so we don't apply any transformation
try {
client().prepareSearch("test")
.setQuery(QueryBuilders.filteredQuery(matchAllQuery(), FilterBuilders.rangeFilter("date").from(1388534400000L).to(1388537940999L).timeZone("+1:00")))
.setQuery(QueryBuilders.filteredQuery(matchAllQuery(), FilterBuilders.rangeFilter("date").from(1388534400000L).to(1388537940999L).timeZone("+01:00")))
.get();
fail("A Range Filter using ms since epoch with a TimeZone should raise a QueryParsingException");
} catch (SearchPhaseExecutionException e) {
@ -2278,13 +2278,13 @@ public class SimpleQueryTests extends ElasticsearchIntegrationTest {
}
searchResponse = client().prepareSearch("test")
.setQuery(QueryBuilders.filteredQuery(matchAllQuery(), FilterBuilders.rangeFilter("date").from("2014-01-01").to("2014-01-01T00:59:00").timeZone("-1:00")))
.setQuery(QueryBuilders.filteredQuery(matchAllQuery(), FilterBuilders.rangeFilter("date").from("2014-01-01").to("2014-01-01T00:59:00").timeZone("-01:00")))
.get();
assertHitCount(searchResponse, 1l);
assertThat(searchResponse.getHits().getAt(0).getId(), is("3"));
searchResponse = client().prepareSearch("test")
.setQuery(QueryBuilders.filteredQuery(matchAllQuery(), FilterBuilders.rangeFilter("date").from("now/d-1d").timeZone("+1:00")))
.setQuery(QueryBuilders.filteredQuery(matchAllQuery(), FilterBuilders.rangeFilter("date").from("now/d-1d").timeZone("+01:00")))
.get();
assertHitCount(searchResponse, 1l);
assertThat(searchResponse.getHits().getAt(0).getId(), is("4"));
@ -2292,7 +2292,7 @@ public class SimpleQueryTests extends ElasticsearchIntegrationTest {
// A Range Filter on a numeric field with a TimeZone should raise an exception
try {
client().prepareSearch("test")
.setQuery(QueryBuilders.filteredQuery(matchAllQuery(), FilterBuilders.rangeFilter("num").from("0").to("4").timeZone("-1:00")))
.setQuery(QueryBuilders.filteredQuery(matchAllQuery(), FilterBuilders.rangeFilter("num").from("0").to("4").timeZone("-01:00")))
.get();
fail("A Range Filter on a numeric field with a TimeZone should raise a QueryParsingException");
} catch (SearchPhaseExecutionException e) {
@ -2348,17 +2348,17 @@ public class SimpleQueryTests extends ElasticsearchIntegrationTest {
// We define a time zone to be applied to the filter and from/to have no time zone
searchResponse = client().prepareSearch("test")
.setQuery(QueryBuilders.rangeQuery("date").from("2014-01-01T03:00:00").to("2014-01-01T03:59:00").timeZone("+3:00"))
.setQuery(QueryBuilders.rangeQuery("date").from("2014-01-01T03:00:00").to("2014-01-01T03:59:00").timeZone("+03:00"))
.get();
assertHitCount(searchResponse, 1l);
assertThat(searchResponse.getHits().getAt(0).getId(), is("1"));
searchResponse = client().prepareSearch("test")
.setQuery(QueryBuilders.rangeQuery("date").from("2014-01-01T02:00:00").to("2014-01-01T02:59:00").timeZone("+3:00"))
.setQuery(QueryBuilders.rangeQuery("date").from("2014-01-01T02:00:00").to("2014-01-01T02:59:00").timeZone("+03:00"))
.get();
assertHitCount(searchResponse, 1l);
assertThat(searchResponse.getHits().getAt(0).getId(), is("2"));
searchResponse = client().prepareSearch("test")
.setQuery(QueryBuilders.rangeQuery("date").from("2014-01-01T04:00:00").to("2014-01-01T04:59:00").timeZone("+3:00"))
.setQuery(QueryBuilders.rangeQuery("date").from("2014-01-01T04:00:00").to("2014-01-01T04:59:00").timeZone("+03:00"))
.get();
assertHitCount(searchResponse, 1l);
assertThat(searchResponse.getHits().getAt(0).getId(), is("3"));
@ -2366,7 +2366,7 @@ public class SimpleQueryTests extends ElasticsearchIntegrationTest {
// When we use long values, it means we have ms since epoch UTC based so we don't apply any transformation
try {
client().prepareSearch("test")
.setQuery(QueryBuilders.rangeQuery("date").from(1388534400000L).to(1388537940999L).timeZone("+1:00"))
.setQuery(QueryBuilders.rangeQuery("date").from(1388534400000L).to(1388537940999L).timeZone("+01:00"))
.get();
fail("A Range Filter using ms since epoch with a TimeZone should raise a QueryParsingException");
} catch (SearchPhaseExecutionException e) {
@ -2374,13 +2374,13 @@ public class SimpleQueryTests extends ElasticsearchIntegrationTest {
}
searchResponse = client().prepareSearch("test")
.setQuery(QueryBuilders.rangeQuery("date").from("2014-01-01").to("2014-01-01T00:59:00").timeZone("-1:00"))
.setQuery(QueryBuilders.rangeQuery("date").from("2014-01-01").to("2014-01-01T00:59:00").timeZone("-01:00"))
.get();
assertHitCount(searchResponse, 1l);
assertThat(searchResponse.getHits().getAt(0).getId(), is("3"));
searchResponse = client().prepareSearch("test")
.setQuery(QueryBuilders.rangeQuery("date").from("now/d-1d").timeZone("+1:00"))
.setQuery(QueryBuilders.rangeQuery("date").from("now/d-1d").timeZone("+01:00"))
.get();
assertHitCount(searchResponse, 1l);
assertThat(searchResponse.getHits().getAt(0).getId(), is("4"));
@ -2388,7 +2388,7 @@ public class SimpleQueryTests extends ElasticsearchIntegrationTest {
// A Range Filter on a numeric field with a TimeZone should raise an exception
try {
client().prepareSearch("test")
.setQuery(QueryBuilders.rangeQuery("num").from("0").to("4").timeZone("-1:00"))
.setQuery(QueryBuilders.rangeQuery("num").from("0").to("4").timeZone("-01:00"))
.get();
fail("A Range Filter on a numeric field with a TimeZone should raise a QueryParsingException");
} catch (SearchPhaseExecutionException e) {

View File

@ -126,53 +126,34 @@ public class SimpleSearchTests extends ElasticsearchIntegrationTest {
}
@Test
public void simpleDateRangeWithUpperInclusiveEnabledTests() throws Exception {
createIndex("test");
client().prepareIndex("test", "type1", "1").setSource("field", "2010-01-05T02:00").execute().actionGet();
client().prepareIndex("test", "type1", "2").setSource("field", "2010-01-06T02:00").execute().actionGet();
refresh();
// test include upper on ranges to include the full day on the upper bound
SearchResponse searchResponse = client().prepareSearch("test").setQuery(QueryBuilders.rangeQuery("field").gte("2010-01-05").lte("2010-01-06")).execute().actionGet();
assertHitCount(searchResponse, 2l);
searchResponse = client().prepareSearch("test").setQuery(QueryBuilders.rangeQuery("field").gte("2010-01-05").lt("2010-01-06")).execute().actionGet();
assertHitCount(searchResponse, 1l);
}
@Test
public void simpleDateRangeWithUpperInclusiveDisabledTests() throws Exception {
assertAcked(prepareCreate("test").setSettings(ImmutableSettings.settingsBuilder()
.put(indexSettings())
.put("index.mapping.date.round_ceil", false)));
client().prepareIndex("test", "type1", "1").setSource("field", "2010-01-05T02:00").execute().actionGet();
client().prepareIndex("test", "type1", "2").setSource("field", "2010-01-06T02:00").execute().actionGet();
ensureGreen();
refresh();
// test include upper on ranges to include the full day on the upper bound (disabled here though...)
SearchResponse searchResponse = client().prepareSearch("test").setQuery(QueryBuilders.rangeQuery("field").gte("2010-01-05").lte("2010-01-06")).execute().actionGet();
assertNoFailures(searchResponse);
assertHitCount(searchResponse, 1l);
searchResponse = client().prepareSearch("test").setQuery(QueryBuilders.rangeQuery("field").gte("2010-01-05").lt("2010-01-06")).execute().actionGet();
assertHitCount(searchResponse, 1l);
}
@Test @TestLogging("action.search.type:TRACE,action.admin.indices.refresh:TRACE")
public void simpleDateMathTests() throws Exception {
public void simpleDateRangeTests() throws Exception {
createIndex("test");
client().prepareIndex("test", "type1", "1").setSource("field", "2010-01-05T02:00").execute().actionGet();
client().prepareIndex("test", "type1", "2").setSource("field", "2010-01-06T02:00").execute().actionGet();
ensureGreen();
refresh();
SearchResponse searchResponse = client().prepareSearch("test").setQuery(QueryBuilders.rangeQuery("field").gte("2010-01-03||+2d").lte("2010-01-04||+2d")).execute().actionGet();
SearchResponse searchResponse = client().prepareSearch("test").setQuery(QueryBuilders.rangeQuery("field").gte("2010-01-03||+2d").lte("2010-01-04||+2d/d")).execute().actionGet();
assertNoFailures(searchResponse);
assertHitCount(searchResponse, 2l);
searchResponse = client().prepareSearch("test").setQuery(QueryBuilders.queryStringQuery("field:[2010-01-03||+2d TO 2010-01-04||+2d]")).execute().actionGet();
searchResponse = client().prepareSearch("test").setQuery(QueryBuilders.rangeQuery("field").gte("2010-01-05T02:00").lte("2010-01-06T02:00")).execute().actionGet();
assertNoFailures(searchResponse);
assertHitCount(searchResponse, 2l);
searchResponse = client().prepareSearch("test").setQuery(QueryBuilders.rangeQuery("field").gte("2010-01-05T02:00").lt("2010-01-06T02:00")).execute().actionGet();
assertNoFailures(searchResponse);
assertHitCount(searchResponse, 1l);
searchResponse = client().prepareSearch("test").setQuery(QueryBuilders.rangeQuery("field").gt("2010-01-05T02:00").lt("2010-01-06T02:00")).execute().actionGet();
assertNoFailures(searchResponse);
assertHitCount(searchResponse, 0l);
searchResponse = client().prepareSearch("test").setQuery(QueryBuilders.queryStringQuery("field:[2010-01-03||+2d TO 2010-01-04||+2d/d]")).execute().actionGet();
assertHitCount(searchResponse, 2l);
}
@Test
public void localDependentDateTests() throws Exception {
public void localeDependentDateTests() throws Exception {
assertAcked(prepareCreate("test")
.addMapping("type1",
jsonBuilder().startObject()
@ -219,28 +200,25 @@ public class SimpleSearchTests extends ElasticsearchIntegrationTest {
for (int i = 1; i <= max; i++) {
String id = String.valueOf(i);
docbuilders.add(client().prepareIndex("test", "type1", id).setSource("field", "2010-01-"+ id +"T02:00"));
docbuilders.add(client().prepareIndex("test", "type1", id).setSource("field", i));
}
indexRandom(true, docbuilders);
ensureGreen();
refresh();
String upperBound = "2010-01-" + String.valueOf(max+1) + "||+2d";
String lowerBound = "2009-12-01||+2d";
SearchResponse searchResponse;
for (int i = 1; i <= max; i++) {
searchResponse = client().prepareSearch("test")
.setQuery(QueryBuilders.rangeQuery("field").gte(lowerBound).lte(upperBound))
.setQuery(QueryBuilders.rangeQuery("field").gte(1).lte(max))
.setTerminateAfter(i).execute().actionGet();
assertHitCount(searchResponse, (long)i);
assertTrue(searchResponse.isTerminatedEarly());
}
searchResponse = client().prepareSearch("test")
.setQuery(QueryBuilders.rangeQuery("field").gte(lowerBound).lte(upperBound))
.setQuery(QueryBuilders.rangeQuery("field").gte(1).lte(max))
.setTerminateAfter(2 * max).execute().actionGet();
assertHitCount(searchResponse, max);