From 922c7dd4a7d64d98715bd79a33ba2d7fa7846ae4 Mon Sep 17 00:00:00 2001 From: Peter-Josef Meisch Date: Sat, 26 Aug 2023 22:13:26 +0200 Subject: [PATCH] Add support for parameters for runtime fields. Original Pull Request #2677 Closes #2303 --- ...elasticsearch-migration-guide-5.1-5.2.adoc | 2 +- .../client/elc/RequestConverter.java | 35 +++++++++---- .../elasticsearch/client/elc/TypeUtils.java | 32 +++++++++++- .../core/query/RuntimeField.java | 25 ++++++++- ...iptedAndRuntimeFieldsIntegrationTests.java | 52 ++++++++++++++----- 5 files changed, 118 insertions(+), 28 deletions(-) diff --git a/src/main/asciidoc/reference/elasticsearch-migration-guide-5.1-5.2.adoc b/src/main/asciidoc/reference/elasticsearch-migration-guide-5.1-5.2.adoc index d0329ea7b..314239025 100644 --- a/src/main/asciidoc/reference/elasticsearch-migration-guide-5.1-5.2.adoc +++ b/src/main/asciidoc/reference/elasticsearch-migration-guide-5.1-5.2.adoc @@ -19,7 +19,7 @@ public record FailureDetails(Integer status, String errorMessage) { The classes `org.springframework.data.elasticsearch.core.RuntimeField` and `org.springframework.data.elasticsearch.core.query.ScriptType` have been moved to the subpackage `org.springframework.data.elasticsearch.core.query`. -The `type` parameter of the `ScriptData` constructir is not nullable any longer. +The `type` parameter of the `ScriptData` constructor is not nullable any longer. [[elasticsearch-migration-guide-5.1-5.2.deprecations]] == Deprecations diff --git a/src/main/java/org/springframework/data/elasticsearch/client/elc/RequestConverter.java b/src/main/java/org/springframework/data/elasticsearch/client/elc/RequestConverter.java index e1903eb18..9021f12bf 100644 --- a/src/main/java/org/springframework/data/elasticsearch/client/elc/RequestConverter.java +++ b/src/main/java/org/springframework/data/elasticsearch/client/elc/RequestConverter.java @@ -67,7 +67,6 @@ import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.data.domain.Sort; import org.springframework.data.elasticsearch.core.RefreshPolicy; -import org.springframework.data.elasticsearch.core.query.ScriptType; import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter; import org.springframework.data.elasticsearch.core.document.Document; import org.springframework.data.elasticsearch.core.index.*; @@ -1237,14 +1236,23 @@ public MsearchRequest searchMsearchRequest( Map runtimeMappings = new HashMap<>(); query.getRuntimeFields().forEach(runtimeField -> { RuntimeField esRuntimeField = RuntimeField.of(rt -> { - RuntimeField.Builder builder = rt + RuntimeField.Builder rfb = rt .type(RuntimeFieldType._DESERIALIZER.parse(runtimeField.getType())); String script = runtimeField.getScript(); if (script != null) { - builder = builder.script(s -> s.inline(is -> is.source(script))); + rfb + .script(s -> s + .inline(is -> { + is.source(script); + + if (runtimeField.getParams() != null) { + is.params(TypeUtils.paramsMap(runtimeField.getParams())); + } + return is; + })); } - return builder; + return rfb; }); runtimeMappings.put(runtimeField.getName(), esRuntimeField); }); @@ -1393,14 +1401,23 @@ private void prepareSearchRequest(Query query, @Nullable String routing, @Nu Map runtimeMappings = new HashMap<>(); query.getRuntimeFields() - .forEach(runtimeField -> runtimeMappings.put(runtimeField.getName(), RuntimeField.of(runtimeFieldBuilder -> { - runtimeFieldBuilder.type(RuntimeFieldType._DESERIALIZER.parse(runtimeField.getType())); + .forEach(runtimeField -> runtimeMappings.put(runtimeField.getName(), RuntimeField.of(rfb -> { + rfb.type(RuntimeFieldType._DESERIALIZER.parse(runtimeField.getType())); String script = runtimeField.getScript(); - if (script != null) { - runtimeFieldBuilder.script(s -> s.inline(is -> is.source(script))); + rfb + .script(s -> s + .inline(is -> { + is.source(script); + + if (runtimeField.getParams() != null) { + is.params(TypeUtils.paramsMap(runtimeField.getParams())); + } + return is; + })); } - return runtimeFieldBuilder; + + return rfb; }))); builder.runtimeMappings(runtimeMappings); } diff --git a/src/main/java/org/springframework/data/elasticsearch/client/elc/TypeUtils.java b/src/main/java/org/springframework/data/elasticsearch/client/elc/TypeUtils.java index fb9f5fe73..c3b4d73e5 100644 --- a/src/main/java/org/springframework/data/elasticsearch/client/elc/TypeUtils.java +++ b/src/main/java/org/springframework/data/elasticsearch/client/elc/TypeUtils.java @@ -18,12 +18,20 @@ import co.elastic.clients.elasticsearch._types.*; import co.elastic.clients.elasticsearch._types.mapping.FieldType; import co.elastic.clients.elasticsearch._types.mapping.TypeMapping; -import co.elastic.clients.elasticsearch.core.search.*; +import co.elastic.clients.elasticsearch.core.search.BoundaryScanner; +import co.elastic.clients.elasticsearch.core.search.HighlighterEncoder; +import co.elastic.clients.elasticsearch.core.search.HighlighterFragmenter; +import co.elastic.clients.elasticsearch.core.search.HighlighterOrder; +import co.elastic.clients.elasticsearch.core.search.HighlighterTagsSchema; +import co.elastic.clients.elasticsearch.core.search.HighlighterType; +import co.elastic.clients.elasticsearch.core.search.ScoreMode; import co.elastic.clients.elasticsearch.indices.IndexSettings; +import co.elastic.clients.json.JsonData; import java.io.StringReader; import java.time.Duration; import java.util.EnumSet; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -31,10 +39,16 @@ import org.springframework.data.domain.Sort; import org.springframework.data.elasticsearch.core.RefreshPolicy; import org.springframework.data.elasticsearch.core.document.Document; -import org.springframework.data.elasticsearch.core.query.*; +import org.springframework.data.elasticsearch.core.query.GeoDistanceOrder; +import org.springframework.data.elasticsearch.core.query.IndexQuery; import org.springframework.data.elasticsearch.core.query.IndicesOptions; +import org.springframework.data.elasticsearch.core.query.Order; +import org.springframework.data.elasticsearch.core.query.Query; +import org.springframework.data.elasticsearch.core.query.RescorerQuery; +import org.springframework.data.elasticsearch.core.query.UpdateResponse; import org.springframework.data.elasticsearch.core.reindex.ReindexRequest; import org.springframework.lang.Nullable; +import org.springframework.util.Assert; /** * Utility to handle new Elasticsearch client type values. @@ -438,4 +452,18 @@ static IndexSettings indexSettings(@Nullable Map settings) { return settings != null ? IndexSettings.of(b -> b.withJson(new StringReader(Document.from(settings).toJson()))) : null; } + + /** + * @since 5.2 + */ + static Map paramsMap(Map params) { + + Assert.notNull(params, "params must not be null"); + + Map mappedParams = new LinkedHashMap<>(); + params.forEach((key, value) -> { + mappedParams.put(key, JsonData.of(value)); + }); + return mappedParams; + } } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/query/RuntimeField.java b/src/main/java/org/springframework/data/elasticsearch/core/query/RuntimeField.java index 902a2120f..85616b840 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/query/RuntimeField.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/query/RuntimeField.java @@ -31,14 +31,26 @@ public class RuntimeField { private final String name; + /** + * the type of the runtime field (long, keyword, etc.) + */ private final String type; @Nullable private final String script; + /** + * @since 5.2 + */ + @Nullable Map params; + public RuntimeField(String name, String type) { - this(name, type, null); + this(name, type, null, null); } - public RuntimeField(String name, String type, @Nullable String script) { + public RuntimeField(String name, String type, String script) { + this(name, type, script, null); + } + + public RuntimeField(String name, String type, @Nullable String script, @Nullable Map params) { Assert.notNull(name, "name must not be null"); Assert.notNull(type, "type must not be null"); @@ -46,6 +58,7 @@ public RuntimeField(String name, String type, @Nullable String script) { this.name = name; this.type = type; this.script = script; + this.params = params; } public String getName() { @@ -78,4 +91,12 @@ public String getType() { public @Nullable String getScript() { return script; } + + /** + * @since 5.2 + */ + @Nullable + public Map getParams() { + return params; + } } diff --git a/src/test/java/org/springframework/data/elasticsearch/core/query/scriptedandruntimefields/ScriptedAndRuntimeFieldsIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/query/scriptedandruntimefields/ScriptedAndRuntimeFieldsIntegrationTests.java index dbd9a23ac..9493ea26c 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/query/scriptedandruntimefields/ScriptedAndRuntimeFieldsIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/query/scriptedandruntimefields/ScriptedAndRuntimeFieldsIntegrationTests.java @@ -18,6 +18,7 @@ import static org.assertj.core.api.Assertions.*; import java.time.LocalDate; +import java.util.List; import java.util.Map; import org.jetbrains.annotations.NotNull; @@ -251,6 +252,29 @@ private void insert(String id, String description, double price) { operations.save(entity); } + @Test // #2303 + @DisplayName("should use parameters for runtime fields in search queries") + void shouldUseParametersForRuntimeFieldsInSearchQueries() { + + insert("1", "item 1", 80.0); + insert("2", "item 2", 90.0); + + RuntimeField runtimeField = new RuntimeField( + "priceWithTax", + "double", + "emit(doc['price'].value * params.tax)", + Map.of("tax", 1.19) + ); + var query = CriteriaQuery.builder( + Criteria.where("priceWithTax").greaterThan(100.0)) + .withRuntimeFields(List.of(runtimeField)) + .build(); + + var searchHits = operations.search(query, SomethingToBuy.class); + + assertThat(searchHits).hasSize(1); + } + @SuppressWarnings("unused") @Document(indexName = "#{@indexNameProvider.indexName()}-something-to-by") private static class SomethingToBuy { @@ -386,13 +410,13 @@ SearchHits findByValue(Integer value, @org.springframework.data.elasticsearch.annotations.Query(""" { - "term": { - "value": { - "value": "?0" - } - } - } - """) + "term": { + "value": { + "value": "?0" + } + } + } + """) SearchHits findWithScriptedFields(Integer value, org.springframework.data.elasticsearch.core.query.ScriptedField scriptedField1, org.springframework.data.elasticsearch.core.query.ScriptedField scriptedField2); @@ -401,13 +425,13 @@ SearchHits findWithScriptedFields(Integer value, @org.springframework.data.elasticsearch.annotations.Query(""" { - "term": { - "value": { - "value": "?0" - } - } - } - """) + "term": { + "value": { + "value": "?0" + } + } + } + """) SearchHits findWithRuntimeFields(Integer value, RuntimeField runtimeField1, RuntimeField runtimeField2); } }