Skip to content

Commit 82278bb

Browse files
committed
[Aggregations] Meta data support
This commit adds the ability to associate a bit of state with each individual aggregation. The aggregation response can be hard to stitch back together without having a reference to the aggregation request. In many cases this is not available, many json serializer frameworks cache types globally or have a static deserialisation override mechanism. In these cases making the original request available, if at all possible, would be a hack. The old facets returned `_type` which was just enough metadata to know what the originating facet type in the request was. This PR takes `_type` one step further by introducing ANY arbitrary meta data. This could be further <strike>ab</strike>used for instance by generic/automated aggregations that include UI state (color information, thresholds, user input states, etc) per aggregation.
1 parent 7ec31ab commit 82278bb

File tree

94 files changed

+806
-528
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

94 files changed

+806
-528
lines changed

docs/reference/search/aggregations.asciidoc

+44
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ The following snippet captures the basic structure of aggregations:
4242
"<aggregation_type>" : {
4343
<aggregation_body>
4444
}
45+
[,"meta" : { [<meta_data_body>] } ]?
4546
[,"aggregations" : { [<sub_aggregation>]+ } ]?
4647
}
4748
[,"<aggregation_name_2>" : { ... } ]*
@@ -148,6 +149,49 @@ $ curl -XGET 'http://localhost:9200/twitter/tweet/_search?search_type=count' -d
148149
Setting `search_type` to `count` avoids executing the fetch phase of the search making the request more efficient. See
149150
<<search-request-search-type>> for more information on the `search_type` parameter.
150151

152+
[float]
153+
=== Metadata
154+
155+
You can associate a piece of metadata with individual aggregations at request time that will be returned in place
156+
at response time.
157+
158+
Consider this example where we want to associate the color blue with our `terms` aggregation.
159+
160+
[source,js]
161+
--------------------------------------------------
162+
{
163+
...
164+
aggs": {
165+
"titles": {
166+
"terms": {
167+
"field": "title"
168+
},
169+
"meta": {
170+
"color": "blue"
171+
},
172+
}
173+
}
174+
}
175+
--------------------------------------------------
176+
177+
Then that piece of metadata will be returned in place for our `titles` terms aggregation
178+
179+
[source,js]
180+
--------------------------------------------------
181+
{
182+
...
183+
"aggregations": {
184+
"titles": {
185+
"meta": {
186+
"color" : "blue"
187+
},
188+
"buckets": [
189+
]
190+
}
191+
}
192+
}
193+
--------------------------------------------------
194+
151195
include::aggregations/metrics.asciidoc[]
152196

153197
include::aggregations/bucket.asciidoc[]

src/main/java/org/elasticsearch/search/aggregations/Aggregation.java

+6
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
*/
1919
package org.elasticsearch.search.aggregations;
2020

21+
import java.util.Map;
22+
2123
/**
2224
* An aggregation
2325
*/
@@ -28,4 +30,8 @@ public interface Aggregation {
2830
*/
2931
String getName();
3032

33+
/**
34+
* Get the optional byte array metadata that was set on the aggregation
35+
*/
36+
Map<String, Object> getMetaData();
3137
}

src/main/java/org/elasticsearch/search/aggregations/AggregationBuilder.java

+12
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ public abstract class AggregationBuilder<B extends AggregationBuilder<B>> extend
3838

3939
private List<AbstractAggregationBuilder> aggregations;
4040
private BytesReference aggregationsBinary;
41+
private Map<String, Object> metaData;
4142

4243
/**
4344
* Sole constructor, typically used by sub-classes.
@@ -101,10 +102,21 @@ public B subAggregation(Map<String, Object> aggs) {
101102
}
102103
}
103104

105+
/**
106+
* Sets the meta data to be included in the aggregation response
107+
*/
108+
public B setMetaData(Map<String, Object> metaData) {
109+
this.metaData = metaData;
110+
return (B)this;
111+
}
112+
104113
@Override
105114
public final XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
106115
builder.startObject(getName());
107116

