diff --git a/java-client/src/main/java/co/elastic/clients/elasticsearch/core/MsearchRequest.java b/java-client/src/main/java/co/elastic/clients/elasticsearch/core/MsearchRequest.java index ad4c3d249..c93a3bafc 100644 --- a/java-client/src/main/java/co/elastic/clients/elasticsearch/core/MsearchRequest.java +++ b/java-client/src/main/java/co/elastic/clients/elasticsearch/core/MsearchRequest.java @@ -108,6 +108,9 @@ public class MsearchRequest extends RequestBase implements NdJsonpSerializable, @Nullable private final Boolean ignoreUnavailable; + @Nullable + private final Boolean includeNamedQueriesScore; + private final List index; @Nullable @@ -136,6 +139,7 @@ private MsearchRequest(Builder builder) { this.expandWildcards = ApiTypeHelper.unmodifiable(builder.expandWildcards); this.ignoreThrottled = builder.ignoreThrottled; this.ignoreUnavailable = builder.ignoreUnavailable; + this.includeNamedQueriesScore = builder.includeNamedQueriesScore; this.index = ApiTypeHelper.unmodifiable(builder.index); this.maxConcurrentSearches = builder.maxConcurrentSearches; this.maxConcurrentShardRequests = builder.maxConcurrentShardRequests; @@ -210,6 +214,22 @@ public final Boolean ignoreUnavailable() { return this.ignoreUnavailable; } + /** + * Indicates whether hit.matched_queries should be rendered as a map that + * includes the name of the matched query associated with its score (true) or as + * an array containing the name of the matched queries (false) This + * functionality reruns each named query on every hit in a search response. + * Typically, this adds a small overhead to a request. However, using + * computationally expensive named queries on a large number of hits may add + * significant overhead. + *

+ * API name: {@code include_named_queries_score} + */ + @Nullable + public final Boolean includeNamedQueriesScore() { + return this.includeNamedQueriesScore; + } + /** * Comma-separated list of data streams, indices, and index aliases to search. *

@@ -320,6 +340,9 @@ public static class Builder extends RequestBase.AbstractBuilder impleme @Nullable private Boolean ignoreUnavailable; + @Nullable + private Boolean includeNamedQueriesScore; + @Nullable private List index; @@ -413,6 +436,22 @@ public final Builder ignoreUnavailable(@Nullable Boolean value) { return this; } + /** + * Indicates whether hit.matched_queries should be rendered as a map that + * includes the name of the matched query associated with its score (true) or as + * an array containing the name of the matched queries (false) This + * functionality reruns each named query on every hit in a search response. + * Typically, this adds a small overhead to a request. However, using + * computationally expensive named queries on a large number of hits may add + * significant overhead. + *

+ * API name: {@code include_named_queries_score} + */ + public final Builder includeNamedQueriesScore(@Nullable Boolean value) { + this.includeNamedQueriesScore = value; + return this; + } + /** * Comma-separated list of data streams, indices, and index aliases to search. *

@@ -613,6 +652,9 @@ public MsearchRequest build() { params.put("expand_wildcards", request.expandWildcards.stream().map(v -> v.jsonValue()).collect(Collectors.joining(","))); } + if (request.includeNamedQueriesScore != null) { + params.put("include_named_queries_score", String.valueOf(request.includeNamedQueriesScore)); + } if (request.searchType != null) { params.put("search_type", request.searchType.jsonValue()); } diff --git a/java-client/src/main/java/co/elastic/clients/elasticsearch/core/SearchRequest.java b/java-client/src/main/java/co/elastic/clients/elasticsearch/core/SearchRequest.java index f908b57ba..bdbb7acb3 100644 --- a/java-client/src/main/java/co/elastic/clients/elasticsearch/core/SearchRequest.java +++ b/java-client/src/main/java/co/elastic/clients/elasticsearch/core/SearchRequest.java @@ -184,6 +184,9 @@ public class SearchRequest extends RequestBase implements JsonpSerializable { @Nullable private final Boolean ignoreUnavailable; + @Nullable + private final Boolean includeNamedQueriesScore; + private final List index; private final List> indicesBoost; @@ -304,6 +307,7 @@ private SearchRequest(Builder builder) { this.highlight = builder.highlight; this.ignoreThrottled = builder.ignoreThrottled; this.ignoreUnavailable = builder.ignoreUnavailable; + this.includeNamedQueriesScore = builder.includeNamedQueriesScore; this.index = ApiTypeHelper.unmodifiable(builder.index); this.indicesBoost = ApiTypeHelper.unmodifiable(builder.indicesBoost); this.knn = ApiTypeHelper.unmodifiable(builder.knn); @@ -598,6 +602,22 @@ public final Boolean ignoreUnavailable() { return this.ignoreUnavailable; } + /** + * If true, the response includes the score contribution from any + * named queries. + *

+ * This functionality reruns each named query on every hit in a search response. + * Typically, this adds a small overhead to a request. However, using + * computationally expensive named queries on a large number of hits may add + * significant overhead. + *

+ * API name: {@code include_named_queries_score} + */ + @Nullable + public final Boolean includeNamedQueriesScore() { + return this.includeNamedQueriesScore; + } + /** * A comma-separated list of data streams, indices, and aliases to search. It * supports wildcards (*). To search all data streams and indices, @@ -1363,6 +1383,9 @@ public static class Builder extends RequestBase.AbstractBuilder impleme @Nullable private Boolean ignoreUnavailable; + @Nullable + private Boolean includeNamedQueriesScore; + @Nullable private List index; @@ -1883,6 +1906,22 @@ public final Builder ignoreUnavailable(@Nullable Boolean value) { return this; } + /** + * If true, the response includes the score contribution from any + * named queries. + *

+ * This functionality reruns each named query on every hit in a search response. + * Typically, this adds a small overhead to a request. However, using + * computationally expensive named queries on a large number of hits may add + * significant overhead. + *

+ * API name: {@code include_named_queries_score} + */ + public final Builder includeNamedQueriesScore(@Nullable Boolean value) { + this.includeNamedQueriesScore = value; + return this; + } + /** * A comma-separated list of data streams, indices, and aliases to search. It * supports wildcards (*). To search all data streams and indices, @@ -2909,6 +2948,9 @@ protected static void setupSearchRequestDeserializer(ObjectDeserializer implements JsonpSerializable { private final Map innerHits; - private final List matchedQueries; + private final Map matchedQueries; @Nullable private final NestedIdentity nested; @@ -212,7 +212,7 @@ public final Map innerHits() { /** * API name: {@code matched_queries} */ - public final List matchedQueries() { + public final Map matchedQueries() { return this.matchedQueries; } @@ -380,12 +380,21 @@ protected void serializeInternal(JsonGenerator generator, JsonpMapper mapper) { } if (ApiTypeHelper.isDefined(this.matchedQueries)) { generator.writeKey("matched_queries"); - generator.writeStartArray(); - for (String item0 : this.matchedQueries) { - generator.write(item0); + if (this.matchedQueries.values().stream().allMatch(Objects::isNull)) { + generator.writeStartArray(); + for (String item0 : this.matchedQueries.keySet()) { + generator.write(item0); + } + generator.writeEnd(); + } else { + generator.writeStartObject(); + for (Map.Entry item0 : this.matchedQueries.entrySet()) { + generator.writeKey(item0.getKey()); + generator.write(item0.getValue()); + } + generator.writeEnd(); } - generator.writeEnd(); } if (this.nested != null) { @@ -509,7 +518,7 @@ public static class Builder extends WithJsonObjectBuilderBase innerHits; @Nullable - private List matchedQueries; + private Map matchedQueries; @Nullable private NestedIdentity nested; @@ -662,20 +671,20 @@ public final Builder innerHits(String key, /** * API name: {@code matched_queries} *

- * Adds all elements of list to matchedQueries. + * Adds all entries of map to matchedQueries. */ - public final Builder matchedQueries(List list) { - this.matchedQueries = _listAddAll(this.matchedQueries, list); + public final Builder matchedQueries(Map map) { + this.matchedQueries = _mapPutAll(this.matchedQueries, map); return this; } /** * API name: {@code matched_queries} *

- * Adds one or more values to matchedQueries. + * Adds an entry to matchedQueries. */ - public final Builder matchedQueries(String value, String... values) { - this.matchedQueries = _listAdd(this.matchedQueries, value, values); + public final Builder matchedQueries(String key, Double value) { + this.matchedQueries = _mapPut(this.matchedQueries, key, value); return this; } @@ -937,7 +946,8 @@ protected static void setupHitDeserializer(ObjectDeserializer JsonpDeserializer> stringMapDeserializer(JsonpDeserial return new JsonpDeserializerBase.StringMapDeserializer(itemDeserializer); } + static JsonpDeserializer> stringArrayMapUnionDeserializer(JsonpDeserializer itemDeserializer) { + return new JsonpDeserializerBase.StringArrayMapUnionDeserializer(itemDeserializer); + } + static JsonpDeserializer> enumMapDeserializer( JsonpDeserializer keyDeserializer, JsonpDeserializer valueDeserializer ) { diff --git a/java-client/src/main/java/co/elastic/clients/json/JsonpDeserializerBase.java b/java-client/src/main/java/co/elastic/clients/json/JsonpDeserializerBase.java index fb9bd88e7..bbe6eb7de 100644 --- a/java-client/src/main/java/co/elastic/clients/json/JsonpDeserializerBase.java +++ b/java-client/src/main/java/co/elastic/clients/json/JsonpDeserializerBase.java @@ -356,6 +356,43 @@ public Map deserialize(JsonParser parser, JsonpMapper mapper, Event e } } + // Used for fields such as matched_queries, which can be either an array or a dictionary, where the array + // value type is the same as the dictionary key type + static class StringArrayMapUnionDeserializer extends JsonpDeserializerBase> { + private final JsonpDeserializer itemDeserializer; + + protected StringArrayMapUnionDeserializer(JsonpDeserializer itemDeserializer) { + super(EnumSet.of(Event.START_OBJECT,Event.START_ARRAY)); + this.itemDeserializer = itemDeserializer; + } + + @Override + public Map deserialize(JsonParser parser, JsonpMapper mapper, Event event) { + Map result = new HashMap<>(); + String key = null; + try { + // Array case, deserializing it into a map with null values + if (event == Event.START_ARRAY) { + while ((event = parser.next()) != Event.END_ARRAY) { + JsonpUtils.expectEvent(parser, Event.VALUE_STRING, event); + result.put(parser.getString(),null); + } + } else { + // Dictionary case + while ((event = parser.next()) != Event.END_OBJECT) { + JsonpUtils.expectEvent(parser, Event.KEY_NAME, event); + key = parser.getString(); + T value = itemDeserializer.deserialize(parser, mapper); + result.put(key, value); + } + } + } catch (Exception e) { + throw JsonpMappingException.from(e, null, key, parser); + } + return result; + } + } + static class EnumMapDeserializer extends JsonpDeserializerBase> { private final JsonpDeserializer keyDeserializer; private final JsonpDeserializer valueDeserializer; diff --git a/java-client/src/test/java/co/elastic/clients/elasticsearch/model/BehaviorsTest.java b/java-client/src/test/java/co/elastic/clients/elasticsearch/model/BehaviorsTest.java index a0498fc3f..3c740d0e7 100644 --- a/java-client/src/test/java/co/elastic/clients/elasticsearch/model/BehaviorsTest.java +++ b/java-client/src/test/java/co/elastic/clients/elasticsearch/model/BehaviorsTest.java @@ -33,6 +33,7 @@ import co.elastic.clients.elasticsearch._types.query_dsl.ShapeQuery; import co.elastic.clients.elasticsearch._types.query_dsl.TermQuery; import co.elastic.clients.elasticsearch.connector.UpdateIndexNameRequest; +import co.elastic.clients.elasticsearch.core.SearchResponse; import co.elastic.clients.elasticsearch.core.rank_eval.RankEvalQuery; import co.elastic.clients.elasticsearch.core.search.SourceFilter; import co.elastic.clients.json.JsonData; @@ -42,6 +43,8 @@ import co.elastic.clients.util.MapBuilder; import org.junit.jupiter.api.Test; +import java.io.StringReader; + import static co.elastic.clients.elasticsearch._types.query_dsl.Query.Kind.MatchAll; public class BehaviorsTest extends ModelTestCase { @@ -346,4 +349,91 @@ public void testWithNull() { assertEquals(jsonValue,toJson(updateValue)); assertEquals(jsonNull,toJson(updateNull)); } + + @Test + public void testArrayToMapHitMatchedQueries() { + + // matched_queries has a special deserialization because it can be either an array or a map + String jsonValueArray = "{\n" + + " \"took\": 0,\n" + + " \"timed_out\": false,\n" + + " \"_shards\": {\n" + + " \"total\": 1,\n" + + " \"successful\": 1,\n" + + " \"skipped\": 0,\n" + + " \"failed\": 0\n" + + " },\n" + + " \"hits\": {\n" + + " \"total\": {\n" + + " \"value\": 1,\n" + + " \"relation\": \"eq\"\n" + + " },\n" + + " \"max_score\": 1,\n" + + " \"hits\": [\n" + + " {\n" + + " \"_index\": \"my-index\",\n" + + " \"_id\": \"N6V065UBHxF3EsAetzcl\",\n" + + " \"_score\": 1,\n" + + " \"_source\": {\n" + + " \"id\": \"park_rocky-mountain\",\n" + + " \"title\": \"Rocky Mountain\",\n" + + " \"description\": \"description\"\n" + + " },\n" + + " \"matched_queries\": [\n" + + " \"test\"\n" + + " ]\n" + + " }\n" + + " ]\n" + + " }\n" + + "}"; + + SearchResponse arrayResp = SearchResponse.of(s -> s.withJson(new StringReader(jsonValueArray))); + + String roundtripArray = arrayResp.toString(); + assertTrue(roundtripArray.contains("\"matched_queries\":[\"test\"]")); + + assertTrue(arrayResp.hits().hits().get(0).matchedQueries().containsKey("test")); + assertTrue(arrayResp.hits().hits().get(0).matchedQueries().get("test") == null); + + String jsonValueMap = "{\n" + + " \"took\": 1,\n" + + " \"timed_out\": false,\n" + + " \"_shards\": {\n" + + " \"total\": 1,\n" + + " \"successful\": 1,\n" + + " \"skipped\": 0,\n" + + " \"failed\": 0\n" + + " },\n" + + " \"hits\": {\n" + + " \"total\": {\n" + + " \"value\": 1,\n" + + " \"relation\": \"eq\"\n" + + " },\n" + + " \"max_score\": 1,\n" + + " \"hits\": [\n" + + " {\n" + + " \"_index\": \"my-index\",\n" + + " \"_id\": \"N6V065UBHxF3EsAetzcl\",\n" + + " \"_score\": 1,\n" + + " \"_source\": {\n" + + " \"id\": \"park_rocky-mountain\",\n" + + " \"title\": \"Rocky Mountain\",\n" + + " \"description\": \"description\"\n" + + " },\n" + + " \"matched_queries\": {\n" + + " \"test\": 1\n" + + " }\n" + + " }\n" + + " ]\n" + + " }\n" + + "}"; + + SearchResponse mapResp = SearchResponse.of(s -> s.withJson(new StringReader(jsonValueMap))); + + String roundtripMap = mapResp.toString(); + assertTrue(roundtripMap.contains("\"matched_queries\":{\"test\":1.0}")); + + assertTrue(mapResp.hits().hits().get(0).matchedQueries().containsKey("test")); + assertTrue(mapResp.hits().hits().get(0).matchedQueries().get("test").equals(1D)); + } }