Skip to content

Commit 9dcd64c

Browse files
authored
Preserve metric types in top_metrics (backport of #53288) (#53440)
This changes the `top_metrics` aggregation to return metrics in their original type. Since it only supports numerics, that means that dates, longs, and doubles will come back as stored, with their appropriate formatter applied.
1 parent 97621e7 commit 9dcd64c

File tree

11 files changed

+825
-132
lines changed

11 files changed

+825
-132
lines changed

client/rest-high-level/src/main/java/org/elasticsearch/client/analytics/ParsedTopMetrics.java

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,10 @@
2424
import org.elasticsearch.common.xcontent.ObjectParser;
2525
import org.elasticsearch.common.xcontent.ToXContent;
2626
import org.elasticsearch.common.xcontent.XContentBuilder;
27-
import org.elasticsearch.common.xcontent.XContentParser;
2827
import org.elasticsearch.common.xcontent.XContentParserUtils;
2928
import org.elasticsearch.search.aggregations.ParsedAggregation;
3029

3130
import java.io.IOException;
32-
import java.util.HashMap;
3331
import java.util.List;
3432
import java.util.Map;
3533

@@ -88,9 +86,9 @@ public static class TopMetrics implements ToXContent {
8886
private static final ParseField METRICS_FIELD = new ParseField("metrics");
8987

9088
private final List<Object> sort;
91-
private final Map<String, Double> metrics;
89+
private final Map<String, Object> metrics;
9290

93-
private TopMetrics(List<Object> sort, Map<String, Double> metrics) {
91+
private TopMetrics(List<Object> sort, Map<String, Object> metrics) {
9492
this.sort = sort;
9593
this.metrics = metrics;
9694
}
@@ -105,7 +103,7 @@ public List<Object> getSort() {
105103
/**
106104
* The top metric values returned by the aggregation.
107105
*/
108-
public Map<String, Double> getMetrics() {
106+
public Map<String, Object> getMetrics() {
109107
return metrics;
110108
}
111109

@@ -114,13 +112,13 @@ public Map<String, Double> getMetrics() {
114112
@SuppressWarnings("unchecked")
115113
List<Object> sort = (List<Object>) args[0];
116114
@SuppressWarnings("unchecked")
117-
Map<String, Double> metrics = (Map<String, Double>) args[1];
115+
Map<String, Object> metrics = (Map<String, Object>) args[1];
118116
return new TopMetrics(sort, metrics);
119117
});
120118
static {
121119
PARSER.declareFieldArray(constructorArg(), (p, c) -> XContentParserUtils.parseFieldsValue(p),
122120
SORT_FIELD, ObjectParser.ValueType.VALUE_ARRAY);
123-
PARSER.declareObject(constructorArg(), (p, c) -> p.map(HashMap::new, XContentParser::doubleValue), METRICS_FIELD);
121+
PARSER.declareObject(constructorArg(), (p, c) -> p.map(), METRICS_FIELD);
124122
}
125123

126124
public XContentBuilder toXContent(XContentBuilder builder, ToXContent.Params params) throws IOException {

client/rest-high-level/src/test/java/org/elasticsearch/client/analytics/AnalyticsAggsIT.java

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import org.elasticsearch.action.support.WriteRequest.RefreshPolicy;
2727
import org.elasticsearch.client.ESRestHighLevelClientTestCase;
2828
import org.elasticsearch.client.RequestOptions;
29+
import org.elasticsearch.client.indices.CreateIndexRequest;
2930
import org.elasticsearch.common.xcontent.XContentType;
3031
import org.elasticsearch.search.sort.FieldSortBuilder;
3132
import org.elasticsearch.search.sort.SortOrder;
@@ -61,8 +62,8 @@ public void testStringStats() throws IOException {
6162
assertThat(stats.getDistribution(), hasEntry(equalTo("t"), closeTo(.09, .005)));
6263
}
6364

64-
public void testTopMetricsSizeOne() throws IOException {
65-
indexTopMetricsData();
65+
public void testTopMetricsDoubleMetric() throws IOException {
66+
indexTopMetricsDoubleTestData();
6667
SearchRequest search = new SearchRequest("test");
6768
search.source().aggregation(new TopMetricsAggregationBuilder(
6869
"test", new FieldSortBuilder("s").order(SortOrder.DESC), 1, "v"));
@@ -74,8 +75,34 @@ public void testTopMetricsSizeOne() throws IOException {
7475
assertThat(metric.getMetrics(), equalTo(singletonMap("v", 3.0)));
7576
}
7677

78+
public void testTopMetricsLongMetric() throws IOException {
79+
indexTopMetricsLongTestData();
80+
SearchRequest search = new SearchRequest("test");
81+
search.source().aggregation(new TopMetricsAggregationBuilder(
82+
"test", new FieldSortBuilder("s").order(SortOrder.DESC), 1, "v"));
83+
SearchResponse response = highLevelClient().search(search, RequestOptions.DEFAULT);
84+
ParsedTopMetrics top = response.getAggregations().get("test");
85+
assertThat(top.getTopMetrics(), hasSize(1));
86+
ParsedTopMetrics.TopMetrics metric = top.getTopMetrics().get(0);
87+
assertThat(metric.getSort(), equalTo(singletonList(2)));
88+
assertThat(metric.getMetrics(), equalTo(singletonMap("v", 3)));
89+
}
90+
91+
public void testTopMetricsDateMetric() throws IOException {
92+
indexTopMetricsDateTestData();
93+
SearchRequest search = new SearchRequest("test");
94+
search.source().aggregation(new TopMetricsAggregationBuilder(
95+
"test", new FieldSortBuilder("s").order(SortOrder.DESC), 1, "v"));
96+
SearchResponse response = highLevelClient().search(search, RequestOptions.DEFAULT);
97+
ParsedTopMetrics top = response.getAggregations().get("test");
98+
assertThat(top.getTopMetrics(), hasSize(1));
99+
ParsedTopMetrics.TopMetrics metric = top.getTopMetrics().get(0);
100+
assertThat(metric.getSort(), equalTo(singletonList(2)));
101+
assertThat(metric.getMetrics(), equalTo(singletonMap("v", "2020-01-02T01:01:00.000Z")));
102+
}
103+
77104
public void testTopMetricsManyMetrics() throws IOException {
78-
indexTopMetricsData();
105+
indexTopMetricsDoubleTestData();
79106
SearchRequest search = new SearchRequest("test");
80107
search.source().aggregation(new TopMetricsAggregationBuilder(
81108
"test", new FieldSortBuilder("s").order(SortOrder.DESC), 1, "v", "m"));
@@ -89,7 +116,7 @@ public void testTopMetricsManyMetrics() throws IOException {
89116
}
90117

91118
public void testTopMetricsSizeTwo() throws IOException {
92-
indexTopMetricsData();
119+
indexTopMetricsDoubleTestData();
93120
SearchRequest search = new SearchRequest("test");
94121
search.source().aggregation(new TopMetricsAggregationBuilder(
95122
"test", new FieldSortBuilder("s").order(SortOrder.DESC), 2, "v"));
@@ -104,10 +131,28 @@ public void testTopMetricsSizeTwo() throws IOException {
104131
assertThat(metric.getMetrics(), equalTo(singletonMap("v", 2.0)));
105132
}
106133

107-
private void indexTopMetricsData() throws IOException {
134+
private void indexTopMetricsDoubleTestData() throws IOException {
108135
BulkRequest bulk = new BulkRequest("test").setRefreshPolicy(RefreshPolicy.IMMEDIATE);
109136
bulk.add(new IndexRequest().source(XContentType.JSON, "s", 1, "v", 2.0, "m", 12.0));
110137
bulk.add(new IndexRequest().source(XContentType.JSON, "s", 2, "v", 3.0, "m", 13.0));
111138
highLevelClient().bulk(bulk, RequestOptions.DEFAULT);
112139
}
140+
141+
private void indexTopMetricsLongTestData() throws IOException {
142+
BulkRequest bulk = new BulkRequest("test").setRefreshPolicy(RefreshPolicy.IMMEDIATE);
143+
bulk.add(new IndexRequest().source(XContentType.JSON, "s", 1, "v", 2));
144+
bulk.add(new IndexRequest().source(XContentType.JSON, "s", 2, "v", 3));
145+
highLevelClient().bulk(bulk, RequestOptions.DEFAULT);
146+
}
147+
148+
private void indexTopMetricsDateTestData() throws IOException {
149+
CreateIndexRequest create = new CreateIndexRequest("test");
150+
create.mapping("{\"properties\": {\"v\": {\"type\": \"date\"}}}", XContentType.JSON);
151+
highLevelClient().indices().create(create, RequestOptions.DEFAULT);
152+
BulkRequest bulk = new BulkRequest("test").setRefreshPolicy(RefreshPolicy.IMMEDIATE);
153+
bulk.add(new IndexRequest().source(XContentType.JSON, "s", 1, "v", "2020-01-01T01:01:00Z"));
154+
bulk.add(new IndexRequest().source(XContentType.JSON, "s", 2, "v", "2020-01-02T01:01:00Z"));
155+
highLevelClient().bulk(bulk, RequestOptions.DEFAULT);
156+
}
157+
113158
}

docs/reference/aggregations/metrics/top-metrics-aggregation.asciidoc

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -70,31 +70,36 @@ the same sort values then this aggregation could return either document's fields
7070

7171
==== `metrics`
7272

73-
`metrics` selects the fields to of the "top" document to return. Like most other
74-
aggregations, `top_metrics` casts these values cast to `double` precision
75-
floating point numbers. So they have to be numeric. Dates *work*, but they
76-
come back as a `double` precision floating point containing milliseconds since
77-
epoch. `keyword` fields aren't allowed.
73+
`metrics` selects the fields to of the "top" document to return.
7874

7975
You can return multiple metrics by providing a list:
8076

8177
[source,console,id=search-aggregations-metrics-top-metrics-list-of-metrics]
8278
----
79+
PUT /test
80+
{
81+
"mappings": {
82+
"properties": {
83+
"d": {"type": "date"}
84+
}
85+
}
86+
}
8387
POST /test/_bulk?refresh
8488
{"index": {}}
85-
{"s": 1, "v": 3.1415, "m": 1.9}
89+
{"s": 1, "v": 3.1415, "m": 1, "d": "2020-01-01T00:12:12Z"}
8690
{"index": {}}
87-
{"s": 2, "v": 1.0, "m": 6.7}
91+
{"s": 2, "v": 1.0, "m": 6, "d": "2020-01-02T00:12:12Z"}
8892
{"index": {}}
89-
{"s": 3, "v": 2.71828, "m": -12.2}
93+
{"s": 3, "v": 2.71828, "m": -12, "d": "2019-12-31T00:12:12Z"}
9094
POST /test/_search?filter_path=aggregations
9195
{
9296
"aggs": {
9397
"tm": {
9498
"top_metrics": {
9599
"metrics": [
96100
{"field": "v"},
97-
{"field": "m"}
101+
{"field": "m"},
102+
{"field": "d"}
98103
],
99104
"sort": {"s": "desc"}
100105
}
@@ -114,7 +119,8 @@ Which returns:
114119
"sort": [3],
115120
"metrics": {
116121
"v": 2.718280076980591,
117-
"m": -12.199999809265137
122+
"m": -12,
123+
"d": "2019-12-31T00:12:12.000Z"
118124
}
119125
} ]
120126
}
@@ -123,7 +129,6 @@ Which returns:
123129
----
124130
// TESTRESPONSE
125131

126-
127132
==== `size`
128133

129134
`top_metrics` can return the top few document's worth of metrics using the size parameter:
@@ -246,14 +251,14 @@ Which returns:
246251
"key": "192.168.0.1",
247252
"doc_count": 2,
248253
"tm": {
249-
"top": [ {"sort": ["2020-01-01T02:01:01.000Z"], "metrics": {"v": 2.0 } } ]
254+
"top": [ {"sort": ["2020-01-01T02:01:01.000Z"], "metrics": {"v": 2 } } ]
250255
}
251256
},
252257
{
253258
"key": "192.168.0.2",
254259
"doc_count": 1,
255260
"tm": {
256-
"top": [ {"sort": ["2020-01-01T02:01:01.000Z"], "metrics": {"v": 3.0 } } ]
261+
"top": [ {"sort": ["2020-01-01T02:01:01.000Z"], "metrics": {"v": 3 } } ]
257262
}
258263
}
259264
],
@@ -303,14 +308,14 @@ Which returns:
303308
"key": "192.168.0.2",
304309
"doc_count": 1,
305310
"tm": {
306-
"top": [ {"sort": ["2020-01-01T02:01:01.000Z"], "metrics": {"v": 3.0 } } ]
311+
"top": [ {"sort": ["2020-01-01T02:01:01.000Z"], "metrics": {"v": 3 } } ]
307312
}
308313
},
309314
{
310315
"key": "192.168.0.1",
311316
"doc_count": 2,
312317
"tm": {
313-
"top": [ {"sort": ["2020-01-01T02:01:01.000Z"], "metrics": {"v": 2.0 } } ]
318+
"top": [ {"sort": ["2020-01-01T02:01:01.000Z"], "metrics": {"v": 2 } } ]
314319
}
315320
}
316321
],

server/src/main/java/org/elasticsearch/search/sort/SortValue.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,11 @@ public final XContentBuilder toXContent(XContentBuilder builder, DocValueFormat
117117
@Override
118118
public abstract String toString();
119119

120+
/**
121+
* Return this {@linkplain SortValue} as a boxed {@linkplain Number}.
122+
*/
123+
public abstract Number numberValue();
124+
120125
private static class DoubleSortValue extends SortValue {
121126
public static final String NAME = "double";
122127

@@ -179,6 +184,11 @@ public int hashCode() {
179184
public String toString() {
180185
return Double.toString(key);
181186
}
187+
188+
@Override
189+
public Number numberValue() {
190+
return key;
191+
}
182192
}
183193

184194
private static class LongSortValue extends SortValue {
@@ -243,5 +253,10 @@ public int hashCode() {
243253
public String toString() {
244254
return Long.toString(key);
245255
}
256+
257+
@Override
258+
public Number numberValue() {
259+
return key;
260+
}
246261
}
247262
}

0 commit comments

Comments
 (0)