117+
if (this.metaData != null) {
118+
builder.field("meta", this.metaData);
119+
}
108120
builder.field(type);
109121
internalXContent(builder, params);
110122

src/main/java/org/elasticsearch/search/aggregations/Aggregator.java

+9-4
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ public boolean apply(Aggregator aggregator) {
4444
return aggregator.shouldCollect();
4545
}
4646
};
47+
private final Map<String, Object> metaData;
4748

4849
/**
4950
* Returns whether any of the parent aggregators has {@link BucketAggregationMode#PER_BUCKET} as a bucket aggregation mode.
@@ -60,6 +61,10 @@ public static boolean hasParentBucketAggregator(Aggregator parent) {
6061

6162
public static final ParseField COLLECT_MODE = new ParseField("collect_mode");
6263

64+
public Map<String, Object> getMetaData() {
65+
return this.metaData;
66+
}
67+
6368
/**
6469
* Defines the nature of the aggregator's aggregation execution when nested in other aggregators and the buckets they create.
6570
*/
@@ -177,9 +182,11 @@ public int nextDoc() throws IOException {
177182
* @param estimatedBucketsCount When served as a sub-aggregator, indicate how many buckets the parent aggregator will generate.
178183
* @param context The aggregation context
179184
* @param parent The parent aggregator (may be {@code null} for top level aggregators)
185+
* @param metaData The metaData associated with this aggregator
180186
*/
181-
protected Aggregator(String name, BucketAggregationMode bucketAggregationMode, AggregatorFactories factories, long estimatedBucketsCount, AggregationContext context, Aggregator parent) {
187+
protected Aggregator(String name, BucketAggregationMode bucketAggregationMode, AggregatorFactories factories, long estimatedBucketsCount, AggregationContext context, Aggregator parent, Map<String, Object> metaData) {
182188
this.name = name;
189+
this.metaData = metaData;
183190
this.parent = parent;
184191
this.estimatedBucketCount = estimatedBucketsCount;
185192
this.context = context;
@@ -217,6 +224,7 @@ public void gatherAnalysis(BucketAnalysisCollector results, long bucketOrdinal)
217224
}
218225
};
219226
}
227+
220228
protected void preCollection() {
221229
Iterable<Aggregator> collectables = Iterables.filter(Arrays.asList(subAggregators), COLLECTABLE_AGGREGATOR);
222230
List<BucketCollector> nextPassCollectors = new ArrayList<>();
@@ -362,9 +370,6 @@ public void gatherAnalysis(BucketAnalysisCollector results, long bucketOrdinal)
362370
results.add(buildAggregation(bucketOrdinal));
363371
}
364372

365-
366-
367-
368373
public abstract InternalAggregation buildEmptyAggregation();
369374

