Skip to content

Commit b69830f

Browse files
authored
Support global ords in top_metrics (#64967) (#65101)
Adds support for fetching `keyword` and `ip` fields in `top_metrics`. This only works if we can get global ordinals for those fields so we can't support the entire `keyword` family, but this gets us *somewhere*. Closes #64774
1 parent f30d88b commit b69830f

File tree

10 files changed

+530
-160
lines changed

10 files changed

+530
-160
lines changed

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

+3-3
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ public int getBucketSize() {
171171
*/
172172
@FunctionalInterface
173173
public interface ResultBuilder<T> {
174-
T build(long index, SortValue sortValue);
174+
T build(long index, SortValue sortValue) throws IOException;
175175
}
176176

177177
/**
@@ -180,7 +180,7 @@ public interface ResultBuilder<T> {
180180
* @param builder builds results. See {@link ExtraData} for how to store
181181
* data along side the sort for this to extract.
182182
*/
183-
public final <T extends Comparable<T>> List<T> getValues(long bucket, ResultBuilder<T> builder) {
183+
public final <T extends Comparable<T>> List<T> getValues(long bucket, ResultBuilder<T> builder) throws IOException {
184184
long rootIndex = bucket * bucketSize;
185185
if (rootIndex >= values().size()) {
186186
// We've never seen this bucket.
@@ -201,7 +201,7 @@ public final <T extends Comparable<T>> List<T> getValues(long bucket, ResultBuil
201201
* Get the values for a bucket if it has been collected. If it hasn't
202202
* then returns an empty array.
203203
*/
204-
public final List<SortValue> getValues(long bucket) {
204+
public final List<SortValue> getValues(long bucket) throws IOException {
205205
return getValues(bucket, (i, sv) -> sv);
206206
}
207207

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

+89-2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919

2020
package org.elasticsearch.search.sort;
2121

22+
import org.apache.lucene.util.BytesRef;
23+
import org.elasticsearch.Version;
2224
import org.elasticsearch.common.io.stream.NamedWriteable;
2325
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
2426
import org.elasticsearch.common.io.stream.StreamInput;
@@ -48,13 +50,22 @@ public static SortValue from(long l) {
4850
return new LongSortValue(l);
4951
}
5052

53+
/**
54+
* Get a {@linkplain SortValue} for bytes. Callers should be sure that they
55+
* have a {@link BytesRef#deepCopyOf} of any mutable references.
56+
*/
57+
public static SortValue from(BytesRef bytes) {
58+
return new BytesSortValue(bytes);
59+
}
60+
5161
/**
5262
* Get the list of {@linkplain NamedWriteable}s that this class needs.
5363
*/
5464
public static List<NamedWriteableRegistry.Entry> namedWriteables() {
5565
return Arrays.asList(
5666
new NamedWriteableRegistry.Entry(SortValue.class, DoubleSortValue.NAME, DoubleSortValue::new),
57-
new NamedWriteableRegistry.Entry(SortValue.class, LongSortValue.NAME, LongSortValue::new));
67+
new NamedWriteableRegistry.Entry(SortValue.class, LongSortValue.NAME, LongSortValue::new),
68+
new NamedWriteableRegistry.Entry(SortValue.class, BytesSortValue.NAME, BytesSortValue::new));
5869
}
5970

6071
private SortValue() {
@@ -200,7 +211,7 @@ private static class LongSortValue extends SortValue {
200211
this.key = key;
201212
}
202213

203-
LongSortValue(StreamInput in) throws IOException {
214+
private LongSortValue(StreamInput in) throws IOException {
204215
key = in.readLong();
205216
}
206217

@@ -259,4 +270,80 @@ public Number numberValue() {
259270
return key;
260271
}
261272
}
273+
274+
private static class BytesSortValue extends SortValue {
275+
public static final String NAME = "bytes";
276+
277+
private final BytesRef key;
278+
279+
BytesSortValue(BytesRef key) {
280+
this.key = key;
281+
}
282+
283+
private BytesSortValue(StreamInput in) throws IOException {
284+
key = in.readBytesRef();
285+
}
286+
287+
@Override
288+
public void writeTo(StreamOutput out) throws IOException {
289+
if (out.getVersion().before(Version.V_7_11_0)) {
290+
throw new IllegalArgumentException(
291+
"versions of Elasticsearch before 7.11.0 can't handle non-numeric sort values and attempted to send to ["
292+
+ out.getVersion()
293+
+ "]"
294+
);
295+
}
296+
out.writeBytesRef(key);
297+
}
298+
299+
@Override
300+
public String getWriteableName() {
301+
return NAME;
302+
}
303+
304+
@Override
305+
public Object getKey() {
306+
return key;
307+
}
308+
309+
@Override
310+
public String format(DocValueFormat format) {
311+
return format.format(key).toString();
312+
}
313+
314+
@Override
315+
protected XContentBuilder rawToXContent(XContentBuilder builder) throws IOException {
316+
return builder.value(key.utf8ToString());
317+
}
318+
319+
@Override
320+
protected int compareToSameType(SortValue obj) {
321+
BytesSortValue other = (BytesSortValue) obj;
322+
return key.compareTo(other.key);
323+
}
324+
325+
@Override
326+
public boolean equals(Object obj) {
327+
if (obj == null || false == getClass().equals(obj.getClass())) {
328+
return false;
329+
}
330+
BytesSortValue other = (BytesSortValue) obj;
331+
return key.equals(other.key);
332+
}
333+
334+
@Override
335+
public int hashCode() {
336+
return key.hashCode();
337+
}
338+
339+
@Override
340+
public String toString() {
341+
return key.toString();
342+
}
343+
344+
@Override
345+
public Number numberValue() {
346+
throw new UnsupportedOperationException();
347+
}
348+
}
262349
}

server/src/test/java/org/elasticsearch/search/sort/BucketedSortTestCase.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ private T build(SortOrder order, int bucketSize, double[] values) {
7373
return build(order, format, bucketSize, BucketedSort.NOOP_EXTRA_DATA, values);
7474
}
7575

76-
public final void testNeverCalled() {
76+
public final void testNeverCalled() throws IOException {
7777
SortOrder order = randomFrom(SortOrder.values());
7878
DocValueFormat format = randomFrom(DocValueFormat.RAW, DocValueFormat.BINARY, DocValueFormat.BOOLEAN);
7979
try (T sort = build(order, format, 1, BucketedSort.NOOP_EXTRA_DATA, new double[] {})) {

server/src/test/java/org/elasticsearch/search/sort/SortValueTests.java

+42-1
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,19 @@
1919

2020
package org.elasticsearch.search.sort;
2121

22+
import org.apache.lucene.document.InetAddressPoint;
23+
import org.apache.lucene.util.BytesRef;
24+
import org.elasticsearch.Version;
2225
import org.elasticsearch.common.Strings;
2326
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
27+
import org.elasticsearch.common.network.InetAddresses;
2428
import org.elasticsearch.common.time.DateFormatter;
2529
import org.elasticsearch.common.xcontent.ToXContentFragment;
2630
import org.elasticsearch.common.xcontent.XContentBuilder;
2731
import org.elasticsearch.index.mapper.DateFieldMapper;
2832
import org.elasticsearch.search.DocValueFormat;
2933
import org.elasticsearch.test.AbstractNamedWriteableTestCase;
34+
import org.elasticsearch.test.VersionUtils;
3035

3136
import java.io.IOException;
3237
import java.time.ZoneId;
@@ -51,7 +56,12 @@ protected NamedWriteableRegistry getNamedWriteableRegistry() {
5156

5257
@Override
5358
protected SortValue createTestInstance() {
54-
return randomBoolean() ? SortValue.from(randomDouble()) : SortValue.from(randomLong());
59+
switch (between(0, 2)) {
60+
case 0: return SortValue.from(randomDouble());
61+
case 1: return SortValue.from(randomLong());
62+
case 2: return SortValue.from(new BytesRef(randomAlphaOfLength(5)));
63+
default: throw new AssertionError();
64+
}
5565
}
5666

5767
@Override
@@ -81,11 +91,23 @@ public void testToXContentLong() {
8191
assertThat(toXContent(SortValue.from(1), STRICT_DATE_TIME), equalTo("{\"test\":\"1970-01-01T00:00:00.001Z\"}"));
8292
}
8393

94+
public void testToXContentBytes() {
95+
assertThat(toXContent(SortValue.from(new BytesRef("cat")), DocValueFormat.RAW), equalTo("{\"test\":\"cat\"}"));
96+
assertThat(
97+
toXContent(SortValue.from(new BytesRef(InetAddressPoint.encode(InetAddresses.forString("127.0.0.1")))), DocValueFormat.IP),
98+
equalTo("{\"test\":\"127.0.0.1\"}")
99+
);
100+
}
101+
84102
public void testCompareDifferentTypes() {
85103
assertThat(SortValue.from(1.0), lessThan(SortValue.from(1)));
86104
assertThat(SortValue.from(Double.MAX_VALUE), lessThan(SortValue.from(Long.MIN_VALUE)));
87105
assertThat(SortValue.from(1), greaterThan(SortValue.from(1.0)));
88106
assertThat(SortValue.from(Long.MIN_VALUE), greaterThan(SortValue.from(Double.MAX_VALUE)));
107+
assertThat(SortValue.from(new BytesRef("cat")), lessThan(SortValue.from(1)));
108+
assertThat(SortValue.from(1), greaterThan(SortValue.from(new BytesRef("cat"))));
109+
assertThat(SortValue.from(new BytesRef("cat")), lessThan(SortValue.from(1.0)));
110+
assertThat(SortValue.from(1.0), greaterThan(SortValue.from(new BytesRef("cat"))));
89111
}
90112

91113
public void testCompareDoubles() {
@@ -102,6 +124,25 @@ public void testCompareLongs() {
102124
assertThat(SortValue.from(r), greaterThan(SortValue.from(r - 1)));
103125
}
104126

127+
public void testBytes() {
128+
String r = randomAlphaOfLength(5);
129+
assertThat(SortValue.from(new BytesRef(r)), equalTo(SortValue.from(new BytesRef(r))));
130+
assertThat(SortValue.from(new BytesRef(r)), lessThan(SortValue.from(new BytesRef(r + "with_suffix"))));
131+
assertThat(SortValue.from(new BytesRef(r)), greaterThan(SortValue.from(new BytesRef(new byte[] {}))));
132+
}
133+
134+
public void testSerializeBytesToOldVersion() {
135+
SortValue value = SortValue.from(new BytesRef("can't send me!"));
136+
Version version = VersionUtils.randomVersionBetween(random(), Version.V_7_0_0, Version.V_7_10_1);
137+
Exception e = expectThrows(IllegalArgumentException.class, () -> copyInstance(value, version));
138+
assertThat(
139+
e.getMessage(),
140+
equalTo(
141+
"versions of Elasticsearch before 7.11.0 can't handle non-numeric sort values and attempted to send to [" + version + "]"
142+
)
143+
);
144+
}
145+
105146
public String toXContent(SortValue sortValue, DocValueFormat format) {
106147
return Strings.toString(new ToXContentFragment() {
107148
@Override

x-pack/plugin/analytics/src/main/java/org/elasticsearch/xpack/analytics/AnalyticsPlugin.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,8 @@ public List<AggregationSpec> getAggregations() {
114114
TopMetricsAggregationBuilder.NAME,
115115
TopMetricsAggregationBuilder::new,
116116
usage.track(AnalyticsStatsAction.Item.TOP_METRICS, checkLicense(TopMetricsAggregationBuilder.PARSER)))
117-
.addResultReader(InternalTopMetrics::new),
117+
.addResultReader(InternalTopMetrics::new)
118+
.setAggregatorRegistrar(TopMetricsAggregationBuilder::registerAggregators),
118119
new AggregationSpec(
119120
TTestAggregationBuilder.NAME,
120121
TTestAggregationBuilder::new,

x-pack/plugin/analytics/src/main/java/org/elasticsearch/xpack/analytics/topmetrics/TopMetricsAggregationBuilder.java

+30
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,10 @@
1818
import org.elasticsearch.search.aggregations.AggregatorFactories.Builder;
1919
import org.elasticsearch.search.aggregations.AggregatorFactory;
2020
import org.elasticsearch.search.aggregations.support.AggregationContext;
21+
import org.elasticsearch.search.aggregations.support.CoreValuesSourceType;
2122
import org.elasticsearch.search.aggregations.support.MultiValuesSourceFieldConfig;
23+
import org.elasticsearch.search.aggregations.support.ValuesSourceRegistry;
24+
import org.elasticsearch.search.aggregations.support.ValuesSourceRegistry.RegistryKey;
2225
import org.elasticsearch.search.sort.SortBuilder;
2326

2427
import java.io.IOException;
@@ -34,6 +37,33 @@ public class TopMetricsAggregationBuilder extends AbstractAggregationBuilder<Top
3437
public static final String NAME = "top_metrics";
3538
public static final ParseField METRIC_FIELD = new ParseField("metrics");
3639

40+
static final RegistryKey<TopMetricsAggregator.MetricValuesSupplier> REGISTRY_KEY = new RegistryKey<>(
41+
TopMetricsAggregationBuilder.NAME,
42+
TopMetricsAggregator.MetricValuesSupplier.class
43+
);
44+
45+
public static void registerAggregators(ValuesSourceRegistry.Builder registry) {
46+
registry.registerUsage(NAME);
47+
registry.register(
48+
REGISTRY_KEY,
49+
org.elasticsearch.common.collect.List.of(CoreValuesSourceType.NUMERIC),
50+
TopMetricsAggregator::buildNumericMetricValues,
51+
false
52+
);
53+
registry.register(
54+
REGISTRY_KEY,
55+
org.elasticsearch.common.collect.List.of(CoreValuesSourceType.BOOLEAN, CoreValuesSourceType.DATE),
56+
TopMetricsAggregator.LongMetricValues::new,
57+
false
58+
);
59+
registry.register(
60+
REGISTRY_KEY,
61+
org.elasticsearch.common.collect.List.of(CoreValuesSourceType.BYTES, CoreValuesSourceType.IP),
62+
TopMetricsAggregator.GlobalOrdsValues::new,
63+
false
64+
);
65+
}
66+
3767
/**
3868
* Default to returning only a single top metric.
3969
*/

0 commit comments

Comments
 (0)