@@ -182,7 +182,7 @@ public List<String> getPathElementsAsStringList() {
182182 return stringPathElements ;
183183 }
184184
185- private AggregationPath subPath (int offset , int length ) {
185+ public AggregationPath subPath (int offset , int length ) {
186186 List <PathElement > subTokens = new ArrayList <>(pathElements .subList (offset , offset + length ));
187187 return new AggregationPath (subTokens );
188188 }
@@ -196,29 +196,38 @@ public double resolveValue(InternalAggregations aggregations) {
196196 assert path .hasNext ();
197197 return aggregations .sortValue (path .next (), path );
198198 } catch (IllegalArgumentException e ) {
199- throw new IllegalArgumentException ("Invalid aggregation order path [" + this + "]. " + e .getMessage (), e );
199+ throw new IllegalArgumentException ("Invalid order path [" + this + "]. " + e .getMessage (), e );
200200 }
201201 }
202202
203203 /**
204- * Resolves the {@linkplain Aggregator} pointed to by this path against
205- * the given root {@linkplain Aggregator}.
204+ * Resolves the aggregator pointed by this path using the given root as a point of reference.
205+ *
206+ * @param root The point of reference of this path
207+ * @return The aggregator pointed by this path starting from the given aggregator as a point of reference
206208 */
207209 public Aggregator resolveAggregator (Aggregator root ) {
208- Iterator <PathElement > path = pathElements .iterator ();
209- assert path .hasNext ();
210- return root .resolveSortPathOnValidAgg (path .next (), path );
210+ Aggregator aggregator = root ;
211+ for (int i = 0 ; i < pathElements .size (); i ++) {
212+ AggregationPath .PathElement token = pathElements .get (i );
213+ aggregator = ProfilingAggregator .unwrap (aggregator .subAggregator (token .name ));
214+ assert (aggregator instanceof SingleBucketAggregator && i <= pathElements .size () - 1 )
215+ || (aggregator instanceof NumericMetricsAggregator && i == pathElements .size () - 1 ) :
216+ "this should be picked up before aggregation execution - on validate" ;
217+ }
218+ return aggregator ;
211219 }
212220
213221 /**
214- * Resolves the {@linkplain Aggregator} pointed to by the first element
215- * of this path against the given root {@linkplain Aggregator}.
222+ * Resolves the topmost aggregator pointed by this path using the given root as a point of reference.
223+ *
224+ * @param root The point of reference of this path
225+ * @return The first child aggregator of the root pointed by this path
216226 */
217227 public Aggregator resolveTopmostAggregator (Aggregator root ) {
218228 AggregationPath .PathElement token = pathElements .get (0 );
219- // TODO both unwrap and subAggregator are only used here!
220229 Aggregator aggregator = ProfilingAggregator .unwrap (root .subAggregator (token .name ));
221- assert (aggregator instanceof SingleBucketAggregator )
230+ assert (aggregator instanceof SingleBucketAggregator )
222231 || (aggregator instanceof NumericMetricsAggregator ) : "this should be picked up before aggregation execution - on validate" ;
223232 return aggregator ;
224233 }
@@ -230,10 +239,76 @@ public Aggregator resolveTopmostAggregator(Aggregator root) {
230239 * @throws AggregationExecutionException on validation error
231240 */
232241 public void validate (Aggregator root ) throws AggregationExecutionException {
233- try {
234- resolveAggregator (root ).validateSortPathKey (lastPathElement ().key );
235- } catch (IllegalArgumentException e ) {
236- throw new AggregationExecutionException ("Invalid aggregation order path [" + this + "]. " + e .getMessage (), e );
242+ Aggregator aggregator = root ;
243+ for (int i = 0 ; i < pathElements .size (); i ++) {
244+ String name = pathElements .get (i ).name ;
245+ aggregator = ProfilingAggregator .unwrap (aggregator .subAggregator (name ));
246+ if (aggregator == null ) {
247+ throw new AggregationExecutionException ("Invalid aggregator order path [" + this + "]. The " +
248+ "provided aggregation [" + name + "] either does not exist, or is a pipeline aggregation " +
249+ "and cannot be used to sort the buckets." );
250+ }
251+
252+ if (i < pathElements .size () - 1 ) {
253+
254+ // we're in the middle of the path, so the aggregator can only be a single-bucket aggregator
255+
256+ if (!(aggregator instanceof SingleBucketAggregator )) {
257+ throw new AggregationExecutionException ("Invalid aggregation order path [" + this +
258+ "]. Buckets can only be sorted on a sub-aggregator path " +
259+ "that is built out of zero or more single-bucket aggregations within the path and a final " +
260+ "single-bucket or a metrics aggregation at the path end. Sub-path [" +
261+ subPath (0 , i + 1 ) + "] points to non single-bucket aggregation" );
262+ }
263+
264+ if (pathElements .get (i ).key != null ) {
265+ throw new AggregationExecutionException ("Invalid aggregation order path [" + this +
266+ "]. Buckets can only be sorted on a sub-aggregator path " +
267+ "that is built out of zero or more single-bucket aggregations within the path and a " +
268+ "final single-bucket or a metrics aggregation at the path end. Sub-path [" +
269+ subPath (0 , i + 1 ) + "] points to non single-bucket aggregation" );
270+ }
271+ }
272+ }
273+ boolean singleBucket = aggregator instanceof SingleBucketAggregator ;
274+ if (!singleBucket && !(aggregator instanceof NumericMetricsAggregator )) {
275+ throw new AggregationExecutionException ("Invalid aggregation order path [" + this +
276+ "]. Buckets can only be sorted on a sub-aggregator path " +
277+ "that is built out of zero or more single-bucket aggregations within the path and a final " +
278+ "single-bucket or a metrics aggregation at the path end." );
279+ }
280+
281+ AggregationPath .PathElement lastToken = lastPathElement ();
282+
283+ if (singleBucket ) {
284+ if (lastToken .key != null && !"doc_count" .equals (lastToken .key )) {
285+ throw new AggregationExecutionException ("Invalid aggregation order path [" + this +
286+ "]. Ordering on a single-bucket aggregation can only be done on its doc_count. " +
287+ "Either drop the key (a la \" " + lastToken .name + "\" ) or change it to \" doc_count\" (a la \" " + lastToken .name +
288+ ".doc_count\" )" );
289+ }
290+ return ; // perfectly valid to sort on single-bucket aggregation (will be sored on its doc_count)
291+ }
292+
293+ if (aggregator instanceof NumericMetricsAggregator .SingleValue ) {
294+ if (lastToken .key != null && !"value" .equals (lastToken .key )) {
295+ throw new AggregationExecutionException ("Invalid aggregation order path [" + this +
296+ "]. Ordering on a single-value metrics aggregation can only be done on its value. " +
297+ "Either drop the key (a la \" " + lastToken .name + "\" ) or change it to \" value\" (a la \" " + lastToken .name +
298+ ".value\" )" );
299+ }
300+ return ; // perfectly valid to sort on single metric aggregation (will be sorted on its associated value)
301+ }
302+
303+ // the aggregator must be of a multi-value metrics type
304+ if (lastToken .key == null ) {
305+ throw new AggregationExecutionException ("Invalid aggregation order path [" + this +
306+ "]. When ordering on a multi-value metrics aggregation a metric name must be specified" );
307+ }
308+
309+ if (!((NumericMetricsAggregator .MultiValue ) aggregator ).hasMetric (lastToken .key )) {
310+ throw new AggregationExecutionException ("Invalid aggregation order path [" + this +
311+ "]. Unknown metric name [" + lastToken .key + "] on multi-value metrics aggregation [" + lastToken .name + "]" );
237312 }
238313 }
239314
0 commit comments