370375
protected final InternalAggregations buildEmptySubAggregations() {

src/main/java/org/elasticsearch/search/aggregations/AggregatorFactories.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ public Aggregator[] createSubAggregators(Aggregator parent, final long estimated
7575
continue;
7676
}
7777
// the aggregator doesn't support multiple ordinals, let's wrap it so that it does.
78-
aggregators[i] = new Aggregator(first.name(), BucketAggregationMode.MULTI_BUCKETS, AggregatorFactories.EMPTY, 1, first.context(), first.parent()) {
78+
aggregators[i] = new Aggregator(first.name(), BucketAggregationMode.MULTI_BUCKETS, AggregatorFactories.EMPTY, 1, first.context(), first.parent(), first.getMetaData()) {
7979

8080
ObjectArray<Aggregator> aggregators;
8181

src/main/java/org/elasticsearch/search/aggregations/AggregatorFactory.java

+12-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020

2121
import org.elasticsearch.search.aggregations.support.AggregationContext;
2222

23+
import java.util.Map;
24+
2325
/**
2426
* A factory that knows how to create an {@link Aggregator} of a specific type.
2527
*/
@@ -29,6 +31,7 @@ public abstract class AggregatorFactory {
2931
protected String type;
3032
protected AggregatorFactory parent;
3133
protected AggregatorFactories factories = AggregatorFactories.EMPTY;
34+
protected Map<String, Object> metaData;
3235

3336
/**
3437
* Constructs a new aggregator factory.
@@ -79,9 +82,17 @@ public AggregatorFactory parent() {
7982
*
8083
* @return The created aggregator
8184
*/
82-
public abstract Aggregator create(AggregationContext context, Aggregator parent, long expectedBucketsCount);
85+
protected abstract Aggregator createInternal(AggregationContext context, Aggregator parent, long expectedBucketsCount, Map<String, Object> metaData);
86+
87+
public Aggregator create(AggregationContext context, Aggregator parent, long expectedBucketsCount) {
88+
Aggregator aggregator = createInternal(context, parent, expectedBucketsCount, this.metaData);
89+
return aggregator;
90+
}
8391

8492
public void doValidate() {
8593
}
8694

95+
public void setMetaData(Map<String, Object> metaData) {
96+
this.metaData = metaData;
97+
}
8798
}

src/main/java/org/elasticsearch/search/aggregations/AggregatorParsers.java

+10
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import org.elasticsearch.search.internal.SearchContext;
2727

2828
import java.io.IOException;
29+
import java.util.Map;
2930
import java.util.Set;
3031
import java.util.regex.Matcher;
3132
import java.util.regex.Pattern;
@@ -100,6 +101,8 @@ private AggregatorFactories parseAggregators(XContentParser parser, SearchContex
100101
AggregatorFactory factory = null;
101102
AggregatorFactories subFactories = null;
102103

104+
Map<String, Object> metaData = null;
105+
103106
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
104107
if (token != XContentParser.Token.FIELD_NAME) {
105108
throw new SearchParseException(context, "Expected [" + XContentParser.Token.FIELD_NAME + "] under a [" + XContentParser.Token.START_OBJECT + "], but got a [" + token + "] in [" + aggregationName + "]");
@@ -112,6 +115,9 @@ private AggregatorFactories parseAggregators(XContentParser parser, SearchContex
112115
}
113116

114117
switch (fieldName) {
118+
case "meta":
119+
metaData = parser.map();
120+
break;
115121
case "aggregations":
116122
case "aggs":
117123
if (subFactories != null) {
@@ -135,6 +141,10 @@ private AggregatorFactories parseAggregators(XContentParser parser, SearchContex
135141
throw new SearchParseException(context, "Missing definition for aggregation [" + aggregationName + "]");
136142
}
137143

144+
if (metaData != null) {
145+
factory.setMetaData(metaData);
146+
}
147+
138148
if (subFactories != null) {
139149
factory.subFactories(subFactories);
140150
}

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

+37-2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
*/
1919
package org.elasticsearch.search.aggregations;
2020

21+
import org.elasticsearch.Version;
2122
import org.elasticsearch.common.bytes.BytesArray;
2223
import org.elasticsearch.common.bytes.BytesReference;
2324
import org.elasticsearch.common.io.stream.StreamInput;
@@ -31,12 +32,14 @@
3132

3233
import java.io.IOException;
3334
import java.util.List;
35+
import java.util.Map;
3436

3537
/**
3638
* An internal implementation of {@link Aggregation}. Serves as a base class for all aggregation implementations.
3739
*/
3840
public abstract class InternalAggregation implements Aggregation, ToXContent, Streamable {
3941

42+
4043
/**
4144
* The aggregation type that holds all the string types that are associated with an aggregation:
4245
* <ul>
@@ -111,6 +114,8 @@ public ScriptService scriptService() {
111114

112115
protected String name;
113116

117+
protected Map<String, Object> metaData;
118+
114119
/** Constructs an un initialized addAggregation (used for serialization) **/
115120
protected InternalAggregation() {}
116121

@@ -119,8 +124,9 @@ protected InternalAggregation() {}
119124
*
120125
* @param name The name of the get.
121126
*/
122-
protected InternalAggregation(String name) {
127+
protected InternalAggregation(String name, Map<String, Object> metaData) {
123128
this.name = name;
129+
this.metaData = metaData;
124130
}
125131

126132
@Override
@@ -158,21 +164,50 @@ protected static void writeSize(int size, StreamOutput out) throws IOException {
158164
}
159165
out.writeVInt(size);
160166
}
161-
167+
168+
public Map<String, Object> getMetaData() {
169+
return metaData;
170+
}
171+
162172
@Override
163173
public final XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
164174
builder.startObject(name);
175+
if (this.metaData != null) {
176+
builder.field(CommonFields.META);
177+
builder.map(this.metaData);
178+
}
165179
doXContentBody(builder, params);
166180
builder.endObject();
167181
return builder;
168182
}
169183

170184
public abstract XContentBuilder doXContentBody(XContentBuilder builder, Params params) throws IOException;
171185

186+
public final void writeTo(StreamOutput out) throws IOException {
187+
out.writeString(name);
188+
if (out.getVersion().onOrAfter(Version.V_1_5_0)) {
189+
out.writeGenericValue(metaData);
190+
}
191+
doWriteTo(out);
192+
}
193+
194+
protected abstract void doWriteTo(StreamOutput out) throws IOException;
195+
196+
public final void readFrom(StreamInput in) throws IOException {
197+
name = in.readString();
198+
if (in.getVersion().onOrAfter(Version.V_1_5_0)) {
199+
metaData = in.readMap();
200+
}
201+
doReadFrom(in);
202+
}
203+
204+
protected abstract void doReadFrom(StreamInput in) throws IOException;
205+
172206
/**
173207
* Common xcontent fields that are shared among addAggregation
174208
*/
175209
public static final class CommonFields {
210+
public static final XContentBuilderString META = new XContentBuilderString("meta");
176211
public static final XContentBuilderString BUCKETS = new XContentBuilderString("buckets");
177212
public static final XContentBuilderString VALUE = new XContentBuilderString("value");
178213
public static final XContentBuilderString VALUES = new XContentBuilderString("values");

src/main/java/org/elasticsearch/search/aggregations/NonCollectingAggregator.java

+3-2
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,16 @@
2323
import org.elasticsearch.search.aggregations.support.AggregationContext;
2424

2525
import java.io.IOException;
26+
import java.util.Map;
2627

2728
/**
2829
* An aggregator that is not collected, this can typically be used when running an aggregation over a field that doesn't have
2930
* a mapping.
3031
*/
3132
public abstract class NonCollectingAggregator extends Aggregator {
3233

33-
protected NonCollectingAggregator(String name, AggregationContext context, Aggregator parent) {
34-
super(name, BucketAggregationMode.MULTI_BUCKETS, AggregatorFactories.EMPTY, 0, context, parent);
34+
protected NonCollectingAggregator(String name, AggregationContext context, Aggregator parent, Map<String, Object> metaData) {
35+
super(name, BucketAggregationMode.MULTI_BUCKETS, AggregatorFactories.EMPTY, 0, context, parent, metaData);
3536
}
3637

3738
private void fail() {

src/main/java/org/elasticsearch/search/aggregations/bucket/BucketsAggregator.java

+3-2
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import java.io.IOException;
2727
import java.util.ArrayList;
2828
import java.util.Arrays;
29+
import java.util.Map;
2930

3031
/**
3132
*
@@ -35,8 +36,8 @@ public abstract class BucketsAggregator extends Aggregator {
3536
private IntArray docCounts;
3637

3738
public BucketsAggregator(String name, BucketAggregationMode bucketAggregationMode, AggregatorFactories factories,
38-
long estimatedBucketsCount, AggregationContext context, Aggregator parent) {
39-
super(name, bucketAggregationMode, factories, estimatedBucketsCount, context, parent);
39+
long estimatedBucketsCount, AggregationContext context, Aggregator parent, Map<String, Object> metaData) {
40+
super(name, bucketAggregationMode, factories, estimatedBucketsCount, context, parent, metaData);
4041
docCounts = bigArrays.newIntArray(estimatedBucketsCount, true);
4142
}
4243

0 commit comments

Comments
 (0)