diff --git a/rest-api-spec/src/main/resources/rest-api-spec/test/search.aggregation/230_composite.yml b/rest-api-spec/src/main/resources/rest-api-spec/test/search.aggregation/230_composite.yml index eea3d7792c7f6..1ae5686db3988 100644 --- a/rest-api-spec/src/main/resources/rest-api-spec/test/search.aggregation/230_composite.yml +++ b/rest-api-spec/src/main/resources/rest-api-spec/test/search.aggregation/230_composite.yml @@ -8,6 +8,8 @@ setup: properties: date: type: date + date_nanos: + type: date_nanos keyword: type: keyword long: @@ -851,6 +853,57 @@ setup: - match: { aggregations.test.buckets.0.key.date: "2017-10-21T00:00:00.000-02:00" } - match: { aggregations.test.buckets.0.doc_count: 2 } +--- +"date_histogram on date_nanos": + - skip: + version: " - 7.99.99" + reason: Fixed in 8.0.0 with backport pending to 7.7.0 + - do: + index: + index: test + id: 7 + body: { "date_nanos": "2017-11-21T01:00:00" } + refresh: true + - do: + index: + index: test + id: 8 + body: { "date_nanos": "2017-11-22T01:00:00" } + refresh: true + - do: + index: + index: test + id: 9 + body: { "date_nanos": "2017-11-22T02:00:00" } + refresh: true + - do: + search: + index: test + body: + aggregations: + test: + composite: + sources: + - date: + date_histogram: + field: date_nanos + calendar_interval: 1d + format: iso8601 # Format makes the comparisons a little more obvious + aggregations: + avg: + avg: + field: date_nanos + + - match: { hits.total.value: 9 } + - match: { hits.total.relation: eq } + - length: { aggregations.test.buckets: 2 } + - match: { aggregations.test.buckets.0.key.date: "2017-11-21T00:00:00.000Z" } + - match: { aggregations.test.buckets.0.doc_count: 1 } + - match: { aggregations.test.buckets.0.avg.value_as_string: "2017-11-21T01:00:00.000Z" } + - match: { aggregations.test.buckets.1.key.date: "2017-11-22T00:00:00.000Z" } + - match: { aggregations.test.buckets.1.doc_count: 2 } + - match: { aggregations.test.buckets.1.avg.value_as_string: "2017-11-22T01:30:00.000Z" } + --- "Terms source from sorted": - do: 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 87d489d04b556..4e6b04a72418a 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/DateFieldMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/DateFieldMapper.java @@ -97,6 +97,11 @@ public Instant toInstant(long value) { public Instant clampToValidRange(Instant instant) { return instant; } + + @Override + public long parsePointAsMillis(byte[] value) { + return LongPoint.decodeDimension(value, 0); + } }, NANOSECONDS(DATE_NANOS_CONTENT_TYPE, NumericType.DATE_NANOSECONDS) { @Override @@ -113,6 +118,11 @@ public Instant toInstant(long value) { public Instant clampToValidRange(Instant instant) { return DateUtils.clampToNanosRange(instant); } + + @Override + public long parsePointAsMillis(byte[] value) { + return DateUtils.toMilliSeconds(LongPoint.decodeDimension(value, 0)); + } }; private final String type; @@ -141,8 +151,17 @@ NumericType numericType() { */ public abstract Instant toInstant(long value); + /** + * Return the instant that this range can represent that is closest to + * the provided instant. + */ public abstract Instant clampToValidRange(Instant instant); + /** + * Decode the points representation of this field as milliseconds. + */ + public abstract long parsePointAsMillis(byte[] value); + public static Resolution ofOrdinal(int ord) { for (Resolution resolution : values()) { if (ord == resolution.ordinal()) { diff --git a/server/src/main/java/org/elasticsearch/search/aggregations/bucket/composite/LongValuesSource.java b/server/src/main/java/org/elasticsearch/search/aggregations/bucket/composite/LongValuesSource.java index d71ed3c3bd97d..93dcdf7be3bda 100644 --- a/server/src/main/java/org/elasticsearch/search/aggregations/bucket/composite/LongValuesSource.java +++ b/server/src/main/java/org/elasticsearch/search/aggregations/bucket/composite/LongValuesSource.java @@ -269,7 +269,8 @@ SortedDocsProducer createSortedDocsProducerOrNull(IndexReader reader, Query quer } return new PointsSortedDocsProducer(fieldType.name(), toBucketFunction, lowerPoint, upperPoint); } else if (fieldType instanceof DateFieldMapper.DateFieldType) { - final ToLongFunction toBucketFunction = (value) -> rounding.applyAsLong(LongPoint.decodeDimension(value, 0)); + ToLongFunction decode = ((DateFieldMapper.DateFieldType) fieldType).resolution()::parsePointAsMillis; + ToLongFunction toBucketFunction = value -> rounding.applyAsLong(decode.applyAsLong(value)); return new PointsSortedDocsProducer(fieldType.name(), toBucketFunction, lowerPoint, upperPoint); } else { return null; diff --git a/server/src/main/java/org/elasticsearch/search/aggregations/metrics/MinAggregator.java b/server/src/main/java/org/elasticsearch/search/aggregations/metrics/MinAggregator.java index 96563d917bf8f..b8bfae41b2314 100644 --- a/server/src/main/java/org/elasticsearch/search/aggregations/metrics/MinAggregator.java +++ b/server/src/main/java/org/elasticsearch/search/aggregations/metrics/MinAggregator.java @@ -18,15 +18,14 @@ */ package org.elasticsearch.search.aggregations.metrics; -import org.apache.lucene.document.LongPoint; import org.apache.lucene.index.IndexOptions; import org.apache.lucene.index.LeafReader; import org.apache.lucene.index.LeafReaderContext; import org.apache.lucene.index.PointValues; import org.apache.lucene.search.CollectionTerminatedException; import org.apache.lucene.search.MatchAllDocsQuery; -import org.apache.lucene.util.Bits; import org.apache.lucene.search.ScoreMode; +import org.apache.lucene.util.Bits; import org.elasticsearch.common.lease.Releasables; import org.elasticsearch.common.util.BigArrays; import org.elasticsearch.common.util.DoubleArray; @@ -191,11 +190,7 @@ static Function getPointReaderOrNull(SearchContext context, Aggr converter = ((NumberFieldMapper.NumberFieldType) fieldType)::parsePoint; } else if (fieldType.getClass() == DateFieldMapper.DateFieldType.class) { DateFieldMapper.DateFieldType dft = (DateFieldMapper.DateFieldType) fieldType; - /* - * Makes sure that nanoseconds decode to milliseconds, just - * like they do when you run the agg without the optimization. - */ - converter = (in) -> dft.resolution().toInstant(LongPoint.decodeDimension(in, 0)).toEpochMilli(); + converter = dft.resolution()::parsePointAsMillis; } return converter; }