Skip to content

Commit dc91a30

Browse files
authored
Move getPointReaderOrNull into AggregatorBase (#58769)
1 parent d543c27 commit dc91a30

File tree

9 files changed

+266
-187
lines changed

9 files changed

+266
-187
lines changed

server/src/main/java/org/elasticsearch/index/mapper/DateFieldMapper.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
import java.util.Locale;
6565
import java.util.Map;
6666
import java.util.Objects;
67+
import java.util.function.Function;
6768
import java.util.function.LongSupplier;
6869

6970
import static org.elasticsearch.common.time.DateUtils.toLong;
@@ -464,6 +465,14 @@ public Relation isFieldWithinQuery(IndexReader reader,
464465
}
465466
}
466467

468+
@Override
469+
public Function<byte[], Number> pointReaderIfPossible() {
470+
if (isSearchable()) {
471+
return resolution()::parsePointAsMillis;
472+
}
473+
return null;
474+
}
475+
467476
@Override
468477
public IndexFieldData.Builder fielddataBuilder(String fullyQualifiedIndexName) {
469478
failIfNoDocValues();

server/src/main/java/org/elasticsearch/index/mapper/MappedFieldType.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
import java.util.List;
5454
import java.util.Map;
5555
import java.util.Objects;
56+
import java.util.function.Function;
5657

5758
/**
5859
* This defines the core properties and functions to operate on a field.
@@ -136,6 +137,17 @@ public boolean isSearchable() {
136137
return isIndexed;
137138
}
138139

140+
/**
141+
* If the field supports using the indexed data to speed up operations related to ordering of data, such as sorting or aggs, return
142+
* a function for doing that. If it is unsupported for this field type, there is no need to override this method.
143+
*
144+
* @return null if the optimization cannot be applied, otherwise a function to use for the optimization
145+
*/
146+
@Nullable
147+
public Function<byte[], Number> pointReaderIfPossible() {
148+
return null;
149+
}
150+
139151
/** Returns true if the field is aggregatable.
140152
*
141153
*/

server/src/main/java/org/elasticsearch/index/mapper/NumberFieldMapper.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
import java.util.List;
6767
import java.util.Map;
6868
import java.util.Objects;
69+
import java.util.function.Function;
6970

7071
/** A {@link FieldMapper} for numeric types: byte, short, int, long, float and double. */
7172
public class NumberFieldMapper extends FieldMapper {
@@ -961,6 +962,14 @@ public Query rangeQuery(Object lowerTerm, Object upperTerm, boolean includeLower
961962
return query;
962963
}
963964

965+
@Override
966+
public Function<byte[], Number> pointReaderIfPossible() {
967+
if (isSearchable()) {
968+
return this::parsePoint;
969+
}
970+
return null;
971+
}
972+
964973
@Override
965974
public IndexFieldData.Builder fielddataBuilder(String fullyQualifiedIndexName) {
966975
failIfNoDocValues();

server/src/main/java/org/elasticsearch/search/aggregations/AggregatorBase.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,13 @@
1919
package org.elasticsearch.search.aggregations;
2020

2121
import org.apache.lucene.index.LeafReaderContext;
22+
import org.apache.lucene.search.MatchAllDocsQuery;
2223
import org.apache.lucene.search.ScoreMode;
2324
import org.elasticsearch.common.breaker.CircuitBreaker;
2425
import org.elasticsearch.common.breaker.CircuitBreakingException;
2526
import org.elasticsearch.indices.breaker.CircuitBreakerService;
2627
import org.elasticsearch.search.SearchShardTarget;
28+
import org.elasticsearch.search.aggregations.support.ValuesSourceConfig;
2729
import org.elasticsearch.search.internal.SearchContext;
2830
import org.elasticsearch.search.internal.SearchContext.Lifetime;
2931
import org.elasticsearch.search.query.QueryPhaseExecutionException;
@@ -34,6 +36,7 @@
3436
import java.util.HashMap;
3537
import java.util.List;
3638
import java.util.Map;
39+
import java.util.function.Function;
3740

3841
/**
3942
* Base implementation for concrete aggregators.
@@ -106,6 +109,26 @@ public ScoreMode scoreMode() {
106109
addRequestCircuitBreakerBytes(DEFAULT_WEIGHT);
107110
}
108111

112+
/**
113+
* Returns a converter for point values if it's safe to use the indexed data instead of
114+
* doc values. Generally, this means that the query has no filters or scripts, the aggregation is
115+
* top level, and the underlying field is indexed, and the index is sorted in the right order.
116+
*
117+
* If those conditions aren't met, return <code>null</code> to indicate a point reader cannot
118+
* be used in this case.
119+
*
120+
* @param config The config for the values source metric.
121+
*/
122+
public final Function<byte[], Number> pointReaderIfAvailable(ValuesSourceConfig config) {
123+
if (context.query() != null && context.query().getClass() != MatchAllDocsQuery.class) {
124+
return null;
125+
}
126+
if (parent != null) {
127+
return null;
128+
}
129+
return config.getPointReaderOrNull();
130+
}
131+
109132
/**
110133
* Increment or decrement the number of bytes that have been allocated to service
111134
* this request and potentially trigger a {@link CircuitBreakingException}. The

server/src/main/java/org/elasticsearch/search/aggregations/metrics/MaxAggregator.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,6 @@
4444
import java.util.Map;
4545
import java.util.function.Function;
4646

47-
import static org.elasticsearch.search.aggregations.metrics.MinAggregator.getPointReaderOrNull;
48-
4947
class MaxAggregator extends NumericMetricsAggregator.SingleValue {
5048

5149
final ValuesSource.Numeric valuesSource;
@@ -68,7 +66,7 @@ class MaxAggregator extends NumericMetricsAggregator.SingleValue {
6866
maxes.fill(0, maxes.size(), Double.NEGATIVE_INFINITY);
6967
}
7068
this.formatter = config.format();
71-
this.pointConverter = getPointReaderOrNull(context, parent, config);
69+
this.pointConverter = pointReaderIfAvailable(config);
7270
if (pointConverter != null) {
7371
pointField = config.fieldContext().field();
7472
} else {
@@ -96,7 +94,7 @@ public LeafBucketCollector getLeafCollector(LeafReaderContext ctx,
9694
Number segMax = findLeafMaxValue(ctx.reader(), pointField, pointConverter);
9795
if (segMax != null) {
9896
/*
99-
* There is no parent aggregator (see {@link MinAggregator#getPointReaderOrNull}
97+
* There is no parent aggregator (see {@link AggregatorBase#getPointReaderOrNull}
10098
* so the ordinal for the bucket is always 0.
10199
*/
102100
assert maxes.size() == 1;

server/src/main/java/org/elasticsearch/search/aggregations/metrics/MinAggregator.java

Lines changed: 1 addition & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,13 @@
2222
import org.apache.lucene.index.LeafReaderContext;
2323
import org.apache.lucene.index.PointValues;
2424
import org.apache.lucene.search.CollectionTerminatedException;
25-
import org.apache.lucene.search.MatchAllDocsQuery;
2625
import org.apache.lucene.search.ScoreMode;
2726
import org.apache.lucene.util.Bits;
2827
import org.elasticsearch.common.lease.Releasables;
2928
import org.elasticsearch.common.util.BigArrays;
3029
import org.elasticsearch.common.util.DoubleArray;
3130
import org.elasticsearch.index.fielddata.NumericDoubleValues;
3231
import org.elasticsearch.index.fielddata.SortedNumericDoubleValues;
33-
import org.elasticsearch.index.mapper.DateFieldMapper;
34-
import org.elasticsearch.index.mapper.MappedFieldType;
35-
import org.elasticsearch.index.mapper.NumberFieldMapper;
3632
import org.elasticsearch.search.DocValueFormat;
3733
import org.elasticsearch.search.MultiValueMode;
3834
import org.elasticsearch.search.aggregations.Aggregator;
@@ -71,7 +67,7 @@ class MinAggregator extends NumericMetricsAggregator.SingleValue {
7167
mins.fill(0, mins.size(), Double.POSITIVE_INFINITY);
7268
}
7369
this.format = config.format();
74-
this.pointConverter = getPointReaderOrNull(context, parent, config);
70+
this.pointConverter = pointReaderIfAvailable(config);
7571
if (pointConverter != null) {
7672
pointField = config.fieldContext().field();
7773
} else {
@@ -159,40 +155,6 @@ public void doClose() {
159155
}
160156

161157

162-
/**
163-
* Returns a converter for point values if early termination is applicable to
164-
* the context or <code>null</code> otherwise.
165-
*
166-
* @param context The {@link SearchContext} of the aggregation.
167-
* @param parent The parent aggregator.
168-
* @param config The config for the values source metric.
169-
*/
170-
static Function<byte[], Number> getPointReaderOrNull(SearchContext context, Aggregator parent,
171-
ValuesSourceConfig config) {
172-
if (context.query() != null &&
173-
context.query().getClass() != MatchAllDocsQuery.class) {
174-
return null;
175-
}
176-
if (parent != null) {
177-
return null;
178-
}
179-
if (config.fieldContext() != null && config.script() == null && config.missing() == null) {
180-
MappedFieldType fieldType = config.fieldContext().fieldType();
181-
if (fieldType == null || fieldType.isSearchable() == false) {
182-
return null;
183-
}
184-
Function<byte[], Number> converter = null;
185-
if (fieldType instanceof NumberFieldMapper.NumberFieldType) {
186-
converter = ((NumberFieldMapper.NumberFieldType) fieldType)::parsePoint;
187-
} else if (fieldType.getClass() == DateFieldMapper.DateFieldType.class) {
188-
DateFieldMapper.DateFieldType dft = (DateFieldMapper.DateFieldType) fieldType;
189-
converter = dft.resolution()::parsePointAsMillis;
190-
}
191-
return converter;
192-
}
193-
return null;
194-
}
195-
196158
/**
197159
* Returns the minimum value indexed in the <code>fieldName</code> field or <code>null</code>
198160
* if the value cannot be inferred from the indexed {@link PointValues}.

server/src/main/java/org/elasticsearch/search/aggregations/support/ValuesSourceConfig.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import org.elasticsearch.search.DocValueFormat;
3131

3232
import java.time.ZoneId;
33+
import java.util.function.Function;
3334
import java.util.function.LongSupplier;
3435

3536
/**
@@ -372,6 +373,23 @@ public boolean hasGlobalOrdinals() {
372373
return valuesSource.hasGlobalOrdinals();
373374
}
374375

376+
/**
377+
* 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
378+
* indexed data will match the values source output (meaning there isn't a script or a missing value, since both could modify the
379+
* value at read time). If the settings allow for it, we then ask the {@link ValuesSourceType} to build the actual point reader
380+
* based on the field type. This allows for a point of extensibility in plugins.
381+
*
382+
* @return null if we cannot apply the optimization, otherwise the point reader function.
383+
*/
384+
@Nullable
385+
public Function<byte[], Number> getPointReaderOrNull() {
386+
MappedFieldType fieldType = fieldType();
387+
if (fieldType != null && script() == null && missing() == null) {
388+
return fieldType.pointReaderIfPossible();
389+
}
390+
return null;
391+
}
392+
375393
/**
376394
* Returns a human readable description of this values source, for use in error messages and similar.
377395
*/

0 commit comments

Comments
 (0)