Skip to content

Commit 6beed21

Browse files
committed
Create a parent abstract classes for BucketAggregationParsers
Signed-off-by: Yuanchun Shen <yuanchu@amazon.com>
1 parent d38a916 commit 6beed21

3 files changed

Lines changed: 101 additions & 27 deletions

File tree

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package org.opensearch.sql.opensearch.response.agg;
7+
8+
import java.util.List;
9+
import java.util.Map;
10+
import org.opensearch.search.SearchHits;
11+
import org.opensearch.search.aggregations.bucket.composite.CompositeAggregation;
12+
import org.opensearch.search.aggregations.bucket.range.Range;
13+
14+
/**
15+
* Abstract base class for parsing bucket aggregations from OpenSearch responses. Provides common
16+
* functionality for extracting key-value pairs from different types of buckets.
17+
*/
18+
public abstract class AbstractBucketAggregationParser
19+
implements OpenSearchAggregationResponseParser {
20+
/**
21+
* Extracts key-value pairs from a composite aggregation bucket without processing its
22+
* sub-aggregations.
23+
*
24+
* <p>For example, for the following CompositeAggregation bucket in response:
25+
*
26+
* <pre>{@code
27+
* {
28+
* "key": {
29+
* "firstname": "William",
30+
* "lastname": "Shakespeare"
31+
* },
32+
* "sub_agg_name": {
33+
* "buckets": []
34+
* }
35+
* }
36+
* }</pre>
37+
*
38+
* It returns {@code {"firstname": "William", "lastname": "Shakespeare"}} as the response.
39+
*
40+
* @param bucket the composite aggregation bucket to extract data from
41+
* @return a map containing the bucket's key-value pairs
42+
*/
43+
protected Map<String, Object> extract(CompositeAggregation.Bucket bucket) {
44+
return bucket.getKey();
45+
}
46+
47+
/**
48+
* Extracts key-value pairs from a range aggregation bucket without processing its
49+
* sub-aggregations.
50+
*
51+
* @param bucket the range aggregation bucket to extract data from
52+
* @param name the name to use as the key in the returned map
53+
* @return a map containing the bucket's key mapped to the provided name
54+
*/
55+
protected Map<String, Object> extract(Range.Bucket bucket, String name) {
56+
return Map.of(name, bucket.getKey());
57+
}
58+
59+
@Override
60+
public List<Map<String, Object>> parse(SearchHits hits) {
61+
throw new UnsupportedOperationException(this.getClass() + " doesn't support parse(SearchHits)");
62+
}
63+
}

opensearch/src/main/java/org/opensearch/sql/opensearch/response/agg/BucketAggregationParser.java

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,27 +5,48 @@
55

66
package org.opensearch.sql.opensearch.response.agg;
77

8-
import java.util.HashMap;
98
import java.util.List;
109
import java.util.Map;
1110
import lombok.EqualsAndHashCode;
1211
import lombok.Getter;
13-
import org.opensearch.search.SearchHits;
1412
import org.opensearch.search.aggregations.Aggregation;
1513
import org.opensearch.search.aggregations.Aggregations;
1614
import org.opensearch.search.aggregations.bucket.MultiBucketsAggregation;
1715
import org.opensearch.search.aggregations.bucket.composite.CompositeAggregation;
1816
import org.opensearch.search.aggregations.bucket.range.Range;
1917

