diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b3033aecf..eb4771c7c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,6 +45,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - Fix AwsSdk2TransportOptions.responseCompression ([#322](https://github.com/opensearch-project/opensearch-java/pull/322)) - Bulk UpdateOperation misses upsert options ([#353](https://github.com/opensearch-project/opensearch-java/pull/353)) - Fix missing key property in the RangeBucket ([#370](https://github.com/opensearch-project/opensearch-java/pull/370)) +- Fix missing Highlight and SourceConfig in the MultisearchBody ([#442](https://github.com/opensearch-project/opensearch-java/pull/442)) ### Security diff --git a/java-client/src/main/java/org/opensearch/client/opensearch/core/msearch/MultisearchBody.java b/java-client/src/main/java/org/opensearch/client/opensearch/core/msearch/MultisearchBody.java index 24b6fa98f7..8fb418a153 100644 --- a/java-client/src/main/java/org/opensearch/client/opensearch/core/msearch/MultisearchBody.java +++ b/java-client/src/main/java/org/opensearch/client/opensearch/core/msearch/MultisearchBody.java @@ -38,6 +38,8 @@ import org.opensearch.client.opensearch._types.aggregations.Aggregation; import org.opensearch.client.opensearch._types.query_dsl.Query; +import org.opensearch.client.opensearch.core.search.Highlight; +import org.opensearch.client.opensearch.core.search.SourceConfig; import org.opensearch.client.opensearch.core.search.Suggester; import org.opensearch.client.opensearch.core.search.TrackHits; import org.opensearch.client.json.JsonpDeserializable; @@ -77,6 +79,12 @@ public class MultisearchBody implements JsonpSerializable { @Nullable private final Suggester suggest; + @Nullable + private Highlight highlight; + + @Nullable + private SourceConfig source; + // --------------------------------------------------------------------------------------------- private MultisearchBody(Builder builder) { @@ -87,6 +95,8 @@ private MultisearchBody(Builder builder) { this.size = builder.size; this.trackTotalHits = builder.trackTotalHits; this.suggest = builder.suggest; + this.highlight = builder.highlight; + this.source = builder.source; } @@ -141,6 +151,22 @@ public final Suggester suggest() { return this.suggest; } + /** + * API name: {@code highlight} + */ + @Nullable + public final Highlight highlight() { + return this.highlight; + } + + /** + * API name: {@code _source} + */ + @Nullable + public final SourceConfig source() { + return this.source; + } + /** * Serialize this object to JSON. */ @@ -189,6 +215,18 @@ protected void serializeInternal(JsonGenerator generator, JsonpMapper mapper) { } + if (this.highlight != null) { + generator.writeKey("highlight"); + this.highlight.serialize(generator, mapper); + + } + + if (this.source != null) { + generator.writeKey("_source"); + this.source.serialize(generator, mapper); + + } + } // --------------------------------------------------------------------------------------------- @@ -216,6 +254,13 @@ public static class Builder extends ObjectBuilderBase implements ObjectBuilder @@ -306,6 +351,36 @@ public final Builder suggest(Function> fn) { + return this.highlight(fn.apply(new Highlight.Builder()).build()); + } + + /** + * API name: {@code _source} + */ + public final Builder source(@Nullable SourceConfig value) { + this.source = value; + return this; + } + + /** + * API name: {@code _source} + */ + public final Builder source(Function> fn) { + return this.source(fn.apply(new SourceConfig.Builder()).build()); + } + /** * Builds a {@link MultisearchBody}. * @@ -336,6 +411,8 @@ protected static void setupMultisearchBodyDeserializer(ObjectDeserializer response = sendMSearchRequest(index, List.of(largeItemsQuery, mediumItemsQuery, smallItemsQuery)); + assertEquals(3, response.responses().size()); + assertEquals(2, response.responses().get(0).result().hits().hits().size()); + assertEquals(2, response.responses().get(1).result().hits().hits().size()); + assertEquals(2, response.responses().get(2).result().hits().hits().size()); + } + + @Test + public void shouldReturnHighlightsInMultipleSearches() throws Exception { + String index = "multiple_searches_request_with_highlights"; + createTestDocuments(index); + + RequestItem largeItemsQuery = createMSearchQueryWithHighlight("large"); + RequestItem mediumItemsQuery = createMSearchQueryWithHighlight("medium"); + RequestItem smallItemsQuery = createMSearchQueryWithHighlight("small"); + + MsearchResponse response = sendMSearchRequest(index, List.of(largeItemsQuery, mediumItemsQuery, smallItemsQuery)); + assertEquals(3, response.responses().size()); + assertResponseHighlights(response.responses().get(0)); + assertResponseHighlights(response.responses().get(1)); + assertResponseHighlights(response.responses().get(2)); + } + + @Test + public void shouldReturnMultiSearchesWithSelectedSourceFieldsOnly() throws Exception { + String index = "multiple_searches_request_with_sources"; + createTestDocuments(index); + + RequestItem largeItemsQuery = createMSearchQueryWithSelectedSourceFields("large"); + RequestItem mediumItemsQuery = createMSearchQueryWithSelectedSourceFields("medium"); + RequestItem smallItemsQuery = createMSearchQueryWithSelectedSourceFields("small"); + + MsearchResponse response = sendMSearchRequest(index, List.of(largeItemsQuery, mediumItemsQuery, smallItemsQuery)); + assertEquals(3, response.responses().size()); + assertResponseSources(response.responses().get(0)); + assertResponseSources(response.responses().get(1)); + assertResponseSources(response.responses().get(2)); + } + + private void assertResponseSources(MultiSearchResponseItem response) { + List> hitsWithHighlights = response.result().hits().hits(); + assertEquals(2, hitsWithHighlights.size()); + for (Hit shopItemHit : hitsWithHighlights) { + assertNotNull(shopItemHit.source()); + assertNotNull(shopItemHit.source().getName()); + assertNotNull(shopItemHit.source().getSize()); + assertNull(shopItemHit.source().getCompany()); + } + } + + private void assertResponseHighlights(MultiSearchResponseItem response) { + List> hitsWithHighlights = response.result().hits().hits(); + assertEquals(2, hitsWithHighlights.size()); + assertEquals(1, hitsWithHighlights.get(0).highlight().size()); + assertEquals(1, hitsWithHighlights.get(1).highlight().size()); + } + + private RequestItem createMSearchQuery(String itemSize) { + return createMSearchQuery(itemSize, null, List.of()); + } + + private RequestItem createMSearchQueryWithHighlight(String itemSize) { + return createMSearchQuery(itemSize, "size", List.of()); + } + + private RequestItem createMSearchQueryWithSelectedSourceFields(String itemSize) { + return createMSearchQuery(itemSize, null, List.of("name", "size")); + } + + private RequestItem createMSearchQuery(String itemSize, String fieldName, List sources) { + return RequestItem.of(item -> item.header(header -> header) + .body(body -> body.query(createItemSizeSearchQuery(itemSize)) + .highlight(createHighlight(fieldName)) + .source(createSourcesConfig(sources)) + ) + ); + } + + private SourceConfig createSourcesConfig(List sources) { + return sources.isEmpty() ? null : SourceConfig.of(builder -> builder.filter(filter -> filter.includes(sources))); + } + + private Highlight createHighlight(String fieldName) { + return fieldName != null ? Highlight.of( + builder -> builder.fields( + fieldName, HighlightField.of( + field -> field.preTags("") + .postTags("") + ) + ) + ) : null; + } + + private Query createItemSizeSearchQuery(String itemSize) { + return Query.of(query -> query.bool(builder -> builder.filter( + filter -> filter.term( + TermQuery.of(term -> term.field("size") + .value(FieldValue.of(itemSize)) + ) + ) + ))); + } + + private MsearchResponse sendMSearchRequest(String index, List searches) throws IOException { + return javaClient().msearch(builder -> builder.index(List.of(index)).searches(searches), ShopItem.class); + } + + private void createTestDocuments(String index) throws IOException { + javaClient().create(_1 -> _1.index(index).id("1").document(createItem("hammer", "large", "yes")).refresh(Refresh.True)); + javaClient().create(_1 -> _1.index(index).id("2").document(createItem("drill", "large", "yes")).refresh(Refresh.True)); + javaClient().create(_1 -> _1.index(index).id("3").document(createItem("jack", "medium", "yes")).refresh(Refresh.True)); + javaClient().create(_1 -> _1.index(index).id("4").document(createItem("wrench", "medium", "no")).refresh(Refresh.True)); + javaClient().create(_1 -> _1.index(index).id("5").document(createItem("screws", "small", "no")).refresh(Refresh.True)); + javaClient().create(_1 -> _1.index(index).id("6").document(createItem("nuts", "small", "no")).refresh(Refresh.True)); + } + + private ShopItem createItem(String name, String size, String company) { + return new ShopItem(name, size, company); + } + + public static class ShopItem { + private String name; + private String size; + private String company; + + public ShopItem() { + } + + public ShopItem(String name, String size, String company) { + this.name = name; + this.size = size; + this.company = company; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getSize() { + return size; + } + + public void setSize(String size) { + this.size = size; + } + + public String getCompany() { + return company; + } + + public void setCompany(String company) { + this.company = company; + } + } +} diff --git a/java-client/src/test/java/org/opensearch/client/opensearch/integTest/httpclient5/MultiSearchRequestIT.java b/java-client/src/test/java/org/opensearch/client/opensearch/integTest/httpclient5/MultiSearchRequestIT.java new file mode 100644 index 0000000000..e4b85a7891 --- /dev/null +++ b/java-client/src/test/java/org/opensearch/client/opensearch/integTest/httpclient5/MultiSearchRequestIT.java @@ -0,0 +1,14 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.client.opensearch.integTest.httpclient5; + +import org.opensearch.client.opensearch.integTest.AbstractMultiSearchRequestIT; + +public class MultiSearchRequestIT extends AbstractMultiSearchRequestIT implements HttpClient5TransportSupport { +} diff --git a/java-client/src/test/java/org/opensearch/client/opensearch/integTest/restclient/MultiSearchRequestIT.java b/java-client/src/test/java/org/opensearch/client/opensearch/integTest/restclient/MultiSearchRequestIT.java new file mode 100644 index 0000000000..606cf8be2d --- /dev/null +++ b/java-client/src/test/java/org/opensearch/client/opensearch/integTest/restclient/MultiSearchRequestIT.java @@ -0,0 +1,25 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.client.opensearch.integTest.restclient; + +import org.apache.http.HttpHost; +import org.opensearch.client.json.jackson.JacksonJsonpMapper; +import org.opensearch.client.opensearch.integTest.AbstractMultiSearchRequestIT; +import org.opensearch.client.transport.OpenSearchTransport; +import org.opensearch.client.transport.rest_client.RestClientTransport; +import org.opensearch.common.settings.Settings; + +import java.io.IOException; + +public class MultiSearchRequestIT extends AbstractMultiSearchRequestIT { + @Override + public OpenSearchTransport buildTransport(Settings settings, HttpHost[] hosts) throws IOException { + return new RestClientTransport(buildClient(settings, hosts), new JacksonJsonpMapper()); + } +}