Skip to content

Commit 86d5211

Browse files
authored
Make sorting by an agg results a real abstraction (#52007) (#52212)
This removes a bunch of `instanceof`s in favor of two new methods on `InernalAggregation`. The default implementations of these methods just throw exceptions explaining that you can't sort on this aggregation. They are overridden by all of the classes that used to have `instanceof` checks against them. I doubt this is really any faster in practice. The real benefit here is that it is a little more obvious *that* you can sort by the results of an aggregation and it should be *much* more obvious where to look at *how* aggregations sort themselves. There are still a bunch more `instanceof`s in left in `AggregationPath` but those will wait for a followup change.
1 parent cc1fce9 commit 86d5211

File tree

8 files changed

+90
-67
lines changed

8 files changed

+90
-67
lines changed

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

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

3232
import java.io.IOException;
33+
import java.util.Iterator;
3334
import java.util.List;
3435
import java.util.Map;
3536
import java.util.Objects;
@@ -242,4 +243,20 @@ public String toString() {
242243
return Strings.toString(this);
243244
}
244245

246+
/**
247+
* Get value to use when sorting by this aggregation.
248+
*/
249+
public double sortValue(String key) {
250+
// subclasses will override this with a real implementation if they can be sorted
251+
throw new IllegalArgumentException("Can't sort a [" + getType() + "] aggregation [" + getName() + "]");
252+
}
253+
254+
/**
255+
* Get value to use when sorting by a descendant of this aggregation.
256+
*/
257+
public double sortValue(AggregationPath.PathElement head, Iterator<AggregationPath.PathElement> tail) {
258+
// subclasses will override this with a real implementation if you can sort on a descendant
259+
throw new IllegalArgumentException("Can't sort by a descendant of a [" + getType() + "] aggregation [" + head + "]");
260+
}
261+
245262
}

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,14 @@
2525
import org.elasticsearch.search.aggregations.InternalAggregation.ReduceContext;
2626
import org.elasticsearch.search.aggregations.pipeline.PipelineAggregator;
2727
import org.elasticsearch.search.aggregations.pipeline.SiblingPipelineAggregator;
28+
import org.elasticsearch.search.aggregations.support.AggregationPath;
2829

2930
import java.io.IOException;
3031
import java.util.ArrayList;
3132
import java.util.Collections;
3233
import java.util.Comparator;
3334
import java.util.HashMap;
35+
import java.util.Iterator;
3436
import java.util.List;
3537
import java.util.Map;
3638
import java.util.Objects;
@@ -104,6 +106,20 @@ private List<InternalAggregation> getInternalAggregations() {
104106
return (List<InternalAggregation>) aggregations;
105107
}
106108

