Skip to content

Commit c05ccda

Browse files
author
Patrick Zhai
committed
Add TermsAggregation implementation for grpc
Signed-off-by: Patrick Zhai <pzhai@uber.com>
1 parent 7993871 commit c05ccda

26 files changed

Lines changed: 1343 additions & 22 deletions

.idea/vcs.xml

Lines changed: 15 additions & 15 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
1313
- [Workload Management] Enhance Scroll API support for autotagging ([#20151](https://github.com/opensearch-project/OpenSearch/pull/20151))
1414
- Add indices to search request slowlog ([#20588](https://github.com/opensearch-project/OpenSearch/pull/20588))
1515
- Add gRPC support for Min and Max metric aggregations ([#20676](https://github.com/opensearch-project/OpenSearch/pull/20676))
16+
- Add gRPC support for Term aggregations ([#20970](https://github.com/opensearch-project/OpenSearch/pull/20970))
1617

1718
### Changed
1819
- Move Randomness from server to libs/common ([#20570](https://github.com/opensearch-project/OpenSearch/pull/20570))

build.gradle

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,11 @@ allprojects {
8787
group = 'org.opensearch'
8888
version = VersionProperties.getOpenSearch()
8989
description = "OpenSearch subproject ${project.path}"
90+
91+
// Add local Maven repository for protobuf development
92+
repositories {
93+
mavenLocal()
94+
}
9095
}
9196

9297
configure(allprojects - project(':distribution:archives:integ-test-zip')) {

modules/transport-grpc/src/main/java/org/opensearch/transport/grpc/proto/request/search/aggregation/AggregationBuilderProtoConverterRegistryImpl.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import org.opensearch.common.inject.Singleton;
1414
import org.opensearch.protobufs.AggregationContainer;
1515
import org.opensearch.search.aggregations.AggregationBuilder;
16+
import org.opensearch.transport.grpc.proto.request.search.aggregation.bucket.terms.TermsAggregationBuilderConverter;
1617
import org.opensearch.transport.grpc.proto.request.search.aggregation.metrics.MaxAggregationBuilderProtoConverter;
1718
import org.opensearch.transport.grpc.proto.request.search.aggregation.metrics.MinAggregationBuilderProtoConverter;
1819
import org.opensearch.transport.grpc.spi.AggregationBuilderProtoConverter;
@@ -44,6 +45,7 @@ protected void registerBuiltInConverters() {
4445
// Register metric aggregation converters
4546
delegate.registerConverter(new MinAggregationBuilderProtoConverter());
4647
delegate.registerConverter(new MaxAggregationBuilderProtoConverter());
48+
delegate.registerConverter(new TermsAggregationBuilderConverter());
4749

4850
// Future: Register bucket aggregation converters here
4951
// Example: delegate.registerConverter(new TermsAggregationBuilderProtoConverter());
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
*/
4+
5+
/**
6+
* Protocol Buffer utilities for bucket aggregation requests.
7+
* Contains converters from Protocol Buffer aggregation requests to OpenSearch bucket aggregation builders.
8+
* <p>
9+
* Bucket aggregations group documents into buckets based on criteria like field values, ranges, or filters.
10+
* Examples include terms, histogram, filters, and nested aggregations.
11+
*
12+
* @see org.opensearch.search.aggregations.bucket
13+
*/
14+
15+
package org.opensearch.transport.grpc.proto.request.search.aggregation.bucket;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
package org.opensearch.transport.grpc.proto.request.search.aggregation.bucket.terms;
2+
3+
import org.opensearch.core.xcontent.XContentParser;
4+
import org.opensearch.protobufs.AggregationContainer;
5+
import org.opensearch.protobufs.SortOrder;
6+
import org.opensearch.protobufs.SortOrderSingleMap;
7+
import org.opensearch.protobufs.TermsAggregation;
8+
import org.opensearch.protobufs.TermsAggregationCollectMode;
9+
import org.opensearch.protobufs.TermsAggregationExecutionHint;
10+
import org.opensearch.protobufs.TermsAggregationFields;
11+
import org.opensearch.protobufs.TermsInclude;
12+
import org.opensearch.search.aggregations.AggregationBuilder;
13+
import org.opensearch.search.aggregations.Aggregator.SubAggCollectionMode;
14+
import org.opensearch.search.aggregations.BucketOrder;
15+
import org.opensearch.search.aggregations.InternalOrder;
16+
import org.opensearch.search.aggregations.bucket.terms.IncludeExclude;
17+
import org.opensearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
18+
import org.opensearch.transport.grpc.proto.request.common.ScriptProtoUtils;
19+
import org.opensearch.transport.grpc.proto.request.search.aggregation.support.ValuesSourceAggregationProtoUtils;
20+
import org.opensearch.transport.grpc.proto.request.search.aggregation.support.ValuesSourceProtoFields;
21+
import org.opensearch.transport.grpc.proto.response.common.FieldValueProtoUtils;
22+
import org.opensearch.transport.grpc.spi.AggregationBuilderProtoConverter;
23+
import org.opensearch.transport.grpc.spi.AggregationBuilderProtoConverterRegistry;
24+
25+
import java.util.ArrayList;
26+
import java.util.List;
27+
import java.util.Map;
28+
29+
/**
30+
* Converter from proto {@link TermsAggregation} to {@link TermsAggregationBuilder}
31+
*/
32+
public class TermsAggregationBuilderConverter implements AggregationBuilderProtoConverter {
33+
34+
private AggregationBuilderProtoConverterRegistry registry;
35+
36+
@Override
37+
public AggregationContainer.AggregationContainerCase getHandledAggregationCase() {
38+
return AggregationContainer.AggregationContainerCase.TERMS_AGGREGATION;
39+
}
40+
41+
/**
42+
* This method parallels the REST parsing logic in {@link TermsAggregationBuilder#PARSER}
43+
*
44+
* {@inheritDoc}
45+
*/
46+
@Override
47+
public AggregationBuilder fromProto(String name, AggregationContainer container) {
48+
if (!container.hasTermsAggregation()) {
49+
throw new IllegalStateException("AggregationContainer doesn't have terms aggregation");
50+
}
51+
TermsAggregation proto = container.getTermsAggregation();
52+
TermsAggregationBuilder aggBuilder = new TermsAggregationBuilder(name);
53+
54+
if (!proto.hasTerms()) {
55+
throw new IllegalArgumentException("Terms fields are not provided, cannot parse");
56+
}
57+
58+
TermsAggregationFields terms = proto.getTerms();
59+
60+
// common fields for ValuesSourceAggregation
61+
ValuesSourceProtoFields valuesSourceFields = ValuesSourceProtoFields.builder()
62+
.field(terms.hasField() ? terms.getField() : null)
63+
.missing(terms.hasMissing() ? FieldValueProtoUtils.fromProto(terms.getMissing()) : null)
64+
.valueType(terms.hasValueType() ? terms.getValueType() : null)
65+
.format(terms.hasFormat() ? terms.getFormat() : null)
66+
.script(terms.hasScript() ? ScriptProtoUtils.parseFromProtoRequest(terms.getScript()) : null)
67+
.build();
68+
ValuesSourceAggregationProtoUtils.declareFields(aggBuilder, valuesSourceFields, true, true, false);
69+
70+
if (terms.hasShowTermDocCountError()) {
71+
aggBuilder.showTermDocCountError(terms.getShowTermDocCountError());
72+
}
73+
74+
if (terms.hasShardSize()) {
75+
aggBuilder.shardSize(terms.getShardSize());
76+
}
77+
78+
if (terms.hasMinDocCount()) {
79+
aggBuilder.minDocCount(terms.getMinDocCount());
80+
}
81+
82+
if (terms.hasShardMinDocCount()) {
83+
aggBuilder.shardMinDocCount(terms.getShardMinDocCount());
84+
}
85+
86+
if (terms.hasSize()) {
87+
aggBuilder.size(terms.getSize());
88+
}
89+
90+
if (terms.hasExecutionHint()) {
91+
aggBuilder.executionHint(convertExecutionHint(terms.getExecutionHint()));
92+
}
93+
94+
if (terms.hasCollectMode()) {
95+
aggBuilder.collectMode(convertCollectMode(terms.getCollectMode()));
96+
}
97+
98+
if (terms.getOrderCount() > 0) {
99+
aggBuilder.order(convertOrder(terms.getOrderList()));
100+
}
101+
102+
// include/exclude handling mirrors IncludeExclude.merge logic in the REST parser
103+
IncludeExclude includeExclude = null;
104+
if (terms.hasInclude()) {
105+
includeExclude = convertInclude(terms.getInclude());
106+
}
107+
if (terms.getExcludeCount() > 0) {
108+
// See IncludeExclude::parseExclude
109+
String[] excludeValues = terms.getExcludeList().toArray(new String[0]);
110+
IncludeExclude excludeIE = new IncludeExclude(null, excludeValues);
111+
includeExclude = IncludeExclude.merge(includeExclude, excludeIE);
112+
}
113+
if (includeExclude != null) {
114+
aggBuilder.includeExclude(includeExclude);
115+
}
116+
117+
// sub-aggregations
118+
if (proto.getAggregationsCount() > 0) {
119+
if (registry == null) {
120+
throw new IllegalStateException("Registry not set properly, cannot parse sub aggregations");
121+
}
122+
for (Map.Entry<String, AggregationContainer> entry : proto.getAggregationsMap().entrySet()) {
123+
aggBuilder.subAggregation(registry.fromProto(entry.getKey(), entry.getValue()));
124+
}
125+
}
126+
127+
return aggBuilder;
128+
}
129+
130+
private static String convertExecutionHint(TermsAggregationExecutionHint hint) {
131+
// TODO: Seems the schema is a bit off sync'd between proto and java impl? Enum in proto but string in java
132+
return switch (hint) {
133+
case TERMS_AGGREGATION_EXECUTION_HINT_MAP -> "map";
134+
case TERMS_AGGREGATION_EXECUTION_HINT_GLOBAL_ORDINALS -> "global_ordinals";
135+
case TERMS_AGGREGATION_EXECUTION_HINT_GLOBAL_ORDINALS_HASH -> "global_ordinals_hash";
136+
case TERMS_AGGREGATION_EXECUTION_HINT_GLOBAL_ORDINALS_LOW_CARDINALITY -> "global_ordinals_low_cardinality";
137+
default -> throw new IllegalArgumentException("Unsupported execution hint: " + hint);
138+
};
139+
}
140+
141+
private static SubAggCollectionMode convertCollectMode(TermsAggregationCollectMode mode) {
142+
return switch (mode) {
143+
case TERMS_AGGREGATION_COLLECT_MODE_BREADTH_FIRST -> SubAggCollectionMode.BREADTH_FIRST;
144+
case TERMS_AGGREGATION_COLLECT_MODE_DEPTH_FIRST -> SubAggCollectionMode.DEPTH_FIRST;
145+
default -> throw new IllegalArgumentException("Unsupported collect mode: " + mode);
146+
};
147+
}
148+
149+
private static List<BucketOrder> convertOrder(List<SortOrderSingleMap> orderList) {
150+
List<BucketOrder> orders = new ArrayList<>(orderList.size());
151+
for (SortOrderSingleMap entry : orderList) {
152+
boolean asc = entry.getSortOrder() == SortOrder.SORT_ORDER_ASC;
153+
String key = entry.getField();
154+
orders.add(InternalOrder.Parser.resolveOrderParam(key, asc));
155+
}
156+
return orders;
157+
}
158+
159+
/**
160+
* See {@link IncludeExclude#parseInclude(XContentParser)}
161+
*/
162+
private static IncludeExclude convertInclude(TermsInclude include) {
163+
return switch (include.getTermsIncludeCase()) {
164+
case TERMS -> {
165+
String[] includeTerms = include.getTerms().getStringArrayList().toArray(new String[0]);
166+
yield new IncludeExclude(includeTerms, null);
167+
}
168+
case PARTITION ->
169+
new IncludeExclude(include.getPartition().getPartition(), include.getPartition().getNumPartitions());
170+
default -> throw new IllegalArgumentException("Unsupported include type: " + include.getTermsIncludeCase());
171+
};
172+
}
173+
174+
@Override
175+
public void setRegistry(AggregationBuilderProtoConverterRegistry registry) {
176+
this.registry = registry;
177+
}
178+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
*/
4+
5+
/**
6+
* Protocol Buffer utilities for terms bucket aggregation requests.
7+
* Contains converters from Protocol Buffer terms aggregation requests to OpenSearch terms aggregation builders.
8+
*
9+
* @see org.opensearch.search.aggregations.bucket.terms
10+
*/
11+
package org.opensearch.transport.grpc.proto.request.search.aggregation.bucket.terms;

modules/transport-grpc/src/main/java/org/opensearch/transport/grpc/proto/response/search/aggregation/AggregateProtoConverterRegistryImpl.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@
99

1010
import org.opensearch.protobufs.Aggregate;
1111
import org.opensearch.search.aggregations.InternalAggregation;
12+
import org.opensearch.transport.grpc.proto.response.search.aggregation.bucket.terms.DoubleTermsAggregateConverter;
13+
import org.opensearch.transport.grpc.proto.response.search.aggregation.bucket.terms.LongTermsAggregateConverter;
14+
import org.opensearch.transport.grpc.proto.response.search.aggregation.bucket.terms.StringTermsAggregateConverter;
15+
import org.opensearch.transport.grpc.proto.response.search.aggregation.bucket.terms.UnmappedTermsAggregateConverter;
16+
import org.opensearch.transport.grpc.proto.response.search.aggregation.bucket.terms.UnsignedLongTermsAggregateConverter;
1217
import org.opensearch.transport.grpc.proto.response.search.aggregation.metrics.MaxAggregateProtoConverter;
1318
import org.opensearch.transport.grpc.proto.response.search.aggregation.metrics.MinAggregateProtoConverter;
1419
import org.opensearch.transport.grpc.spi.AggregateProtoConverterRegistry;
@@ -38,6 +43,11 @@ public AggregateProtoConverterRegistryImpl() {
3843
private void registerBuiltInConverters() {
3944
spiRegistry.registerConverter(new MinAggregateProtoConverter());
4045
spiRegistry.registerConverter(new MaxAggregateProtoConverter());
46+
spiRegistry.registerConverter(new DoubleTermsAggregateConverter());
47+
spiRegistry.registerConverter(new LongTermsAggregateConverter());
48+
spiRegistry.registerConverter(new UnsignedLongTermsAggregateConverter());
49+
spiRegistry.registerConverter(new StringTermsAggregateConverter());
50+
spiRegistry.registerConverter(new UnmappedTermsAggregateConverter());
4151
}
4252

4353
@Override

modules/transport-grpc/src/main/java/org/opensearch/transport/grpc/proto/response/search/aggregation/AggregateProtoUtils.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
package org.opensearch.transport.grpc.proto.response.search.aggregation;
99

1010
import org.opensearch.protobufs.Aggregate;
11+
import org.opensearch.protobufs.NullValue;
12+
import org.opensearch.protobufs.ObjectMap;
1113
import org.opensearch.search.aggregations.Aggregation;
1214
import org.opensearch.search.aggregations.InternalAggregation;
1315
import org.opensearch.transport.grpc.spi.AggregateProtoConverterRegistry;
@@ -61,4 +63,27 @@ public static Aggregate toProto(Aggregation aggregation) throws IOException {
6163

6264
return registry.toProto((InternalAggregation) aggregation);
6365
}
66+
67+
public static ObjectMap.Value newValue(ObjectMap value) {
68+
return ObjectMap.Value.newBuilder().setObjectMap(value).build();
69+
}
70+
71+
public static ObjectMap.Value newValue(ObjectMap.ListValue value) {
72+
return ObjectMap.Value.newBuilder().setListValue(value).build();
73+
}
74+
75+
public static ObjectMap.Value newValue(long value) {
76+
return ObjectMap.Value.newBuilder().setInt64(value).build();
77+
}
78+
79+
public static ObjectMap.Value newValue(double value) {
80+
return ObjectMap.Value.newBuilder().setDouble(value).build();
81+
}
82+
public static ObjectMap.Value newValue(String value) {
83+
return ObjectMap.Value.newBuilder().setString(value).build();
84+
}
85+
86+
public static ObjectMap.Value newValue(NullValue nullValue) {
87+
return ObjectMap.Value.newBuilder().setNullValue(nullValue).build();
88+
}
6489
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
*/
4+
5+
/**
6+
* Protocol Buffer utilities for bucket aggregation responses.
7+
* Contains converters from OpenSearch internal bucket aggregation results to Protocol Buffer messages.
8+
*
9+
* @see org.opensearch.search.aggregations.bucket
10+
*/
11+
12+
package org.opensearch.transport.grpc.proto.response.search.aggregation.bucket;

0 commit comments

Comments
 (0)