1818 */
1919package org .elasticsearch .search .aggregations .metrics .max ;
2020
21+ import org .apache .lucene .index .LeafReader ;
2122import org .apache .lucene .index .LeafReaderContext ;
23+ import org .apache .lucene .index .PointValues ;
24+ import org .apache .lucene .search .CollectionTerminatedException ;
25+ import org .apache .lucene .util .Bits ;
26+ import org .apache .lucene .util .FutureArrays ;
2227import org .elasticsearch .common .lease .Releasables ;
2328import org .elasticsearch .common .util .BigArrays ;
2429import org .elasticsearch .common .util .DoubleArray ;
3338import org .elasticsearch .search .aggregations .metrics .NumericMetricsAggregator ;
3439import org .elasticsearch .search .aggregations .pipeline .PipelineAggregator ;
3540import org .elasticsearch .search .aggregations .support .ValuesSource ;
41+ import org .elasticsearch .search .aggregations .support .ValuesSourceConfig ;
3642import org .elasticsearch .search .internal .SearchContext ;
3743
3844import java .io .IOException ;
3945import java .util .List ;
4046import java .util .Map ;
47+ import java .util .function .Function ;
48+
49+ import static org .elasticsearch .search .aggregations .metrics .min .MinAggregator .getPointReaderOrNull ;
4150
4251public class MaxAggregator extends NumericMetricsAggregator .SingleValue {
4352
4453 final ValuesSource .Numeric valuesSource ;
4554 final DocValueFormat formatter ;
4655
56+ final String pointField ;
57+ final Function <byte [], Number > pointConverter ;
58+
4759 DoubleArray maxes ;
4860
49- public MaxAggregator (String name , ValuesSource .Numeric valuesSource , DocValueFormat formatter ,
50- SearchContext context ,
51- Aggregator parent , List <PipelineAggregator > pipelineAggregators ,
52- Map <String , Object > metaData ) throws IOException {
61+ MaxAggregator (String name ,
62+ ValuesSourceConfig <ValuesSource .Numeric > config ,
63+ ValuesSource .Numeric valuesSource ,
64+ SearchContext context ,
65+ Aggregator parent , List <PipelineAggregator > pipelineAggregators ,
66+ Map <String , Object > metaData ) throws IOException {
5367 super (name , context , parent , pipelineAggregators , metaData );
5468 this .valuesSource = valuesSource ;
55- this .formatter = formatter ;
5669 if (valuesSource != null ) {
5770 maxes = context .bigArrays ().newDoubleArray (1 , false );
5871 maxes .fill (0 , maxes .size (), Double .NEGATIVE_INFINITY );
5972 }
73+ this .formatter = config .format ();
74+ this .pointConverter = getPointReaderOrNull (context , parent , config );
75+ if (pointConverter != null ) {
76+ pointField = config .fieldContext ().field ();
77+ } else {
78+ pointField = null ;
79+ }
6080 }
6181
6282 @ Override
@@ -68,8 +88,28 @@ public boolean needsScores() {
6888 public LeafBucketCollector getLeafCollector (LeafReaderContext ctx ,
6989 final LeafBucketCollector sub ) throws IOException {
7090 if (valuesSource == null ) {
71- return LeafBucketCollector .NO_OP_COLLECTOR ;
72- }
91+ if (parent != null ) {
92+ return LeafBucketCollector .NO_OP_COLLECTOR ;
93+ } else {
94+ // we have no parent and the values source is empty so we can skip collecting hits.
95+ throw new CollectionTerminatedException ();
96+ }
97+ }
98+ if (pointConverter != null ) {
99+ Number segMax = findLeafMaxValue (ctx .reader (), pointField , pointConverter );
100+ if (segMax != null ) {
101+ /**
102+ * There is no parent aggregator (see {@link MinAggregator#getPointReaderOrNull}
103+ * so the ordinal for the bucket is always 0.
104+ */
105+ assert maxes .size () == 1 ;
106+ double max = maxes .get (0 );
107+ max = Math .max (max , segMax .doubleValue ());
108+ maxes .set (0 , max );
109+ // the maximum value has been extracted, we don't need to collect hits on this segment.
110+ throw new CollectionTerminatedException ();
111+ }
112+ }
73113 final BigArrays bigArrays = context .bigArrays ();
74114 final SortedNumericDoubleValues allValues = valuesSource .doubleValues (ctx );
75115 final NumericDoubleValues values = MultiValueMode .MAX .select (allValues );
@@ -118,4 +158,48 @@ public InternalAggregation buildEmptyAggregation() {
118158 public void doClose () {
119159 Releasables .close (maxes );
120160 }
161+
162+ /**
163+ * Returns the maximum value indexed in the <code>fieldName</code> field or <code>null</code>
164+ * if the value cannot be inferred from the indexed {@link PointValues}.
165+ */
166+ public static Number findLeafMaxValue (LeafReader reader , String fieldName , Function <byte [], Number > converter ) throws IOException {
167+ final PointValues pointValues = reader .getPointValues (fieldName );
168+ if (pointValues == null ) {
169+ return null ;
170+ }
171+ final Bits liveDocs = reader .getLiveDocs ();
172+ if (liveDocs == null ) {
173+ return converter .apply (pointValues .getMaxPackedValue ());
174+ }
175+ int numBytes = pointValues .getBytesPerDimension ();
176+ final byte [] maxValue = pointValues .getMaxPackedValue ();
177+ final Number [] result = new Number [1 ];
178+ pointValues .intersect (new PointValues .IntersectVisitor () {
179+ @ Override
180+ public void visit (int docID ) {
181+ throw new UnsupportedOperationException ();
182+ }
183+
184+ @ Override
185+ public void visit (int docID , byte [] packedValue ) {
186+ if (liveDocs .get (docID )) {
187+ // we need to collect all values in this leaf (the sort is ascending) where
188+ // the last live doc is guaranteed to contain the max value for the segment.
189+ result [0 ] = converter .apply (packedValue );
190+ }
191+ }
192+
193+ @ Override
194+ public PointValues .Relation compare (byte [] minPackedValue , byte [] maxPackedValue ) {
195+ if (FutureArrays .equals (maxValue , 0 , numBytes , maxPackedValue , 0 , numBytes )) {
196+ // we only check leaves that contain the max value for the segment.
197+ return PointValues .Relation .CELL_CROSSES_QUERY ;
198+ } else {
199+ return PointValues .Relation .CELL_OUTSIDE_QUERY ;
200+ }
201+ }
202+ });
203+ return result [0 ];
204+ }
121205}
0 commit comments