18+
/**
19+
* Parser for bucket aggregations that contain sub-aggregations. This parser handles multiple levels
20+
* of multi-bucket aggregations by delegates sublevels to sub-parsers.
21+
*
22+
* <p>Please note that it does not handle metric or value count responses -- they should be parsed
23+
* only in {@link LeafBucketAggregationParser}.
24+
*/
2025
@Getter
21-
@EqualsAndHashCode
22-
public class BucketAggregationParser implements OpenSearchAggregationResponseParser {
23-
private final OpenSearchAggregationResponseParser subAggParser;
26+
@EqualsAndHashCode(callSuper = false)
27+
public class BucketAggregationParser extends AbstractBucketAggregationParser {
28+
/** The sub-aggregation parser used to process nested aggregations within each bucket. */
29+
private final AbstractBucketAggregationParser subAggParser;
2430

25-
public BucketAggregationParser(OpenSearchAggregationResponseParser subAggParser) {
31+
/**
32+
* Constructs a new BucketAggregationParser with the specified sub-aggregation parser.
33+
*
34+
* @param subAggParser the parser to handle sublevel multi-bucket aggregations within each bucket
35+
*/
36+
public BucketAggregationParser(AbstractBucketAggregationParser subAggParser) {
2637
this.subAggParser = subAggParser;
2738
}
2839

40+
/**
41+
* Parses the provided aggregations into a list of maps containing the aggregated data. This
42+
* method handles multi-bucket aggregations by processing each bucket and merging the results with
43+
* bucket-specific key information.
44+
*
45+
* @param aggregations the aggregations to parse
46+
* @return a list of maps containing the parsed aggregation data
47+
* @throws IllegalStateException if the aggregation type is not supported or if the sub-parser
48+
* type is invalid
49+
*/
2950
@Override
3051
public List<Map<String, Object>> parse(Aggregations aggregations) {
3152
if (subAggParser instanceof BucketAggregationParser) {
@@ -51,21 +72,16 @@ public List<Map<String, Object>> parse(Aggregations aggregations) {
5172
private List<Map<String, Object>> parse(MultiBucketsAggregation.Bucket bucket, String name) {
5273
List<Map<String, Object>> results = subAggParser.parse(bucket.getAggregations());
5374
if (bucket instanceof CompositeAggregation.Bucket compositeBucket) {
54-
Map<String, Object> common = new HashMap<>(compositeBucket.getKey());
75+
Map<String, Object> common = extract(compositeBucket);
5576
for (Map<String, Object> r : results) {
5677
r.putAll(common);
5778
}
58-
} else if (bucket instanceof Range.Bucket) {
79+
} else if (bucket instanceof Range.Bucket rangeBucket) {
80+
Map<String, Object> common = extract(rangeBucket, name);
5981
for (Map<String, Object> r : results) {
60-
r.put(name, bucket.getKey());
82+
r.putAll(common);
6183
}
6284
}
6385
return results;
6486
}
65-
66-
@Override
67-
public List<Map<String, Object>> parse(SearchHits hits) {
68-
throw new UnsupportedOperationException(
69-
"BucketAggregationParser doesn't support parse(SearchHits)");
70-
}
7187
}

opensearch/src/main/java/org/opensearch/sql/opensearch/response/agg/LeafBucketAggregationParser.java

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,20 @@
1111
import java.util.Objects;
1212
import lombok.EqualsAndHashCode;
1313
import lombok.Getter;
14-
import org.opensearch.search.SearchHits;
1514
import org.opensearch.search.aggregations.Aggregation;
1615
import org.opensearch.search.aggregations.Aggregations;
1716
import org.opensearch.search.aggregations.bucket.MultiBucketsAggregation;
1817
import org.opensearch.search.aggregations.bucket.composite.CompositeAggregation;
1918
import org.opensearch.search.aggregations.bucket.range.Range;
2019

2120
/**
22-
* Use LeafBucketAggregationParser only when there is a single group-by key, it returns multiple
23-
* buckets. {@link BucketAggregationParser} is used for multiple group by keys
21+
* Parser for leaf-level bucket aggregations that may contain metric information but no nested
22+
* multi-bucket aggregations.
23+
*
24+
* <p>For aggregations with nested bucket structures, use {@link BucketAggregationParser} instead.
2425
*/
25-
@EqualsAndHashCode
26-
public class LeafBucketAggregationParser implements OpenSearchAggregationResponseParser {
26+
@EqualsAndHashCode(callSuper = false)
27+
public class LeafBucketAggregationParser extends AbstractBucketAggregationParser {
2728
@Getter private final MetricParserHelper metricsParser;
2829
// countAggNameList dedicated the list of count aggregations which are filled by doc_count
2930
private List<String> countAggNameList = List.of();
@@ -50,16 +51,10 @@ public List<Map<String, Object>> parse(Aggregations aggregations) {
5051
.getBuckets().stream().map(b -> parse(b, agg.getName())).filter(Objects::nonNull).toList();
5152
}
5253

53-
@Override
54-
public List<Map<String, Object>> parse(SearchHits hits) {
55-
throw new UnsupportedOperationException(
56-
"LeafBucketAggregationParser doesn't support parse(SearchHits)");
57-
}
58-
5954
private Map<String, Object> parse(MultiBucketsAggregation.Bucket bucket, String name) {
6055
Map<String, Object> result = metricsParser.parse(bucket.getAggregations());
6156
if (bucket instanceof CompositeAggregation.Bucket compositeBucket) {
62-
result.putAll(compositeBucket.getKey());
57+
result.putAll(extract(compositeBucket));
6358
} else if (bucket instanceof Range.Bucket) {
6459
if (bucket.getDocCount() == 0) {
6560
return null;

0 commit comments

Comments
 (0)