Add support for parameters for runtime fields.

Original Pull Request #2677
Closes #2303
This commit is contained in:
Peter-Josef Meisch 2023-08-26 22:13:26 +02:00 committed by GitHub
parent ed898431ab
commit 922c7dd4a7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 118 additions and 28 deletions

View File

@ -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 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]] [[elasticsearch-migration-guide-5.1-5.2.deprecations]]
== Deprecations == Deprecations

View File

@ -67,7 +67,6 @@ import org.jetbrains.annotations.NotNull;
import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort;
import org.springframework.data.elasticsearch.core.RefreshPolicy; 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.convert.ElasticsearchConverter;
import org.springframework.data.elasticsearch.core.document.Document; import org.springframework.data.elasticsearch.core.document.Document;
import org.springframework.data.elasticsearch.core.index.*; import org.springframework.data.elasticsearch.core.index.*;
@ -1237,14 +1236,23 @@ class RequestConverter {
Map<String, RuntimeField> runtimeMappings = new HashMap<>(); Map<String, RuntimeField> runtimeMappings = new HashMap<>();
query.getRuntimeFields().forEach(runtimeField -> { query.getRuntimeFields().forEach(runtimeField -> {
RuntimeField esRuntimeField = RuntimeField.of(rt -> { RuntimeField esRuntimeField = RuntimeField.of(rt -> {
RuntimeField.Builder builder = rt RuntimeField.Builder rfb = rt
.type(RuntimeFieldType._DESERIALIZER.parse(runtimeField.getType())); .type(RuntimeFieldType._DESERIALIZER.parse(runtimeField.getType()));
String script = runtimeField.getScript(); String script = runtimeField.getScript();
if (script != null) { 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); runtimeMappings.put(runtimeField.getName(), esRuntimeField);
}); });
@ -1393,14 +1401,23 @@ class RequestConverter {
Map<String, RuntimeField> runtimeMappings = new HashMap<>(); Map<String, RuntimeField> runtimeMappings = new HashMap<>();
query.getRuntimeFields() query.getRuntimeFields()
.forEach(runtimeField -> runtimeMappings.put(runtimeField.getName(), RuntimeField.of(runtimeFieldBuilder -> { .forEach(runtimeField -> runtimeMappings.put(runtimeField.getName(), RuntimeField.of(rfb -> {
runtimeFieldBuilder.type(RuntimeFieldType._DESERIALIZER.parse(runtimeField.getType())); rfb.type(RuntimeFieldType._DESERIALIZER.parse(runtimeField.getType()));
String script = runtimeField.getScript(); String script = runtimeField.getScript();
if (script != null) { 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); builder.runtimeMappings(runtimeMappings);
} }

View File

@ -18,12 +18,20 @@ package org.springframework.data.elasticsearch.client.elc;
import co.elastic.clients.elasticsearch._types.*; import co.elastic.clients.elasticsearch._types.*;
import co.elastic.clients.elasticsearch._types.mapping.FieldType; import co.elastic.clients.elasticsearch._types.mapping.FieldType;
import co.elastic.clients.elasticsearch._types.mapping.TypeMapping; 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.elasticsearch.indices.IndexSettings;
import co.elastic.clients.json.JsonData;
import java.io.StringReader; import java.io.StringReader;
import java.time.Duration; import java.time.Duration;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -31,10 +39,16 @@ import java.util.stream.Collectors;
import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort;
import org.springframework.data.elasticsearch.core.RefreshPolicy; import org.springframework.data.elasticsearch.core.RefreshPolicy;
import org.springframework.data.elasticsearch.core.document.Document; 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.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.data.elasticsearch.core.reindex.ReindexRequest;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/** /**
* Utility to handle new Elasticsearch client type values. * Utility to handle new Elasticsearch client type values.
@ -438,4 +452,18 @@ final class TypeUtils {
return settings != null ? IndexSettings.of(b -> b.withJson(new StringReader(Document.from(settings).toJson()))) return settings != null ? IndexSettings.of(b -> b.withJson(new StringReader(Document.from(settings).toJson())))
: null; : null;
} }
/**
* @since 5.2
*/
static Map<String, JsonData> paramsMap(Map<String, Object> params) {
Assert.notNull(params, "params must not be null");
Map<String, JsonData> mappedParams = new LinkedHashMap<>();
params.forEach((key, value) -> {
mappedParams.put(key, JsonData.of(value));
});
return mappedParams;
}
} }

View File

@ -31,14 +31,26 @@ import org.springframework.util.Assert;
public class RuntimeField { public class RuntimeField {
private final String name; private final String name;
/**
* the type of the runtime field (long, keyword, etc.)
*/
private final String type; private final String type;
@Nullable private final String script; @Nullable private final String script;
/**
* @since 5.2
*/
@Nullable Map<String, Object> params;
public RuntimeField(String name, String type) { 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<String, Object> params) {
Assert.notNull(name, "name must not be null"); Assert.notNull(name, "name must not be null");
Assert.notNull(type, "type must not be null"); Assert.notNull(type, "type must not be null");
@ -46,6 +58,7 @@ public class RuntimeField {
this.name = name; this.name = name;
this.type = type; this.type = type;
this.script = script; this.script = script;
this.params = params;
} }
public String getName() { public String getName() {
@ -78,4 +91,12 @@ public class RuntimeField {
public @Nullable String getScript() { public @Nullable String getScript() {
return script; return script;
} }
/**
* @since 5.2
*/
@Nullable
public Map<String, Object> getParams() {
return params;
}
} }

View File

@ -18,6 +18,7 @@ package org.springframework.data.elasticsearch.core.query.scriptedandruntimefiel
import static org.assertj.core.api.Assertions.*; import static org.assertj.core.api.Assertions.*;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.List;
import java.util.Map; import java.util.Map;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@ -251,6 +252,29 @@ public abstract class ScriptedAndRuntimeFieldsIntegrationTests {
operations.save(entity); 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") @SuppressWarnings("unused")
@Document(indexName = "#{@indexNameProvider.indexName()}-something-to-by") @Document(indexName = "#{@indexNameProvider.indexName()}-something-to-by")
private static class SomethingToBuy { private static class SomethingToBuy {
@ -386,13 +410,13 @@ public abstract class ScriptedAndRuntimeFieldsIntegrationTests {
@org.springframework.data.elasticsearch.annotations.Query(""" @org.springframework.data.elasticsearch.annotations.Query("""
{ {
"term": { "term": {
"value": { "value": {
"value": "?0" "value": "?0"
} }
} }
} }
""") """)
SearchHits<SAREntity> findWithScriptedFields(Integer value, SearchHits<SAREntity> findWithScriptedFields(Integer value,
org.springframework.data.elasticsearch.core.query.ScriptedField scriptedField1, org.springframework.data.elasticsearch.core.query.ScriptedField scriptedField1,
org.springframework.data.elasticsearch.core.query.ScriptedField scriptedField2); org.springframework.data.elasticsearch.core.query.ScriptedField scriptedField2);
@ -401,13 +425,13 @@ public abstract class ScriptedAndRuntimeFieldsIntegrationTests {
@org.springframework.data.elasticsearch.annotations.Query(""" @org.springframework.data.elasticsearch.annotations.Query("""
{ {
"term": { "term": {
"value": { "value": {
"value": "?0" "value": "?0"
} }
} }
} }
""") """)
SearchHits<SAREntity> findWithRuntimeFields(Integer value, RuntimeField runtimeField1, RuntimeField runtimeField2); SearchHits<SAREntity> findWithRuntimeFields(Integer value, RuntimeField runtimeField1, RuntimeField runtimeField2);
} }
} }