diff --git a/CHANGELOG.md b/CHANGELOG.md index 262966246f..7d79fd6885 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,7 @@ This section is for maintaining a changelog for all breaking changes for the cli ### Fixed - Fix version and build ([#254](https://github.com/opensearch-project/opensearch-java/pull/254)) - Fix integer overflow for variables in indices stats response ([#960](https://github.com/opensearch-project/opensearch-java/pull/960)) +- Fix composite aggregations for search requests ([#967](https://github.com/opensearch-project/opensearch-java/pull/967)) ### Security diff --git a/guides/search.md b/guides/search.md index 7fd3e06cc9..cf08f7a872 100644 --- a/guides/search.md +++ b/guides/search.md @@ -4,11 +4,13 @@ - [Basic Search](#basic-search) - [Get raw JSON results](#get-raw-json-results) - [Search documents using a match query](#search-documents-using-a-match-query) + - [Search documents using a hybrid query](#search-documents-using-a-hybrid-query) - [Search documents using suggesters](#search-documents-using-suggesters) - [Using completion suggester](#using-completion-suggester) - [Using term suggester](#using-term-suggester) - [Using phrase suggester](#using-phrase-suggester) - [Aggregations](#aggregations) + - [Composite Aggregations](#composite-aggregations) # Search @@ -299,4 +301,24 @@ for (Map.Entry entry : searchResponse.aggregations().entrySet } ``` +#### Composite Aggregations + +```java +final Map comAggrSrcMap = new HashMap<>(); +CompositeAggregationSource compositeAggregationSource1 = new CompositeAggregationSource.Builder().terms( + termsAggrBuilder -> termsAggrBuilder.field("title.keyword").missingBucket(false).order(SortOrder.Asc) +).build(); +comAggrSrcMap.put("titles", compositeAggregationSource1); + +CompositeAggregation compAgg = new CompositeAggregation.Builder().sources(comAggrSrcMap).build(); +Aggregation aggregation = new Aggregation.Builder().composite(compAgg).build(); + +SearchRequest request = SearchRequest.of(r -> r.index(indexName).query(q -> q.match(m -> m.field("title").query(FieldValue.of("Document 1")))).aggregations("my_buckets", aggregation)); +SearchResponse response = client.search(request, IndexData.class); +for (Map.Entry entry : response.aggregations().entrySet()) { + LOGGER.info("Agg - {}", entry.getKey()); + entry.getValue().composite().buckets().array().forEach(b -> LOGGER.info("{} : {}", b.key(), b.docCount())); +} +``` + You can find a working sample of the above code in [Search.java](../samples/src/main/java/org/opensearch/client/samples/Search.java). \ No newline at end of file diff --git a/java-client/src/main/java/org/opensearch/client/opensearch/_types/aggregations/CompositeValuesSource.java b/java-client/src/main/java/org/opensearch/client/opensearch/_types/aggregations/CompositeValuesSource.java index 6b40580c30..efd973b1cb 100644 --- a/java-client/src/main/java/org/opensearch/client/opensearch/_types/aggregations/CompositeValuesSource.java +++ b/java-client/src/main/java/org/opensearch/client/opensearch/_types/aggregations/CompositeValuesSource.java @@ -9,14 +9,11 @@ import org.opensearch.client.json.ObjectDeserializer; import org.opensearch.client.opensearch._types.Script; import org.opensearch.client.opensearch._types.SortOrder; -import org.opensearch.client.util.ApiTypeHelper; import org.opensearch.client.util.ObjectBuilder; import org.opensearch.client.util.ObjectBuilderBase; public abstract class CompositeValuesSource implements JsonpSerializable { - private final String name; - @Nullable private final String field; @@ -39,8 +36,6 @@ public abstract class CompositeValuesSource implements JsonpSerializable { private String format; protected CompositeValuesSource(AbstractBuilder builder) { - this.name = ApiTypeHelper.requireNonNull(builder.name, this, "name"); - ; this.field = builder.field; this.script = builder.script; this.valueType = builder.valueType; @@ -50,13 +45,6 @@ protected CompositeValuesSource(AbstractBuilder builder) { this.format = builder.format; } - /** - * API name: {@code name} - */ - public final String name() { - return this.name; - } - /** * API name: {@code field} */ @@ -123,9 +111,6 @@ public void serialize(JsonGenerator generator, JsonpMapper mapper) { } protected void serializeInternal(JsonGenerator generator, JsonpMapper mapper) { - generator.writeKey(this.name); - generator.write(this.name); - if (this.field != null) { generator.writeKey("field"); generator.write(this.field); @@ -165,8 +150,6 @@ protected void serializeInternal(JsonGenerator generator, JsonpMapper mapper) { protected abstract static class AbstractBuilder> extends ObjectBuilderBase { - private String name; - @Nullable private String field; @@ -188,14 +171,6 @@ protected abstract static class AbstractBuilder> void setupCompositeValuesSourceDeserializer( ObjectDeserializer op ) { - op.add(AbstractBuilder::name, JsonpDeserializer.stringDeserializer(), "name"); op.add(AbstractBuilder::field, JsonpDeserializer.stringDeserializer(), "field"); op.add(AbstractBuilder::script, Script._DESERIALIZER, "script"); op.add(AbstractBuilder::valueType, ValueType._DESERIALIZER, "value_type"); diff --git a/java-client/src/test/java11/org/opensearch/client/opensearch/integTest/AbstractSearchRequestIT.java b/java-client/src/test/java11/org/opensearch/client/opensearch/integTest/AbstractSearchRequestIT.java index 463d2d9d04..98914ec5c1 100644 --- a/java-client/src/test/java11/org/opensearch/client/opensearch/integTest/AbstractSearchRequestIT.java +++ b/java-client/src/test/java11/org/opensearch/client/opensearch/integTest/AbstractSearchRequestIT.java @@ -10,6 +10,8 @@ import java.io.IOException; import java.util.Arrays; +import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.stream.Collectors; import org.junit.Test; @@ -17,10 +19,10 @@ import org.opensearch.client.opensearch._types.FieldValue; import org.opensearch.client.opensearch._types.SortOrder; import org.opensearch.client.opensearch._types.aggregations.Aggregation; -import org.opensearch.client.opensearch._types.aggregations.AggregationBuilders; +import org.opensearch.client.opensearch._types.aggregations.CompositeAggregation; import org.opensearch.client.opensearch._types.aggregations.CompositeAggregationSource; +import org.opensearch.client.opensearch._types.aggregations.CompositeBucket; import org.opensearch.client.opensearch._types.mapping.Property; -import org.opensearch.client.opensearch._types.query_dsl.BoolQuery; import org.opensearch.client.opensearch._types.query_dsl.MatchQuery; import org.opensearch.client.opensearch._types.query_dsl.Query; import org.opensearch.client.opensearch._types.query_dsl.QueryBuilders; @@ -71,22 +73,46 @@ public void shouldReturnSearchResultsWithCompositeAgg() throws Exception { final String index = "search_request"; createIndex(index); - BoolQuery.Builder boolQueryBuilder = QueryBuilders.bool(); + final Query query = Query.of( + q -> q.bool( + builder -> builder.filter(filter -> filter.term(TermQuery.of(term -> term.field("size").value(FieldValue.of("huge"))))) + ) + ); + final Map comAggrSrcMap = new HashMap<>(); CompositeAggregationSource compositeAggregationSource1 = new CompositeAggregationSource.Builder().terms( - t -> t.field("size.keyword").order(SortOrder.Desc).name("terms").missingBucket(true) + termsAggrBuilder -> termsAggrBuilder.field("quantity").missingBucket(false).order(SortOrder.Asc) ).build(); + comAggrSrcMap.put("quantity", compositeAggregationSource1); - Aggregation aggregation = new Aggregation.Builder().composite( - AggregationBuilders.composite().sources(Map.of("composite", compositeAggregationSource1)).size(0).build() - ).build(); + CompositeAggregation compAgg = new CompositeAggregation.Builder().sources(comAggrSrcMap).build(); - final SearchRequest request = SearchRequest.of( - r -> r.index(index).query(boolQueryBuilder.build().toQuery()).aggregations("composite", aggregation).size(1000) - ); + Aggregation aggregation = new Aggregation.Builder().composite(compAgg).build(); + + final SearchRequest request = SearchRequest.of(r -> r.index(index).query(query).aggregations("my_buckets", aggregation)); final SearchResponse response = javaClient().search(request, ShopItem.class); - assertEquals(response.hits().hits().size(), 8); + assertEquals(response.hits().hits().size(), 2); + for (Map.Entry entry : response.aggregations().entrySet()) { + CompositeAggregation compositeAggregation = entry.getValue().composite(); + assertEquals(2, compositeAggregation.buckets().size()); + assertEquals(1, Integer.parseInt(compositeAggregation.buckets().get(0).key().get("quantity").toString())); + assertEquals(1, compositeAggregation.buckets().get(0).docCount()); + assertEquals(2, Integer.parseInt(compositeAggregation.buckets().get(1).key().get("quantity").toString())); + assertEquals(1, compositeAggregation.buckets().get(1).docCount()); + } + List buckets = response.aggregations() + .entrySet() + .stream() + .filter(e -> e.getKey().equals("my_buckets")) + .map(e -> e.getValue().composite().buckets().array()) + .flatMap(List::stream) + .collect(Collectors.toList()); + assertEquals(2, buckets.size()); + assertEquals(1, Integer.parseInt(buckets.get(0).key().get("quantity").toString())); + assertEquals(1, buckets.get(0).docCount()); + assertEquals(2, Integer.parseInt(buckets.get(1).key().get("quantity").toString())); + assertEquals(1, buckets.get(1).docCount()); } @Test diff --git a/samples/src/main/java/org/opensearch/client/samples/Search.java b/samples/src/main/java/org/opensearch/client/samples/Search.java index 417ecba409..e6f02b8d92 100644 --- a/samples/src/main/java/org/opensearch/client/samples/Search.java +++ b/samples/src/main/java/org/opensearch/client/samples/Search.java @@ -10,14 +10,18 @@ import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.Map; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.opensearch.client.opensearch.OpenSearchClient; import org.opensearch.client.opensearch._types.FieldValue; import org.opensearch.client.opensearch._types.Refresh; +import org.opensearch.client.opensearch._types.SortOrder; import org.opensearch.client.opensearch._types.aggregations.Aggregate; import org.opensearch.client.opensearch._types.aggregations.Aggregation; +import org.opensearch.client.opensearch._types.aggregations.CompositeAggregation; +import org.opensearch.client.opensearch._types.aggregations.CompositeAggregationSource; import org.opensearch.client.opensearch._types.analysis.Analyzer; import org.opensearch.client.opensearch._types.analysis.CustomAnalyzer; import org.opensearch.client.opensearch._types.analysis.ShingleTokenFilter; @@ -106,6 +110,27 @@ public static void main(String[] args) { entry.getValue().sterms().buckets().array().forEach(b -> LOGGER.info("{} : {}", b.key(), b.docCount())); } + // Custom Aggregations + final Map comAggrSrcMap = new HashMap<>(); + CompositeAggregationSource compositeAggregationSource1 = new CompositeAggregationSource.Builder().terms( + termsAggrBuilder -> termsAggrBuilder.field("title.keyword").missingBucket(false).order(SortOrder.Asc) + ).build(); + comAggrSrcMap.put("titles", compositeAggregationSource1); + + CompositeAggregation compAgg = new CompositeAggregation.Builder().sources(comAggrSrcMap).build(); + Aggregation aggregation = new Aggregation.Builder().composite(compAgg).build(); + + SearchRequest request = SearchRequest.of( + r -> r.index(indexName) + .query(q -> q.match(m -> m.field("title").query(FieldValue.of("Document 1")))) + .aggregations("my_buckets", aggregation) + ); + SearchResponse response = client.search(request, IndexData.class); + for (Map.Entry entry : response.aggregations().entrySet()) { + LOGGER.info("Agg - {}", entry.getKey()); + entry.getValue().composite().buckets().array().forEach(b -> LOGGER.info("{} : {}", b.key(), b.docCount())); + } + // HybridSearch Query searchQuery = Query.of( h -> h.hybrid(