Skip to content

Commit bfedd11

Browse files
committed
Aggregations: Adds ability to sort on multiple criteria
The terms aggregation can now support sorting on multiple criteria by replacing the sort object with an array or sort object whose order signifies the priority of the sort. The existing syntax for sorting on a single criteria also still works. Contributes to #6917
1 parent 11fe940 commit bfedd11

20 files changed

+909
-142
lines changed

docs/reference/search/aggregations/bucket/terms-aggregation.asciidoc

+90-58
Large diffs are not rendered by default.

src/main/java/org/elasticsearch/search/aggregations/bucket/terms/AbstractStringTermsAggregator.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ abstract class AbstractStringTermsAggregator extends TermsAggregator {
3333

3434
public AbstractStringTermsAggregator(String name, AggregatorFactories factories,
3535
long estimatedBucketsCount, AggregationContext context, Aggregator parent,
36-
InternalOrder order, BucketCountThresholds bucketCountThresholds, SubAggCollectionMode subAggCollectMode, boolean showTermDocCountError) {
36+
Terms.Order order, BucketCountThresholds bucketCountThresholds, SubAggCollectionMode subAggCollectMode, boolean showTermDocCountError) {
3737
super(name, BucketAggregationMode.PER_BUCKET, factories, estimatedBucketsCount, context, parent, bucketCountThresholds, order, subAggCollectMode);
3838
this.showTermDocCountError = showTermDocCountError;
3939
}

src/main/java/org/elasticsearch/search/aggregations/bucket/terms/DoubleTerms.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ Bucket newBucket(long docCount, InternalAggregations aggs, long docCountError) {
9898

9999
DoubleTerms() {} // for serialization
100100

101-
public DoubleTerms(String name, InternalOrder order, @Nullable ValueFormatter formatter, int requiredSize, int shardSize, long minDocCount, List<InternalTerms.Bucket> buckets, boolean showTermDocCountError, long docCountError) {
101+
public DoubleTerms(String name, Terms.Order order, @Nullable ValueFormatter formatter, int requiredSize, int shardSize, long minDocCount, List<InternalTerms.Bucket> buckets, boolean showTermDocCountError, long docCountError) {
102102
super(name, order, requiredSize, shardSize, minDocCount, buckets, showTermDocCountError, docCountError);
103103
this.formatter = formatter;
104104
}

src/main/java/org/elasticsearch/search/aggregations/bucket/terms/DoubleTermsAggregator.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
public class DoubleTermsAggregator extends LongTermsAggregator {
3838

3939
public DoubleTermsAggregator(String name, AggregatorFactories factories, ValuesSource.Numeric valuesSource, @Nullable ValueFormat format, long estimatedBucketCount,
40-
InternalOrder order, BucketCountThresholds bucketCountThresholds, AggregationContext aggregationContext, Aggregator parent, SubAggCollectionMode collectionMode, boolean showTermDocCountError) {
40+
Terms.Order order, BucketCountThresholds bucketCountThresholds, AggregationContext aggregationContext, Aggregator parent, SubAggCollectionMode collectionMode, boolean showTermDocCountError) {
4141
super(name, factories, valuesSource, format, estimatedBucketCount, order, bucketCountThresholds, aggregationContext, parent, collectionMode, showTermDocCountError);
4242
}
4343

src/main/java/org/elasticsearch/search/aggregations/bucket/terms/GlobalOrdinalsStringTermsAggregator.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ public class GlobalOrdinalsStringTermsAggregator extends AbstractStringTermsAggr
6868
protected Collector collector;
6969

7070
public GlobalOrdinalsStringTermsAggregator(String name, AggregatorFactories factories, ValuesSource.Bytes.WithOrdinals.FieldData valuesSource, long estimatedBucketCount,
71-
long maxOrd, InternalOrder order, BucketCountThresholds bucketCountThresholds,
71+
long maxOrd, Terms.Order order, BucketCountThresholds bucketCountThresholds,
7272
IncludeExclude includeExclude, AggregationContext aggregationContext, Aggregator parent, SubAggCollectionMode collectionMode, boolean showTermDocCountError) {
7373
super(name, factories, maxOrd, aggregationContext, parent, order, bucketCountThresholds, collectionMode, showTermDocCountError);
7474
this.valuesSource = valuesSource;
@@ -249,7 +249,7 @@ public static class WithHash extends GlobalOrdinalsStringTermsAggregator {
249249
private final LongHash bucketOrds;
250250

251251
public WithHash(String name, AggregatorFactories factories, ValuesSource.Bytes.WithOrdinals.FieldData valuesSource, long estimatedBucketCount,
252-
long maxOrd, InternalOrder order, BucketCountThresholds bucketCountThresholds, IncludeExclude includeExclude, AggregationContext aggregationContext,
252+
long maxOrd, Terms.Order order, BucketCountThresholds bucketCountThresholds, IncludeExclude includeExclude, AggregationContext aggregationContext,
253253
Aggregator parent, SubAggCollectionMode collectionMode, boolean showTermDocCountError) {
254254
// Set maxOrd to estimatedBucketCount! To be conservative with memory.
255255
super(name, factories, valuesSource, estimatedBucketCount, estimatedBucketCount, order, bucketCountThresholds, includeExclude, aggregationContext, parent, collectionMode, showTermDocCountError);
@@ -318,7 +318,7 @@ public static class LowCardinality extends GlobalOrdinalsStringTermsAggregator {
318318
private RandomAccessOrds segmentOrds;
319319

320320
public LowCardinality(String name, AggregatorFactories factories, ValuesSource.Bytes.WithOrdinals.FieldData valuesSource, long estimatedBucketCount,
321-
long maxOrd, InternalOrder order, BucketCountThresholds bucketCountThresholds, AggregationContext aggregationContext, Aggregator parent, SubAggCollectionMode collectionMode, boolean showTermDocCountError) {
321+
long maxOrd, Terms.Order order, BucketCountThresholds bucketCountThresholds, AggregationContext aggregationContext, Aggregator parent, SubAggCollectionMode collectionMode, boolean showTermDocCountError) {
322322
super(name, factories, valuesSource, estimatedBucketCount, maxOrd, order, bucketCountThresholds, null, aggregationContext, parent, collectionMode, showTermDocCountError);
323323
assert factories == null || factories.count() == 0;
324324
this.segmentDocCounts = bigArrays.newIntArray(maxOrd + 1, true);

src/main/java/org/elasticsearch/search/aggregations/bucket/terms/InternalOrder.java

+105-26
Original file line numberDiff line numberDiff line change
@@ -27,68 +27,78 @@
2727
import org.elasticsearch.search.aggregations.bucket.BucketsAggregator;
2828
import org.elasticsearch.search.aggregations.bucket.MultiBucketsAggregation;
2929
import org.elasticsearch.search.aggregations.bucket.SingleBucketAggregator;
30+
import org.elasticsearch.search.aggregations.bucket.terms.Terms.Bucket;
3031
import org.elasticsearch.search.aggregations.metrics.NumericMetricsAggregator;
3132
import org.elasticsearch.search.aggregations.support.OrderPath;
3233

3334
import java.io.IOException;
34-
import java.util.Comparator;
35+
import java.util.*;
3536

3637
/**
3738
*
3839
*/
3940
class InternalOrder extends Terms.Order {
4041

42+
private static final byte COUNT_DESC_ID = 1;
43+
private static final byte COUNT_ASC_ID = 2;
44+
private static final byte TERM_DESC_ID = 3;
45+
private static final byte TERM_ASC_ID = 4;
46+
4147
/**
4248
* Order by the (higher) count of each term.
4349
*/
44-
public static final InternalOrder COUNT_DESC = new InternalOrder((byte) 1, "_count", false, new Comparator<Terms.Bucket>() {
50+
public static final InternalOrder COUNT_DESC = new InternalOrder(COUNT_DESC_ID, "_count", false, new Comparator<Terms.Bucket>() {
4551
@Override
4652
public int compare(Terms.Bucket o1, Terms.Bucket o2) {
47-
int cmp = - Long.compare(o1.getDocCount(), o2.getDocCount());
48-
if (cmp == 0) {
49-
cmp = o1.compareTerm(o2);
50-
}
51-
return cmp;
53+
return Long.compare(o2.getDocCount(), o1.getDocCount());
5254
}
5355
});
5456

5557
/**
5658
* Order by the (lower) count of each term.
5759
*/
58-
public static final InternalOrder COUNT_ASC = new InternalOrder((byte) 2, "_count", true, new Comparator<Terms.Bucket>() {
60+
public static final InternalOrder COUNT_ASC = new InternalOrder(COUNT_ASC_ID, "_count", true, new Comparator<Terms.Bucket>() {
5961

6062
@Override
6163
public int compare(Terms.Bucket o1, Terms.Bucket o2) {
62-
int cmp = Long.compare(o1.getDocCount(), o2.getDocCount());
63-
if (cmp == 0) {
64-
cmp = o1.compareTerm(o2);
65-
}
66-
return cmp;
64+
return Long.compare(o1.getDocCount(), o2.getDocCount());
6765
}
6866
});
6967

7068
/**
7169
* Order by the terms.
7270
*/
73-
public static final InternalOrder TERM_DESC = new InternalOrder((byte) 3, "_term", false, new Comparator<Terms.Bucket>() {
71+
public static final InternalOrder TERM_DESC = new InternalOrder(TERM_DESC_ID, "_term", false, new Comparator<Terms.Bucket>() {
7472

7573
@Override
7674
public int compare(Terms.Bucket o1, Terms.Bucket o2) {
77-
return - o1.compareTerm(o2);
75+
return o2.compareTerm(o1);
7876
}
7977
});
8078

8179
/**
8280
* Order by the terms.
8381
*/
84-
public static final InternalOrder TERM_ASC = new InternalOrder((byte) 4, "_term", true, new Comparator<Terms.Bucket>() {
82+
public static final InternalOrder TERM_ASC = new InternalOrder(TERM_ASC_ID, "_term", true, new Comparator<Terms.Bucket>() {
8583

8684
@Override
8785
public int compare(Terms.Bucket o1, Terms.Bucket o2) {
8886
return o1.compareTerm(o2);
8987
}
9088
});
9189

90+
public static boolean isCountDesc(Terms.Order order) {
91+
if (order == COUNT_DESC) {
92+
return true;
93+
}else if (order instanceof CompoundOrder) {
94+
// check if its a compound order with count desc and the tie breaker (term asc)
95+
CompoundOrder compoundOrder = (CompoundOrder) order;
96+
if (compoundOrder.compoundOrder.size() == 2 && compoundOrder.compoundOrder.get(0) == COUNT_DESC && compoundOrder.compoundOrder.get(1) == TERM_ASC) {
97+
return true;
98+
}
99+
}
100+
return false;
101+
}
92102

93103
final byte id;
94104
final String key;
@@ -116,8 +126,13 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws
116126
return builder.startObject().field(key, asc ? "asc" : "desc").endObject();
117127
}
118128

119-
public static InternalOrder validate(InternalOrder order, Aggregator termsAggregator) {
120-
if (!(order instanceof Aggregation)) {
129+
public static Terms.Order validate(Terms.Order order, Aggregator termsAggregator) {
130+
if (order instanceof CompoundOrder) {
131+
for (Terms.Order innerOrder : ((CompoundOrder)order).compoundOrder) {
132+
validate(innerOrder, termsAggregator);
133+
}
134+
return order;
135+
} else if (!(order instanceof Aggregation)) {
121136
return order;
122137
}
123138
OrderPath path = ((Aggregation) order).path();
@@ -199,12 +214,63 @@ public int compare(Terms.Bucket o1, Terms.Bucket o2) {
199214
}
200215
}
201216

217+
static class CompoundOrder extends Terms.Order{
218+
219+
static final byte ID = -1;
220+
221+
private final List<Terms.Order> compoundOrder;
222+
223+
public CompoundOrder(List<Terms.Order> compoundOrder) {
224+
this.compoundOrder = new LinkedList<>(compoundOrder);
225+
}
226+
227+
@Override
228+
byte id() {
229+
return ID;
230+
}
231+
232+
@Override
233+
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
234+
builder.startArray();
235+
for (Terms.Order order : compoundOrder) {
236+
order.toXContent(builder, params);
237+
}
238+
return builder.endArray();
239+
}
240+
241+
@Override
242+
protected Comparator<Bucket> comparator(Aggregator aggregator) {
243+
return new CompoundOrderComparator(compoundOrder, aggregator);
244+
}
245+
246+
public static class CompoundOrderComparator implements Comparator<Terms.Bucket> {
247+
248+
private List<Terms.Order> compoundOrder;
249+
private Aggregator aggregator;
250+
251+
public CompoundOrderComparator(List<Terms.Order> compoundOrder, Aggregator aggregator) {
252+
this.compoundOrder = compoundOrder;
253+
this.aggregator = aggregator;
254+
}
255+
256+
@Override
257+
public int compare(Bucket o1, Bucket o2) {
258+
int result = 0;
259+
for (Iterator<Terms.Order> itr = compoundOrder.iterator(); itr.hasNext() && result == 0;) {
260+
result = itr.next().comparator(aggregator).compare(o1, o2);
261+
}
262+
return result;
263+
}
264+
}
265+
}
266+
202267
public static class Streams {
203268

204-
public static void writeOrder(InternalOrder order, StreamOutput out) throws IOException {
269+
public static void writeOrder(Terms.Order order, StreamOutput out) throws IOException {
205270
out.writeByte(order.id());
206271
if (order instanceof Aggregation) {
207-
out.writeBoolean(((MultiBucketsAggregation.Bucket.SubAggregationComparator) order.comparator).asc());
272+
Aggregation aggregationOrder = (Aggregation) order;
273+
out.writeBoolean(((MultiBucketsAggregation.Bucket.SubAggregationComparator) aggregationOrder.comparator).asc());
208274
OrderPath path = ((Aggregation) order).path();
209275
if (out.getVersion().onOrAfter(Version.V_1_1_0)) {
210276
out.writeString(path.toString());
@@ -218,17 +284,23 @@ public static void writeOrder(InternalOrder order, StreamOutput out) throws IOEx
218284
out.writeString(token.key);
219285
}
220286
}
287+
} else if (order instanceof CompoundOrder) {
288+
CompoundOrder compoundOrder = (CompoundOrder) order;
289+
out.writeVInt(compoundOrder.compoundOrder.size());
290+
for (Terms.Order innerOrder : compoundOrder.compoundOrder) {
291+
Streams.writeOrder(innerOrder, out);
292+
}
221293
}
222294
}
223295

224-
public static InternalOrder readOrder(StreamInput in) throws IOException {
296+
public static Terms.Order readOrder(StreamInput in) throws IOException {
225297
byte id = in.readByte();
226298
switch (id) {
227-
case 1: return InternalOrder.COUNT_DESC;
228-
case 2: return InternalOrder.COUNT_ASC;
229-
case 3: return InternalOrder.TERM_DESC;
230-
case 4: return InternalOrder.TERM_ASC;
231-
case 0:
299+
case COUNT_DESC_ID: return InternalOrder.COUNT_DESC;
300+
case COUNT_ASC_ID: return InternalOrder.COUNT_ASC;
301+
case TERM_DESC_ID: return InternalOrder.TERM_DESC;
302+
case TERM_ASC_ID: return InternalOrder.TERM_ASC;
303+
case Aggregation.ID:
232304
boolean asc = in.readBoolean();
233305
String key = in.readString();
234306
if (in.getVersion().onOrAfter(Version.V_1_1_0)) {
@@ -239,6 +311,13 @@ public static InternalOrder readOrder(StreamInput in) throws IOException {
239311
return new InternalOrder.Aggregation(key + "." + in.readString(), asc);
240312
}
241313
return new InternalOrder.Aggregation(key, asc);
314+
case CompoundOrder.ID:
315+
int size = in.readVInt();
316+
List<Terms.Order> compoundOrder = new ArrayList<>(size);
317+
for (int i = 0; i < size; i++) {
318+
compoundOrder.add(Streams.readOrder(in));
319+
}
320+
return new CompoundOrder(compoundOrder);
242321
default:
243322
throw new RuntimeException("unknown terms order");
244323
}

src/main/java/org/elasticsearch/search/aggregations/bucket/terms/InternalTerms.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ public Bucket reduce(List<? extends Bucket> buckets, ReduceContext context) {
9696
}
9797
}
9898

99-
protected InternalOrder order;
99+
protected Terms.Order order;
100100
protected int requiredSize;
101101
protected int shardSize;
102102
protected long minDocCount;
@@ -107,7 +107,7 @@ public Bucket reduce(List<? extends Bucket> buckets, ReduceContext context) {
107107

108108
protected InternalTerms() {} // for serialization
109109

110-
protected InternalTerms(String name, InternalOrder order, int requiredSize, int shardSize, long minDocCount, List<Bucket> buckets, boolean showTermDocCountError, long docCountError) {
110+
protected InternalTerms(String name, Terms.Order order, int requiredSize, int shardSize, long minDocCount, List<Bucket> buckets, boolean showTermDocCountError, long docCountError) {
111111
super(name);
112112
this.order = order;
113113
this.requiredSize = requiredSize;
@@ -150,7 +150,7 @@ public InternalAggregation reduce(ReduceContext reduceContext) {
150150
final long thisAggDocCountError;
151151
if (terms.buckets.size() < this.shardSize || this.order == InternalOrder.TERM_ASC || this.order == InternalOrder.TERM_DESC) {
152152
thisAggDocCountError = 0;
153-
} else if (this.order == InternalOrder.COUNT_DESC) {
153+
} else if (InternalOrder.isCountDesc(this.order)) {
154154
thisAggDocCountError = terms.buckets.get(terms.buckets.size() - 1).docCount;
155155
} else {
156156
thisAggDocCountError = -1;

src/main/java/org/elasticsearch/search/aggregations/bucket/terms/LongTerms.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ Bucket newBucket(long docCount, InternalAggregations aggs, long docCountError) {
9999

100100
LongTerms() {} // for serialization
101101

102-
public LongTerms(String name, InternalOrder order, @Nullable ValueFormatter formatter, int requiredSize, int shardSize, long minDocCount, List<InternalTerms.Bucket> buckets, boolean showTermDocCountError, long docCountError) {
102+
public LongTerms(String name, Terms.Order order, @Nullable ValueFormatter formatter, int requiredSize, int shardSize, long minDocCount, List<InternalTerms.Bucket> buckets, boolean showTermDocCountError, long docCountError) {
103103
super(name, order, requiredSize, shardSize, minDocCount, buckets, showTermDocCountError, docCountError);
104104
this.formatter = formatter;
105105
}

src/main/java/org/elasticsearch/search/aggregations/bucket/terms/LongTermsAggregator.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ public class LongTermsAggregator extends TermsAggregator {
4848
private SortedNumericDocValues values;
4949

5050
public LongTermsAggregator(String name, AggregatorFactories factories, ValuesSource.Numeric valuesSource, @Nullable ValueFormat format, long estimatedBucketCount,
51-
InternalOrder order, BucketCountThresholds bucketCountThresholds, AggregationContext aggregationContext, Aggregator parent, SubAggCollectionMode subAggCollectMode, boolean showTermDocCountError) {
51+
Terms.Order order, BucketCountThresholds bucketCountThresholds, AggregationContext aggregationContext, Aggregator parent, SubAggCollectionMode subAggCollectMode, boolean showTermDocCountError) {
5252
super(name, BucketAggregationMode.PER_BUCKET, factories, estimatedBucketCount, aggregationContext, parent, bucketCountThresholds, order, subAggCollectMode);
5353
this.valuesSource = valuesSource;
5454
this.showTermDocCountError = showTermDocCountError;

src/main/java/org/elasticsearch/search/aggregations/bucket/terms/StringTerms.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ Bucket newBucket(long docCount, InternalAggregations aggs, long docCountError) {
9898

9999
StringTerms() {} // for serialization
100100

101-
public StringTerms(String name, InternalOrder order, int requiredSize, int shardSize, long minDocCount, List<InternalTerms.Bucket> buckets, boolean showTermDocCountError, long docCountError) {
101+
public StringTerms(String name, Terms.Order order, int requiredSize, int shardSize, long minDocCount, List<InternalTerms.Bucket> buckets, boolean showTermDocCountError, long docCountError) {
102102
super(name, order, requiredSize, shardSize, minDocCount, buckets, showTermDocCountError, docCountError);
103103
}
104104

src/main/java/org/elasticsearch/search/aggregations/bucket/terms/StringTermsAggregator.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ public class StringTermsAggregator extends AbstractStringTermsAggregator {
4848
private final BytesRefBuilder previous;
4949

5050
public StringTermsAggregator(String name, AggregatorFactories factories, ValuesSource valuesSource, long estimatedBucketCount,
51-
InternalOrder order, BucketCountThresholds bucketCountThresholds,
51+
Terms.Order order, BucketCountThresholds bucketCountThresholds,
5252
IncludeExclude includeExclude, AggregationContext aggregationContext, Aggregator parent, SubAggCollectionMode collectionMode, boolean showTermDocCountError) {
5353

5454
super(name, factories, estimatedBucketCount, aggregationContext, parent, order, bucketCountThresholds, collectionMode, showTermDocCountError);

0 commit comments

Comments
 (0)