diff --git a/server/src/main/java/org/elasticsearch/index/mapper/DateFieldMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/DateFieldMapper.java index b45f99af8309a..76d6dbb941409 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/DateFieldMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/DateFieldMapper.java @@ -77,6 +77,7 @@ import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.concurrent.TimeUnit; import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.LongSupplier; @@ -116,6 +117,11 @@ public long convert(TimeValue timeValue) { return timeValue.millis(); } + @Override + public long convert(long epochMillis) { + return epochMillis; + } + @Override public Instant toInstant(long value) { return Instant.ofEpochMilli(value); @@ -147,6 +153,11 @@ public long convert(TimeValue timeValue) { return timeValue.nanos(); } + @Override + public long convert(long epochMillis) { + return TimeUnit.MILLISECONDS.toNanos(epochMillis); + } + @Override public Instant toInstant(long value) { return DateUtils.toInstant(value); @@ -210,6 +221,11 @@ ToScriptFieldFactory getDefaultToScriptFieldFactory() { */ public abstract long convert(TimeValue timeValue); + /** + * Convert an epoch millis timestamp into a long value in this resolution. + */ + public abstract long convert(long epochMillis); + /** * Decode the points representation of this field as milliseconds. */ @@ -1234,17 +1250,18 @@ protected String contentType() { @Override protected void parseCreateField(DocumentParserContext context) throws IOException { - String dateAsString = context.parser().textOrNull(); long timestamp; - if (dateAsString == null) { + if (context.parser().currentToken() == XContentParser.Token.VALUE_NULL) { if (nullValue == null) { return; } timestamp = nullValue; + } else if (isEpochMillis(context)) { + timestamp = resolution.convert(context.parser().longValue()); } else { try { - timestamp = fieldType().parse(dateAsString); + timestamp = fieldType().parse(context.parser().text()); } catch (IllegalArgumentException | ElasticsearchParseException | DateTimeException | ArithmeticException e) { if (ignoreMalformed) { context.addIgnoredField(mappedFieldType.name()); @@ -1262,6 +1279,21 @@ protected void parseCreateField(DocumentParserContext context) throws IOExceptio indexValue(context, timestamp); } + /* + If the value is a long and the date formater parses epoch millis, we can index the value directly. + This avoids the overhead of converting the long to a string and then parsing it back to a long via a date formatter. + Note that checking for the date formatter containing "epoch_millis" is not sufficient, + as there may be other formats compatible with a long value before "epoch_millis" (e.g., "epoch_second||epoch_millis"). + */ + private boolean isEpochMillis(DocumentParserContext context) throws IOException { + DateFormatter dateFormatter = fieldType().dateTimeFormatter(); + return context.parser().currentToken() == XContentParser.Token.VALUE_NUMBER + && context.parser().numberType() == XContentParser.NumberType.LONG + && (dateFormatter.equals(DEFAULT_DATE_TIME_FORMATTER) + || dateFormatter.equals(DEFAULT_DATE_TIME_NANOS_FORMATTER) + || dateFormatter.equals(EPOCH_MILLIS_PARSER)); + } + private void indexValue(DocumentParserContext context, long timestamp) { // DataStreamTimestampFieldMapper and TsidExtractingFieldMapper need to use timestamp value, // so when this is true we store it in a well-known place