109+
/**
110+
* Get value to use when sorting by a descendant of the aggregation containing this.
111+
*/
112+
public double sortValue(AggregationPath.PathElement head, Iterator<AggregationPath.PathElement> tail) {
113+
InternalAggregation aggregation = get(head.name);
114+
if (aggregation == null) {
115+
throw new IllegalArgumentException("Cannot find aggregation named [" + head.name + "]");
116+
}
117+
if (tail.hasNext()) {
118+
return aggregation.sortValue(tail.next(), tail);
119+
}
120+
return aggregation.sortValue(head.key);
121+
}
122+
107123
/**
108124
* Begin the reduction process. This should be the entry point for the "first" reduction, e.g. called by
109125
* SearchPhaseController or anywhere else that wants to initiate a reduction. It _should not_ be called

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,5 +192,6 @@ public Object getProperty(String containingAggName, List<String> path) {
192192
}
193193
return aggregation.getProperty(path.subList(1, path.size()));
194194
}
195+
195196
}
196197
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -156,8 +156,8 @@ static class AggregationComparator implements Comparator<Bucket> {
156156

157157
@Override
158158
public int compare(Bucket b1, Bucket b2) {
159-
double v1 = path.resolveValue(b1);
160-
double v2 = path.resolveValue(b2);
159+
double v1 = path.resolveValue(((InternalAggregations) b1.getAggregations()));
160+
double v2 = path.resolveValue(((InternalAggregations) b2.getAggregations()));
161161
return Comparators.compareDiscardNaN(v1, v2, asc);
162162
}
163163
}

server/src/main/java/org/elasticsearch/search/aggregations/bucket/InternalSingleBucketAggregation.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,11 @@
2525
import org.elasticsearch.search.aggregations.InternalAggregation;
2626
import org.elasticsearch.search.aggregations.InternalAggregations;
2727
import org.elasticsearch.search.aggregations.pipeline.PipelineAggregator;
28+
import org.elasticsearch.search.aggregations.support.AggregationPath;
2829

2930
import java.io.IOException;
3031
import java.util.ArrayList;
32+
import java.util.Iterator;
3133
import java.util.List;
3234
import java.util.Map;
3335
import java.util.Objects;
@@ -152,6 +154,21 @@ public XContentBuilder doXContentBody(XContentBuilder builder, Params params) th
152154
return builder;
153155
}
154156

157+
@Override
158+
public final double sortValue(String key) {
159+
if (key != null && false == key.equals("doc_count")) {
160+
throw new IllegalArgumentException(
161+
"Unknown value key [" + key + "] for single-bucket aggregation [" + getName() +
162+
"]. Either use [doc_count] as key or drop the key all together.");
163+
}
164+
return docCount;
165+
}
166+
167+
@Override
168+
public final double sortValue(AggregationPath.PathElement head, Iterator<AggregationPath.PathElement> tail) {
169+
return aggregations.sortValue(head, tail);
170+
}
171+
155172
@Override
156173
public boolean equals(Object obj) {
157174
if (this == obj) return true;

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

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,10 @@
2222
import org.elasticsearch.search.DocValueFormat;
2323
import org.elasticsearch.search.aggregations.InternalAggregation;
2424
import org.elasticsearch.search.aggregations.pipeline.PipelineAggregator;
25+
import org.elasticsearch.search.aggregations.support.AggregationPath;
2526

2627
import java.io.IOException;
28+
import java.util.Iterator;
2729
import java.util.List;
2830
import java.util.Map;
2931
import java.util.Objects;
@@ -62,6 +64,15 @@ public Object getProperty(List<String> path) {
6264
}
6365
}
6466

67+
@Override
68+
public final double sortValue(String key) {
69+
if (key != null && false == key.equals("value")) {
70+
throw new IllegalArgumentException(
71+
"Unknown value key [" + key + "] for single-value metric aggregation [" + getName() +
72+
"]. Either use [value] as key or drop the key all together");
73+
}
74+
return value();
75+
}
6576
}
6677

6778
public abstract static class MultiValue extends InternalNumericMetricsAggregation implements NumericMetricsAggregation.MultiValue {
@@ -92,6 +103,14 @@ public Object getProperty(List<String> path) {
92103
throw new IllegalArgumentException("path not supported for [" + getName() + "]: " + path);
93104
}
94105
}
106+
107+
@Override
108+
public final double sortValue(String key) {
109+
if (key == null) {
110+
throw new IllegalArgumentException("Missing value key in [" + key+ "] which refers to a multi-value metric aggregation");
111+
}
112+
return value(key);
113+
}
95114
}
96115

97116
private InternalNumericMetricsAggregation(String name, List<PipelineAggregator> pipelineAggregators, Map<String, Object> metaData) {
@@ -105,6 +124,11 @@ protected InternalNumericMetricsAggregation(StreamInput in) throws IOException {
105124
super(in);
106125
}
107126

127+
@Override
128+
public final double sortValue(AggregationPath.PathElement head, Iterator<AggregationPath.PathElement> tail) {
129+
throw new IllegalArgumentException("Metrics aggregations cannot have sub-aggregations (at [>" + head + "]");
130+
}
131+
108132
@Override
109133
public int hashCode() {
110134
return Objects.hash(super.hashCode(), format);

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

Lines changed: 10 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,15 @@
2020
package org.elasticsearch.search.aggregations.support;
2121

2222
import org.elasticsearch.common.Strings;
23-
import org.elasticsearch.search.aggregations.Aggregation;
2423
import org.elasticsearch.search.aggregations.AggregationExecutionException;
2524
import org.elasticsearch.search.aggregations.Aggregator;
26-
import org.elasticsearch.search.aggregations.HasAggregations;
27-
import org.elasticsearch.search.aggregations.bucket.SingleBucketAggregation;
25+
import org.elasticsearch.search.aggregations.InternalAggregations;
2826
import org.elasticsearch.search.aggregations.bucket.SingleBucketAggregator;
29-
import org.elasticsearch.search.aggregations.metrics.InternalNumericMetricsAggregation;
3027
import org.elasticsearch.search.aggregations.metrics.NumericMetricsAggregator;
3128
import org.elasticsearch.search.profile.aggregation.ProfilingAggregator;
3229

3330
import java.util.ArrayList;
31+
import java.util.Iterator;
3432
import java.util.List;
3533

3634
/**
@@ -190,61 +188,16 @@ public AggregationPath subPath(int offset, int length) {
190188
}
191189

192190
/**
193-
* Resolves the value pointed by this path given an aggregations root
194-
*
195-
* @param root The root that serves as a point of reference for this path
196-
* @return The resolved value
191+
* Looks up the value of this path against a set of aggregation results.
197192
*/
198-
public double resolveValue(HasAggregations root) {
199-
HasAggregations parent = root;
200-
double value = Double.NaN;
201-
for (int i = 0; i < pathElements.size(); i++) {
202-
AggregationPath.PathElement token = pathElements.get(i);
203-
Aggregation agg = parent.getAggregations().get(token.name);
204-
205-
if (agg == null) {
206-
throw new IllegalArgumentException("Invalid order path [" + this +
207-
"]. Cannot find aggregation named [" + token.name + "]");
208-
}
209-
210-
if (agg instanceof SingleBucketAggregation) {
211-
if (token.key != null && !token.key.equals("doc_count")) {
212-
throw new IllegalArgumentException("Invalid order path [" + this +
213-
"]. Unknown value key [" + token.key + "] for single-bucket aggregation [" + token.name +
214-
"]. Either use [doc_count] as key or drop the key all together");
215-
}
216-
parent = (SingleBucketAggregation) agg;
217-
value = ((SingleBucketAggregation) agg).getDocCount();
218-
continue;
219-
}
220-
221-
// the agg can only be a metrics agg, and a metrics agg must be at the end of the path
222-
if (i != pathElements.size() - 1) {
223-
throw new IllegalArgumentException("Invalid order path [" + this +
224-
"]. Metrics aggregations cannot have sub-aggregations (at [" + token + ">" + pathElements.get(i + 1) + "]");
225-
}
226-
227-
if (agg instanceof InternalNumericMetricsAggregation.SingleValue) {
228-
if (token.key != null && !token.key.equals("value")) {
229-
throw new IllegalArgumentException("Invalid order path [" + this +
230-
"]. Unknown value key [" + token.key + "] for single-value metric aggregation [" + token.name +
231-
"]. Either use [value] as key or drop the key all together");
232-
}
233-
parent = null;
234-
value = ((InternalNumericMetricsAggregation.SingleValue) agg).value();
235-
continue;
236-
}
237-
238-
// we're left with a multi-value metric agg
239-
if (token.key == null) {
240-
throw new IllegalArgumentException("Invalid order path [" + this +
241-
"]. Missing value key in [" + token + "] which refers to a multi-value metric aggregation");
242-
}
243-
parent = null;
244-
value = ((InternalNumericMetricsAggregation.MultiValue) agg).value(token.key);
193+
public double resolveValue(InternalAggregations aggregations) {
194+
try {
195+
Iterator<PathElement> path = pathElements.iterator();
196+
assert path.hasNext();
197+
return aggregations.sortValue(path.next(), path);
198+
} catch (IllegalArgumentException e) {
199+
throw new IllegalArgumentException("Invalid order path [" + this + "]. " + e.getMessage(), e);
245200
}
246-
247-
return value;
248201
}
249202

250203
/**

server/src/test/java/org/elasticsearch/search/aggregations/support/PathTests.java renamed to server/src/test/java/org/elasticsearch/search/aggregations/support/AggregationPathTests.java

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727

2828
import static org.hamcrest.Matchers.equalTo;
2929

30-
public class PathTests extends ESTestCase {
30+
public class AggregationPathTests extends ESTestCase {
3131
public void testInvalidPaths() throws Exception {
3232
assertInvalidPath("[foo]", "brackets at the beginning of the token expression");
3333
assertInvalidPath("foo[bar", "open brackets without closing at the token expression");
@@ -49,13 +49,8 @@ public void testValidPaths() throws Exception {
4949
assertValidPath("foo.bar>baz[qux]", tokens().add("foo.bar").add("baz", "qux"));
5050
}
5151

52-
private void assertInvalidPath(String path, String reason) {
53-
try {
54-
AggregationPath.parse(path);
55-
fail("Expected parsing path [" + path + "] to fail - " + reason);
56-
} catch (AggregationExecutionException aee) {
57-
// expected
58-
}
52+
private AggregationExecutionException assertInvalidPath(String path, String reason) {
53+
return expectThrows(AggregationExecutionException.class, () -> AggregationPath.parse(path));
5954
}
6055

6156
private void assertValidPath(String path, Tokens tokenz) {

0 commit comments

Comments
 (0)