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 642d137dbf3cf..f11b4ded2c1bf 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/DateFieldMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/DateFieldMapper.java @@ -64,6 +64,7 @@ import java.util.Locale; import java.util.Map; import java.util.Objects; +import java.util.function.Function; import java.util.function.LongSupplier; import static org.elasticsearch.common.time.DateUtils.toLong; @@ -490,6 +491,14 @@ public Relation isFieldWithinQuery(IndexReader reader, } } + @Override + public Function pointReaderIfPossible() { + if (isSearchable()) { + return resolution()::parsePointAsMillis; + } + return null; + } + @Override public IndexFieldData.Builder fielddataBuilder(String fullyQualifiedIndexName) { failIfNoDocValues(); diff --git a/server/src/main/java/org/elasticsearch/index/mapper/MappedFieldType.java b/server/src/main/java/org/elasticsearch/index/mapper/MappedFieldType.java index 4bb854c5d98e3..574eb527a681c 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/MappedFieldType.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/MappedFieldType.java @@ -53,6 +53,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.function.Function; /** * This defines the core properties and functions to operate on a field. @@ -129,7 +130,7 @@ public int hashCode() { /** Returns the name of this type, as would be specified in mapping properties */ public abstract String typeName(); - + /** Returns the field family type, as used in field capabilities */ public String familyTypeName() { return typeName(); @@ -173,6 +174,17 @@ public boolean isSearchable() { return isIndexed; } + /** + * If the field supports using the indexed data to speed up operations related to ordering of data, such as sorting or aggs, return + * a function for doing that. If it is unsupported for this field type, there is no need to override this method. + * + * @return null if the optimization cannot be applied, otherwise a function to use for the optimization + */ + @Nullable + public Function pointReaderIfPossible() { + return null; + } + /** Returns true if the field is aggregatable. * */ diff --git a/server/src/main/java/org/elasticsearch/index/mapper/NumberFieldMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/NumberFieldMapper.java index 6b7bd4e374c12..fe96fb1ec6c91 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/NumberFieldMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/NumberFieldMapper.java @@ -66,6 +66,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.function.Function; /** A {@link FieldMapper} for numeric types: byte, short, int, long, float and double. */ public class NumberFieldMapper extends FieldMapper { @@ -971,6 +972,14 @@ public Query rangeQuery(Object lowerTerm, Object upperTerm, boolean includeLower return query; } + @Override + public Function pointReaderIfPossible() { + if (isSearchable()) { + return this::parsePoint; + } + return null; + } + @Override public IndexFieldData.Builder fielddataBuilder(String fullyQualifiedIndexName) { failIfNoDocValues(); diff --git a/server/src/main/java/org/elasticsearch/search/aggregations/AggregatorBase.java b/server/src/main/java/org/elasticsearch/search/aggregations/AggregatorBase.java index d5f4fc4c0f1f5..0727cb5a24dc4 100644 --- a/server/src/main/java/org/elasticsearch/search/aggregations/AggregatorBase.java +++ b/server/src/main/java/org/elasticsearch/search/aggregations/AggregatorBase.java @@ -19,11 +19,13 @@ package org.elasticsearch.search.aggregations; import org.apache.lucene.index.LeafReaderContext; +import org.apache.lucene.search.MatchAllDocsQuery; import org.apache.lucene.search.ScoreMode; import org.elasticsearch.common.breaker.CircuitBreaker; import org.elasticsearch.common.breaker.CircuitBreakingException; import org.elasticsearch.indices.breaker.CircuitBreakerService; import org.elasticsearch.search.SearchShardTarget; +import org.elasticsearch.search.aggregations.support.ValuesSourceConfig; import org.elasticsearch.search.internal.SearchContext; import org.elasticsearch.search.internal.SearchContext.Lifetime; import org.elasticsearch.search.query.QueryPhaseExecutionException; @@ -34,6 +36,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.function.Function; /** * Base implementation for concrete aggregators. @@ -106,6 +109,26 @@ public ScoreMode scoreMode() { addRequestCircuitBreakerBytes(DEFAULT_WEIGHT); } + /** + * Returns a converter for point values if it's safe to use the indexed data instead of + * doc values. Generally, this means that the query has no filters or scripts, the aggregation is + * top level, and the underlying field is indexed, and the index is sorted in the right order. + * + * If those conditions aren't met, return null to indicate a point reader cannot + * be used in this case. + * + * @param config The config for the values source metric. + */ + public final Function pointReaderIfAvailable(ValuesSourceConfig config) { + if (context.query() != null && context.query().getClass() != MatchAllDocsQuery.class) { + return null; + } + if (parent != null) { + return null; + } + return config.getPointReaderOrNull(); + } + /** * Increment or decrement the number of bytes that have been allocated to service * this request and potentially trigger a {@link CircuitBreakingException}. The diff --git a/server/src/main/java/org/elasticsearch/search/aggregations/metrics/MaxAggregator.java b/server/src/main/java/org/elasticsearch/search/aggregations/metrics/MaxAggregator.java index 20e354d01575e..0e454db0d6566 100644 --- a/server/src/main/java/org/elasticsearch/search/aggregations/metrics/MaxAggregator.java +++ b/server/src/main/java/org/elasticsearch/search/aggregations/metrics/MaxAggregator.java @@ -44,8 +44,6 @@ import java.util.Map; import java.util.function.Function; -import static org.elasticsearch.search.aggregations.metrics.MinAggregator.getPointReaderOrNull; - class MaxAggregator extends NumericMetricsAggregator.SingleValue { final ValuesSource.Numeric valuesSource; @@ -68,7 +66,7 @@ class MaxAggregator extends NumericMetricsAggregator.SingleValue { maxes.fill(0, maxes.size(), Double.NEGATIVE_INFINITY); } this.formatter = config.format(); - this.pointConverter = getPointReaderOrNull(context, parent, config); + this.pointConverter = pointReaderIfAvailable(config); if (pointConverter != null) { pointField = config.fieldContext().field(); } else { @@ -96,7 +94,7 @@ public LeafBucketCollector getLeafCollector(LeafReaderContext ctx, Number segMax = findLeafMaxValue(ctx.reader(), pointField, pointConverter); if (segMax != null) { /* - * There is no parent aggregator (see {@link MinAggregator#getPointReaderOrNull} + * There is no parent aggregator (see {@link AggregatorBase#getPointReaderOrNull} * so the ordinal for the bucket is always 0. */ assert maxes.size() == 1; 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 34b905ec5fc57..1fa12e9719882 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 @@ -22,7 +22,6 @@ 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.search.ScoreMode; import org.apache.lucene.util.Bits; import org.elasticsearch.common.lease.Releasables; @@ -30,9 +29,6 @@ import org.elasticsearch.common.util.DoubleArray; import org.elasticsearch.index.fielddata.NumericDoubleValues; import org.elasticsearch.index.fielddata.SortedNumericDoubleValues; -import org.elasticsearch.index.mapper.DateFieldMapper; -import org.elasticsearch.index.mapper.MappedFieldType; -import org.elasticsearch.index.mapper.NumberFieldMapper; import org.elasticsearch.search.DocValueFormat; import org.elasticsearch.search.MultiValueMode; import org.elasticsearch.search.aggregations.Aggregator; @@ -71,7 +67,7 @@ class MinAggregator extends NumericMetricsAggregator.SingleValue { mins.fill(0, mins.size(), Double.POSITIVE_INFINITY); } this.format = config.format(); - this.pointConverter = getPointReaderOrNull(context, parent, config); + this.pointConverter = pointReaderIfAvailable(config); if (pointConverter != null) { pointField = config.fieldContext().field(); } else { @@ -159,40 +155,6 @@ public void doClose() { } - /** - * Returns a converter for point values if early termination is applicable to - * the context or null otherwise. - * - * @param context The {@link SearchContext} of the aggregation. - * @param parent The parent aggregator. - * @param config The config for the values source metric. - */ - static Function getPointReaderOrNull(SearchContext context, Aggregator parent, - ValuesSourceConfig config) { - if (context.query() != null && - context.query().getClass() != MatchAllDocsQuery.class) { - return null; - } - if (parent != null) { - return null; - } - if (config.fieldContext() != null && config.script() == null && config.missing() == null) { - MappedFieldType fieldType = config.fieldContext().fieldType(); - if (fieldType == null || fieldType.isSearchable() == false) { - return null; - } - Function converter = null; - if (fieldType instanceof NumberFieldMapper.NumberFieldType) { - converter = ((NumberFieldMapper.NumberFieldType) fieldType)::parsePoint; - } else if (fieldType.getClass() == DateFieldMapper.DateFieldType.class) { - DateFieldMapper.DateFieldType dft = (DateFieldMapper.DateFieldType) fieldType; - converter = dft.resolution()::parsePointAsMillis; - } - return converter; - } - return null; - } - /** * Returns the minimum value indexed in the fieldName field or null * if the value cannot be inferred from the indexed {@link PointValues}. diff --git a/server/src/main/java/org/elasticsearch/search/aggregations/support/ValuesSourceConfig.java b/server/src/main/java/org/elasticsearch/search/aggregations/support/ValuesSourceConfig.java index a765e49bc77d1..37a1d53213311 100644 --- a/server/src/main/java/org/elasticsearch/search/aggregations/support/ValuesSourceConfig.java +++ b/server/src/main/java/org/elasticsearch/search/aggregations/support/ValuesSourceConfig.java @@ -30,6 +30,7 @@ import org.elasticsearch.search.DocValueFormat; import java.time.ZoneId; +import java.util.function.Function; import java.util.function.LongSupplier; /** @@ -372,6 +373,23 @@ public boolean hasGlobalOrdinals() { return valuesSource.hasGlobalOrdinals(); } + /** + * This method is used when an aggregation can optimize by using the indexed data instead of the doc values. We check to see if the + * indexed data will match the values source output (meaning there isn't a script or a missing value, since both could modify the + * value at read time). If the settings allow for it, we then ask the {@link ValuesSourceType} to build the actual point reader + * based on the field type. This allows for a point of extensibility in plugins. + * + * @return null if we cannot apply the optimization, otherwise the point reader function. + */ + @Nullable + public Function getPointReaderOrNull() { + MappedFieldType fieldType = fieldType(); + if (fieldType != null && script() == null && missing() == null) { + return fieldType.pointReaderIfPossible(); + } + return null; + } + /** * Returns a human readable description of this values source, for use in error messages and similar. */ diff --git a/server/src/test/java/org/elasticsearch/search/aggregations/AggregatorBaseTests.java b/server/src/test/java/org/elasticsearch/search/aggregations/AggregatorBaseTests.java new file mode 100644 index 0000000000000..4673d46fad192 --- /dev/null +++ b/server/src/test/java/org/elasticsearch/search/aggregations/AggregatorBaseTests.java @@ -0,0 +1,192 @@ +/* + * 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.search.aggregations; + +import org.apache.lucene.document.LongPoint; +import org.apache.lucene.index.LeafReaderContext; +import org.apache.lucene.index.Term; +import org.apache.lucene.search.MatchAllDocsQuery; +import org.apache.lucene.search.Query; +import org.apache.lucene.search.TermQuery; +import org.elasticsearch.Version; +import org.elasticsearch.action.support.WriteRequest; +import org.elasticsearch.cluster.metadata.IndexMetadata; +import org.elasticsearch.common.breaker.CircuitBreaker; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.util.BigArrays; +import org.elasticsearch.index.IndexService; +import org.elasticsearch.index.engine.Engine; +import org.elasticsearch.index.mapper.ContentPath; +import org.elasticsearch.index.mapper.DateFieldMapper; +import org.elasticsearch.index.mapper.MappedFieldType; +import org.elasticsearch.index.mapper.Mapper; +import org.elasticsearch.index.mapper.NumberFieldMapper; +import org.elasticsearch.index.query.QueryShardContext; +import org.elasticsearch.indices.breaker.CircuitBreakerService; +import org.elasticsearch.search.aggregations.support.ValuesSourceConfig; +import org.elasticsearch.search.internal.SearchContext; +import org.elasticsearch.test.ESSingleNodeTestCase; + +import java.io.IOException; +import java.time.Instant; +import java.util.Collections; +import java.util.Map; +import java.util.function.Function; + +import static org.hamcrest.Matchers.equalTo; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class AggregatorBaseTests extends ESSingleNodeTestCase { + + class BogusAggregator extends AggregatorBase { + BogusAggregator(SearchContext searchContext, Aggregator parent) throws IOException { + super("bogus", AggregatorFactories.EMPTY, searchContext, parent, CardinalityUpperBound.NONE, Map.of()); + } + + @Override + protected LeafBucketCollector getLeafCollector(LeafReaderContext ctx, LeafBucketCollector sub) throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public InternalAggregation[] buildAggregations(long[] owningBucketOrds) throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public InternalAggregation buildEmptyAggregation() { + throw new UnsupportedOperationException(); + } + } + + private SearchContext mockSearchContext(Query query) { + SearchContext searchContext = mock(SearchContext.class); + when(searchContext.query()).thenReturn(query); + BigArrays mockBigArrays = mock(BigArrays.class); + CircuitBreakerService mockCBS = mock(CircuitBreakerService.class); + when(mockCBS.getBreaker(CircuitBreaker.REQUEST)).thenReturn(mock(CircuitBreaker.class)); + when(mockBigArrays.breakerService()).thenReturn(mockCBS); + when(searchContext.bigArrays()).thenReturn(mockBigArrays); + return searchContext; + } + + private Function pointReaderShim(SearchContext context, Aggregator parent, ValuesSourceConfig config) + throws IOException { + BogusAggregator aggregator = new BogusAggregator(context, parent); + return aggregator.pointReaderIfAvailable(config); + } + + private Aggregator mockAggregator() { + return mock(Aggregator.class); + } + + private ValuesSourceConfig getVSConfig( + String fieldName, + NumberFieldMapper.NumberType numType, + boolean indexed, + QueryShardContext context + ) { + MappedFieldType ft = new NumberFieldMapper.NumberFieldType(fieldName, numType, indexed, true, Collections.emptyMap()); + return ValuesSourceConfig.resolveFieldOnly(ft, context); + } + + private ValuesSourceConfig getVSConfig( + String fieldName, + DateFieldMapper.Resolution resolution, + boolean indexed, + QueryShardContext context + ) { + Mapper.BuilderContext builderContext = new Mapper.BuilderContext( + Settings.builder().put(IndexMetadata.SETTING_VERSION_CREATED, Version.CURRENT).build(), + new ContentPath() + ); + MappedFieldType ft = new DateFieldMapper.Builder(fieldName).index(indexed) + .withResolution(resolution) + .build(builderContext) + .fieldType(); + return ValuesSourceConfig.resolveFieldOnly(ft, context); + } + + public void testShortcutIsApplicable() throws IOException { + IndexService indexService = createIndex("index", Settings.EMPTY, "type", "bytes", "type=keyword"); + client().prepareIndex("index").setId("1").setSource("bytes", "abc").setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE).get(); + + try (Engine.Searcher searcher = indexService.getShard(0).acquireSearcher("test")) { + QueryShardContext context = indexService.newQueryShardContext(0, searcher, () -> 42L, null); + + for (NumberFieldMapper.NumberType type : NumberFieldMapper.NumberType.values()) { + assertNotNull( + pointReaderShim(mockSearchContext(new MatchAllDocsQuery()), null, getVSConfig("number", type, true, context)) + ); + assertNotNull(pointReaderShim(mockSearchContext(null), null, getVSConfig("number", type, true, context))); + assertNull(pointReaderShim(mockSearchContext(null), mockAggregator(), getVSConfig("number", type, true, context))); + assertNull( + pointReaderShim( + mockSearchContext(new TermQuery(new Term("foo", "bar"))), + null, + getVSConfig("number", type, true, context) + ) + ); + assertNull(pointReaderShim(mockSearchContext(null), mockAggregator(), getVSConfig("number", type, true, context))); + assertNull(pointReaderShim(mockSearchContext(null), null, getVSConfig("number", type, false, context))); + } + for (DateFieldMapper.Resolution resolution : DateFieldMapper.Resolution.values()) { + assertNull( + pointReaderShim( + mockSearchContext(new MatchAllDocsQuery()), + mockAggregator(), + getVSConfig("number", resolution, true, context) + ) + ); + assertNull( + pointReaderShim( + mockSearchContext(new TermQuery(new Term("foo", "bar"))), + null, + getVSConfig("number", resolution, true, context) + ) + ); + assertNull(pointReaderShim(mockSearchContext(null), mockAggregator(), getVSConfig("number", resolution, true, context))); + assertNull(pointReaderShim(mockSearchContext(null), null, getVSConfig("number", resolution, false, context))); + } + // Check that we decode a dates "just like" the doc values instance. + Instant expected = Instant.from(DateFieldMapper.DEFAULT_DATE_TIME_FORMATTER.parse("2020-01-01T00:00:00Z")); + byte[] scratch = new byte[8]; + LongPoint.encodeDimension(DateFieldMapper.Resolution.MILLISECONDS.convert(expected), scratch, 0); + assertThat( + pointReaderShim( + mockSearchContext(new MatchAllDocsQuery()), + null, + getVSConfig("number", DateFieldMapper.Resolution.MILLISECONDS, true, context) + ).apply(scratch), + equalTo(expected.toEpochMilli()) + ); + LongPoint.encodeDimension(DateFieldMapper.Resolution.NANOSECONDS.convert(expected), scratch, 0); + assertThat( + pointReaderShim( + mockSearchContext(new MatchAllDocsQuery()), + null, + getVSConfig("number", DateFieldMapper.Resolution.NANOSECONDS, true, context) + ).apply(scratch), + equalTo(expected.toEpochMilli()) + ); + } + } +} diff --git a/server/src/test/java/org/elasticsearch/search/aggregations/metrics/MinAggregatorTests.java b/server/src/test/java/org/elasticsearch/search/aggregations/metrics/MinAggregatorTests.java index 8991639f5ac7e..0886ad91015f1 100644 --- a/server/src/test/java/org/elasticsearch/search/aggregations/metrics/MinAggregatorTests.java +++ b/server/src/test/java/org/elasticsearch/search/aggregations/metrics/MinAggregatorTests.java @@ -43,22 +43,16 @@ import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.MatchAllDocsQuery; import org.apache.lucene.search.Query; -import org.apache.lucene.search.TermQuery; import org.apache.lucene.store.Directory; import org.apache.lucene.util.BytesRef; -import org.elasticsearch.Version; -import org.elasticsearch.cluster.metadata.IndexMetadata; import org.elasticsearch.common.CheckedConsumer; import org.elasticsearch.common.collect.Tuple; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.BigArrays; import org.elasticsearch.index.IndexSettings; -import org.elasticsearch.index.mapper.ContentPath; -import org.elasticsearch.index.mapper.DateFieldMapper; import org.elasticsearch.index.mapper.IpFieldMapper; import org.elasticsearch.index.mapper.KeywordFieldMapper; import org.elasticsearch.index.mapper.MappedFieldType; -import org.elasticsearch.index.mapper.Mapper; import org.elasticsearch.index.mapper.MapperService; import org.elasticsearch.index.mapper.NumberFieldMapper; import org.elasticsearch.index.query.QueryShardContext; @@ -70,7 +64,6 @@ import org.elasticsearch.script.ScriptService; import org.elasticsearch.script.ScriptType; import org.elasticsearch.search.aggregations.AggregationBuilder; -import org.elasticsearch.search.aggregations.Aggregator; import org.elasticsearch.search.aggregations.AggregatorTestCase; import org.elasticsearch.search.aggregations.BucketOrder; import org.elasticsearch.search.aggregations.bucket.filter.Filter; @@ -84,13 +77,9 @@ import org.elasticsearch.search.aggregations.bucket.terms.Terms; import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder; import org.elasticsearch.search.aggregations.support.AggregationInspectionHelper; -import org.elasticsearch.search.aggregations.support.FieldContext; -import org.elasticsearch.search.aggregations.support.ValuesSourceConfig; -import org.elasticsearch.search.internal.SearchContext; import org.elasticsearch.search.lookup.LeafDocLookup; import java.io.IOException; -import java.time.Instant; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -106,8 +95,6 @@ import static java.util.Collections.singleton; import static org.elasticsearch.index.query.QueryBuilders.termQuery; import static org.hamcrest.Matchers.equalTo; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; public class MinAggregatorTests extends AggregatorTestCase { @@ -690,103 +677,6 @@ public void testScriptCaching() throws IOException { } } - public void testShortcutIsApplicable() { - for (NumberFieldMapper.NumberType type : NumberFieldMapper.NumberType.values()) { - assertNotNull( - MinAggregator.getPointReaderOrNull( - mockSearchContext(new MatchAllDocsQuery()), - null, - mockNumericValuesSourceConfig("number", type, true) - ) - ); - assertNotNull( - MinAggregator.getPointReaderOrNull( - mockSearchContext(null), - null, - mockNumericValuesSourceConfig("number", type, true) - ) - ); - assertNull( - MinAggregator.getPointReaderOrNull( - mockSearchContext(null), - mockAggregator(), - mockNumericValuesSourceConfig("number", type, true) - ) - ); - assertNull( - MinAggregator.getPointReaderOrNull( - mockSearchContext(new TermQuery(new Term("foo", "bar"))), - null, - mockNumericValuesSourceConfig("number", type, true) - ) - ); - assertNull( - MinAggregator.getPointReaderOrNull( - mockSearchContext(null), - mockAggregator(), - mockNumericValuesSourceConfig("number", type, true) - ) - ); - assertNull( - MinAggregator.getPointReaderOrNull( - mockSearchContext(null), - null, - mockNumericValuesSourceConfig("number", type, false) - ) - ); - } - for (DateFieldMapper.Resolution resolution : DateFieldMapper.Resolution.values()) { - assertNull( - MinAggregator.getPointReaderOrNull( - mockSearchContext(new MatchAllDocsQuery()), - mockAggregator(), - mockDateValuesSourceConfig("number", true, resolution) - ) - ); - assertNull( - MinAggregator.getPointReaderOrNull( - mockSearchContext(new TermQuery(new Term("foo", "bar"))), - null, - mockDateValuesSourceConfig("number", true, resolution) - ) - ); - assertNull( - MinAggregator.getPointReaderOrNull( - mockSearchContext(null), - mockAggregator(), - mockDateValuesSourceConfig("number", true, resolution) - ) - ); - assertNull( - MinAggregator.getPointReaderOrNull( - mockSearchContext(null), - null, - mockDateValuesSourceConfig("number", false, resolution) - ) - ); - } - // Check that we decode a dates "just like" the doc values instance. - Instant expected = Instant.from(DateFieldMapper.DEFAULT_DATE_TIME_FORMATTER.parse("2020-01-01T00:00:00Z")); - byte[] scratch = new byte[8]; - LongPoint.encodeDimension(DateFieldMapper.Resolution.MILLISECONDS.convert(expected), scratch, 0); - assertThat( - MinAggregator.getPointReaderOrNull( - mockSearchContext(new MatchAllDocsQuery()), - null, - mockDateValuesSourceConfig("number", true, DateFieldMapper.Resolution.MILLISECONDS) - ).apply(scratch), equalTo(expected.toEpochMilli()) - ); - LongPoint.encodeDimension(DateFieldMapper.Resolution.NANOSECONDS.convert(expected), scratch, 0); - assertThat( - MinAggregator.getPointReaderOrNull( - mockSearchContext(new MatchAllDocsQuery()), - null, - mockDateValuesSourceConfig("number", true, DateFieldMapper.Resolution.NANOSECONDS) - ).apply(scratch), equalTo(expected.toEpochMilli()) - ); - - } - public void testMinShortcutRandom() throws Exception { testMinShortcutCase( () -> randomLongBetween(Integer.MIN_VALUE, Integer.MAX_VALUE), @@ -862,40 +752,6 @@ private void testMinShortcutCase(Supplier randomNumber, directory.close(); } - private SearchContext mockSearchContext(Query query) { - SearchContext searchContext = mock(SearchContext.class); - when(searchContext.query()).thenReturn(query); - return searchContext; - } - - private Aggregator mockAggregator() { - return mock(Aggregator.class); - } - - private ValuesSourceConfig mockNumericValuesSourceConfig(String fieldName, - NumberFieldMapper.NumberType numType, - boolean indexed) { - ValuesSourceConfig config = mock(ValuesSourceConfig.class); - MappedFieldType ft = new NumberFieldMapper.NumberFieldType(fieldName, numType, indexed, true, Collections.emptyMap()); - when(config.fieldContext()).thenReturn(new FieldContext(fieldName, null, ft)); - return config; - } - - private ValuesSourceConfig mockDateValuesSourceConfig(String fieldName, boolean indexed, - DateFieldMapper.Resolution resolution) { - ValuesSourceConfig config = mock(ValuesSourceConfig.class); - Mapper.BuilderContext builderContext = new Mapper.BuilderContext( - Settings.builder().put(IndexMetadata.SETTING_VERSION_CREATED, Version.CURRENT).build(), - new ContentPath()); - MappedFieldType ft = new DateFieldMapper.Builder(fieldName) - .index(indexed) - .withResolution(resolution) - .build(builderContext) - .fieldType(); - when(config.fieldContext()).thenReturn(new FieldContext(fieldName, null, ft)); - return config; - } - private void testCase(Query query, CheckedConsumer buildIndex, Consumer verify) throws IOException {