mirror of
https://github.com/spring-projects/spring-data-elasticsearch.git
synced 2025-06-29 15:22:11 +00:00
Scripted and runtime fields improvements.
Original Pull Request #2663 Closes #2035
This commit is contained in:
parent
82ae11833a
commit
1fb034a9a8
@ -6,6 +6,7 @@ This section describes breaking changes from version 5.1.x to 5.2.x and how remo
|
||||
[[elasticsearch-migration-guide-5.1-5.2.breaking-changes]]
|
||||
== Breaking Changes
|
||||
|
||||
=== Bulk failures
|
||||
In the `org.springframework.data.elasticsearch.BulkFailureException` class, the return type of the `getFailedDocuments` is changed from `Map<String, String>`
|
||||
to `Map<String, FailureDetails>`, which allows to get additional details about failure reasons.
|
||||
|
||||
@ -14,6 +15,12 @@ The definition of the `FailureDetails` class (inner to `BulkFailureException`):
|
||||
public record FailureDetails(Integer status, String errorMessage) {
|
||||
}
|
||||
|
||||
=== scripted and runtime fields
|
||||
|
||||
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.
|
||||
|
||||
[[elasticsearch-migration-guide-5.1-5.2.deprecations]]
|
||||
== Deprecations
|
||||
|
||||
|
@ -0,0 +1,223 @@
|
||||
[[elasticsearch.misc.scripted-and-runtime-fields]]
|
||||
= Scripted and runtime fields
|
||||
|
||||
Spring Data Elasticsearch supports scripted fields and runtime fields.
|
||||
Please refer to the Elasticsearch documentation about scripting (https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-scripting.html) and runtime fields (https://www.elastic.co/guide/en/elasticsearch/reference/8.9/runtime.html) for detailed information about this.
|
||||
In the context of Spring Data Elasticsearch you can use
|
||||
|
||||
* scripted fields that are used to return fields that are calculated on the result documents and added to the returned document.
|
||||
* runtime fields that are calculated on the stored documents and can be used in a query and/or be returned in the search result.
|
||||
|
||||
The following code snippets will show what you can do (this show imperative code, but the reactive implementation works similar).
|
||||
|
||||
== The person entity
|
||||
|
||||
The enity that is used in these examples is a `Person` entity.
|
||||
This entity has a `birthDate` and an `age` property.
|
||||
Whereas the birthdate is fix, the age depends on the time when a query is issued and needs to be calculated dynamically.
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.elasticsearch.annotations.DateFormat;
|
||||
import org.springframework.data.elasticsearch.annotations.Document;
|
||||
import org.springframework.data.elasticsearch.annotations.Field;
|
||||
import org.springframework.data.elasticsearch.annotations.ScriptedField;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
import static org.springframework.data.elasticsearch.annotations.FieldType.*;
|
||||
|
||||
import java.lang.Integer;
|
||||
|
||||
@Document(indexName = "persons")
|
||||
public record Person(
|
||||
@Id
|
||||
@Nullable
|
||||
String id,
|
||||
@Field(type = Text)
|
||||
String lastName,
|
||||
@Field(type = Text)
|
||||
String firstName,
|
||||
@Field(type = Keyword)
|
||||
String gender,
|
||||
@Field(type = Date, format = DateFormat.basic_date)
|
||||
LocalDate birthDate,
|
||||
@Nullable
|
||||
@ScriptedField Integer age <.>
|
||||
) {
|
||||
public Person(String id,String lastName, String firstName, String gender, String birthDate) {
|
||||
this(id, <.>
|
||||
lastName,
|
||||
firstName,
|
||||
LocalDate.parse(birthDate, DateTimeFormatter.ISO_LOCAL_DATE),
|
||||
gender,
|
||||
null);
|
||||
}
|
||||
}
|
||||
|
||||
----
|
||||
|
||||
<.> the `age` property will be calculated and filled in search results.
|
||||
<.> a convenience constructor to set up the test data
|
||||
====
|
||||
|
||||
Note that the `age` property is annotated with `@ScriptedField`.
|
||||
This inhibits the writing of a corresponding entry in the index mapping and marks the property as a target to put a calculated field from a search response.
|
||||
|
||||
== The repository interface
|
||||
|
||||
The repository used in this example:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
public interface PersonRepository extends ElasticsearchRepository<Person, String> {
|
||||
|
||||
SearchHits<Person> findAllBy(ScriptedField scriptedField);
|
||||
|
||||
SearchHits<Person> findByGenderAndAgeLessThanEqual(String gender, Integer age, RuntimeField runtimeField);
|
||||
}
|
||||
|
||||
----
|
||||
====
|
||||
|
||||
== The service class
|
||||
|
||||
The service class has a repository injected and an `ElasticsearchOperations` instance to show several ways of poplauting and using the `age` property.
|
||||
We show the code split up in different pieces to put the explanations in
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
|
||||
import org.springframework.data.elasticsearch.core.SearchHits;
|
||||
import org.springframework.data.elasticsearch.core.query.Criteria;
|
||||
import org.springframework.data.elasticsearch.core.query.CriteriaQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
|
||||
import org.springframework.data.elasticsearch.core.query.RuntimeField;
|
||||
import org.springframework.data.elasticsearch.core.query.ScriptData;
|
||||
import org.springframework.data.elasticsearch.core.query.ScriptType;
|
||||
import org.springframework.data.elasticsearch.core.query.ScriptedField;
|
||||
import org.springframework.data.elasticsearch.core.query.StringQuery;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class PersonService {
|
||||
private final ElasticsearchOperations operations;
|
||||
private final PersonRepository repository;
|
||||
|
||||
public PersonService(ElasticsearchOperations operations, SaRPersonRepository repository) {
|
||||
this.operations = operations;
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
public void save() { <.>
|
||||
List<Person> persons = List.of(
|
||||
new Person("1", "Smith", "Mary", "f", "1987-05-03"),
|
||||
new Person("2", "Smith", "Joshua", "m", "1982-11-17"),
|
||||
new Person("3", "Smith", "Joanna", "f", "2018-03-27"),
|
||||
new Person("4", "Smith", "Alex", "m", "2020-08-01"),
|
||||
new Person("5", "McNeill", "Fiona", "f", "1989-04-07"),
|
||||
new Person("6", "McNeill", "Michael", "m", "1984-10-20"),
|
||||
new Person("7", "McNeill", "Geraldine", "f", "2020-03-02"),
|
||||
new Person("8", "McNeill", "Patrick", "m", "2022-07-04"));
|
||||
|
||||
repository.saveAll(persons);
|
||||
}
|
||||
----
|
||||
|
||||
<.> a utility method to store some data in Elasticsearch.
|
||||
====
|
||||
|
||||
=== Scripted fields
|
||||
|
||||
The next piece show how to use a scripted field to calculate and return the age of the persons.
|
||||
Scripted fields can only add something to the returned data, the age cannot be used in the query (see runtime fields for that).)
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
public SearchHits<Person> findAllWithAge() {
|
||||
|
||||
var scriptedField = ScriptedField.of("age", <.>
|
||||
ScriptData.of(b -> b
|
||||
.withType(ScriptType.INLINE)
|
||||
.withScript("""
|
||||
Instant currentDate = Instant.ofEpochMilli(new Date().getTime());
|
||||
Instant startDate = doc['birth-date'].value.toInstant();
|
||||
return (ChronoUnit.DAYS.between(startDate, currentDate) / 365);
|
||||
""")));
|
||||
|
||||
// version 1: use a direct query
|
||||
var query = new StringQuery("""
|
||||
{ "match_all": {} }
|
||||
""");
|
||||
query.addScriptedField(scriptedField); <.>
|
||||
query.addSourceFilter(FetchSourceFilter.of(b -> b.withIncludes("*"))); <.>
|
||||
|
||||
var result1 = operations.search(query, Person.class); <.>
|
||||
|
||||
// version 2: use the repository
|
||||
var result2 = repository.findAllBy(scriptedField); <.>
|
||||
|
||||
return result1;
|
||||
}
|
||||
----
|
||||
|
||||
<.> define the `ScriptedField` that calculates the age of a person.
|
||||
<.> when using a `Query`, add the scripted field to the query.
|
||||
<.> when adding a scripted field to a `Query`, an additional source filter is needed to also retrieve the _normal_ fields from the document source.
|
||||
<.> get the data where the `Person` entities now have the values set in their `age` property.
|
||||
<.> when using the repository, all that needs to be done is adding the scripted field as method parameter.
|
||||
====
|
||||
|
||||
=== Runtime fields
|
||||
|
||||
When using runtime fields, the calculated value can be used in the query itself.
|
||||
In the following code this is used to run a query for a given gender and maximum age of persons:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
public SearchHits<Person> findWithGenderAndMaxAge(String gender, Integer maxAge) {
|
||||
|
||||
var runtimeField = new RuntimeField("age", "long", """ <.>
|
||||
Instant currentDate = Instant.ofEpochMilli(new Date().getTime());
|
||||
Instant startDate = doc['birth-date'].value.toInstant();
|
||||
emit (ChronoUnit.DAYS.between(startDate, currentDate) / 365);
|
||||
""");
|
||||
|
||||
// variant 1 : use a direct query
|
||||
var query = CriteriaQuery.builder(Criteria
|
||||
.where("gender").is(gender)
|
||||
.and("age").lessThanEqual(maxAge))
|
||||
.withRuntimeFields(List.of(runtimeField)) <.>
|
||||
.withFields("age") <.>
|
||||
.withSourceFilter(FetchSourceFilter.of(b -> b.withIncludes("*"))) <.>
|
||||
.build();
|
||||
|
||||
var result1 = operations.search(query, Person.class); <.>
|
||||
|
||||
// variant 2: use the repository <.>
|
||||
var result2 = repository.findByGenderAndAgeLessThanEqual(gender, maxAge, runtimeField);
|
||||
|
||||
return result1;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
<.> define the runtime field that caclulates the // see https://asciidoctor.org/docs/user-manual/#builtin-attributes for builtin attributes.
|
||||
<.> when using `Query`, add the runtime field.
|
||||
<.> when adding a scripted field to a `Query`, an additional field parameter is needed to have the calculated value returned.
|
||||
<.> when adding a scripted field to a `Query`, an additional source filter is needed to also retrieve the _normal_ fields from the document source.
|
||||
<.> get the data filtered with the query and where the returned entites have the age property set.
|
||||
<.> when using the repository, all that needs to be done is adding the runtime field as method parameter.
|
||||
====
|
||||
|
||||
In addition to define a runtime fields on a query, they can also be defined in the index by setting the `runtimeFIeldPath` property of the `@Mapping` annotation to point to a JSON file that contains the runtime field definitions.
|
@ -450,3 +450,5 @@ var query = Query.findAll().addSort(Sort.by(order));
|
||||
About the filter query: It is not possible to use a `CriteriaQuery` here, as this query would be converted into a Elasticsearch nested query which does not work in the filter context. So only `StringQuery` or `NativeQuery` can be used here. When using one of these, like the term query above, the Elasticsearch field names must be used, so take care, when these are redefined with the `@Field(name="...")` definition.
|
||||
|
||||
For the definition of the order path and the nested paths, the Java entity property names should be used.
|
||||
|
||||
include::elasticsearch-misc-scripted-and-runtime-fields.adoc[leveloffset=+1]
|
||||
|
@ -3,6 +3,7 @@ package org.springframework.data.elasticsearch.annotations;
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* Marks a property to be populated with the result of a scripted field retrieved from an Elasticsearch response.
|
||||
* @author Ryan Murfitt
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
|
@ -67,7 +67,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.elasticsearch.core.RefreshPolicy;
|
||||
import org.springframework.data.elasticsearch.core.ScriptType;
|
||||
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.*;
|
||||
@ -1333,10 +1333,8 @@ class RequestConverter {
|
||||
}
|
||||
|
||||
if (!isEmpty(query.getFields())) {
|
||||
builder.fields(fb -> {
|
||||
query.getFields().forEach(fb::field);
|
||||
return fb;
|
||||
});
|
||||
var fieldAndFormats = query.getFields().stream().map(field -> FieldAndFormat.of(b -> b.field(field))).toList();
|
||||
builder.fields(fieldAndFormats);
|
||||
}
|
||||
|
||||
if (!isEmpty(query.getStoredFields())) {
|
||||
|
@ -29,7 +29,6 @@ import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.elasticsearch.core.RuntimeField;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@ -548,6 +547,16 @@ public class BaseQuery implements Query {
|
||||
this.docValueFields = docValueFields;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 5.2
|
||||
*/
|
||||
public void addScriptedField(ScriptedField scriptedField) {
|
||||
|
||||
Assert.notNull(scriptedField, "scriptedField must not be null");
|
||||
|
||||
this.scriptedFields.add(scriptedField);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ScriptedField> getScriptedFields() {
|
||||
return scriptedFields;
|
||||
|
@ -25,7 +25,6 @@ import java.util.List;
|
||||
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.elasticsearch.core.RuntimeField;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
|
@ -15,7 +15,10 @@
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core.query;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* SourceFilter implementation for providing includes and excludes.
|
||||
@ -28,6 +31,23 @@ public class FetchSourceFilter implements SourceFilter {
|
||||
@Nullable private final String[] includes;
|
||||
@Nullable private final String[] excludes;
|
||||
|
||||
/**
|
||||
* @since 5.2
|
||||
*/
|
||||
public static SourceFilter of(@Nullable final String[] includes, @Nullable final String[] excludes) {
|
||||
return new FetchSourceFilter(includes, excludes);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 5.2
|
||||
*/
|
||||
public static SourceFilter of(Function<FetchSourceFilterBuilder, FetchSourceFilterBuilder> builderFunction) {
|
||||
|
||||
Assert.notNull(builderFunction, "builderFunction must not be null");
|
||||
|
||||
return builderFunction.apply(new FetchSourceFilterBuilder()).build();
|
||||
}
|
||||
|
||||
public FetchSourceFilter(@Nullable final String[] includes, @Nullable final String[] excludes) {
|
||||
this.includes = includes;
|
||||
this.excludes = excludes;
|
||||
|
@ -25,7 +25,6 @@ import java.util.Optional;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.elasticsearch.core.RuntimeField;
|
||||
import org.springframework.data.elasticsearch.core.SearchHit;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core;
|
||||
package org.springframework.data.elasticsearch.core.query;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
@ -16,9 +16,10 @@
|
||||
package org.springframework.data.elasticsearch.core.query;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.data.elasticsearch.core.ScriptType;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* value class combining script information.
|
||||
@ -26,6 +27,88 @@ import org.springframework.lang.Nullable;
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 4.4
|
||||
*/
|
||||
public record ScriptData(@Nullable ScriptType type, @Nullable String language, @Nullable String script,
|
||||
public record ScriptData(ScriptType type, @Nullable String language, @Nullable String script,
|
||||
@Nullable String scriptName, @Nullable Map<String, Object> params) {
|
||||
|
||||
public ScriptData(ScriptType type, @Nullable String language, @Nullable String script, @Nullable String scriptName,
|
||||
@Nullable Map<String, Object> params) {
|
||||
|
||||
Assert.notNull(type, "type must not be null");
|
||||
|
||||
this.type = type;
|
||||
this.language = language;
|
||||
this.script = script;
|
||||
this.scriptName = scriptName;
|
||||
this.params = params;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 5.2
|
||||
*/
|
||||
public static ScriptData of(ScriptType type, @Nullable String language, @Nullable String script,
|
||||
@Nullable String scriptName, @Nullable Map<String, Object> params) {
|
||||
return new ScriptData(type, language, script, scriptName, params);
|
||||
}
|
||||
|
||||
public static ScriptData of(Function<Builder, Builder> builderFunction) {
|
||||
|
||||
Assert.notNull(builderFunction, "f must not be null");
|
||||
|
||||
return builderFunction.apply(new Builder()).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 5.2
|
||||
*/
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 5.2
|
||||
*/
|
||||
public static final class Builder {
|
||||
@Nullable private ScriptType type;
|
||||
@Nullable private String language;
|
||||
@Nullable private String script;
|
||||
@Nullable private String scriptName;
|
||||
@Nullable private Map<String, Object> params;
|
||||
|
||||
private Builder() {}
|
||||
|
||||
public Builder withType(ScriptType type) {
|
||||
|
||||
Assert.notNull(type, "type must not be null");
|
||||
|
||||
this.type = type;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withLanguage(@Nullable String language) {
|
||||
this.language = language;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withScript(@Nullable String script) {
|
||||
this.script = script;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withScriptName(@Nullable String scriptName) {
|
||||
this.scriptName = scriptName;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withParams(@Nullable Map<String, Object> params) {
|
||||
this.params = params;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ScriptData build() {
|
||||
|
||||
Assert.notNull(type, "type must be set");
|
||||
|
||||
return new ScriptData(type, language, script, scriptName, params);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core;
|
||||
package org.springframework.data.elasticsearch.core.query;
|
||||
|
||||
/**
|
||||
* Define script types for update queries.
|
@ -18,6 +18,8 @@ package org.springframework.data.elasticsearch.core.query;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Class defining a scripted field to be used in a {@link Query}. Must be set by using the builder for a query.
|
||||
*
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 4.4
|
||||
*/
|
||||
@ -26,6 +28,13 @@ public class ScriptedField {
|
||||
private final String fieldName;
|
||||
private final ScriptData scriptData;
|
||||
|
||||
/**
|
||||
* @since 5.2
|
||||
*/
|
||||
public static ScriptedField of(String fieldName, ScriptData scriptData) {
|
||||
return new ScriptedField(fieldName, scriptData);
|
||||
}
|
||||
|
||||
public ScriptedField(String fieldName, ScriptData scriptData) {
|
||||
|
||||
Assert.notNull(fieldName, "fieldName must not be null");
|
||||
|
@ -19,7 +19,6 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.data.elasticsearch.core.RefreshPolicy;
|
||||
import org.springframework.data.elasticsearch.core.ScriptType;
|
||||
import org.springframework.data.elasticsearch.core.document.Document;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
|
@ -25,6 +25,7 @@ import org.springframework.data.elasticsearch.core.SearchHitsImpl;
|
||||
import org.springframework.data.elasticsearch.core.TotalHitsRelation;
|
||||
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
|
||||
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||
import org.springframework.data.elasticsearch.core.query.BaseQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.Query;
|
||||
import org.springframework.data.repository.query.ParametersParameterAccessor;
|
||||
import org.springframework.data.repository.query.QueryMethod;
|
||||
@ -75,7 +76,7 @@ public abstract class AbstractElasticsearchRepositoryQuery implements Repository
|
||||
@Override
|
||||
public Object execute(Object[] parameters) {
|
||||
|
||||
ParametersParameterAccessor parameterAccessor = getParameterAccessor(parameters);
|
||||
ElasticsearchParametersParameterAccessor parameterAccessor = getParameterAccessor(parameters);
|
||||
ResultProcessor resultProcessor = queryMethod.getResultProcessor().withDynamicProjection(parameterAccessor);
|
||||
Class<?> clazz = resultProcessor.getReturnedType().getDomainType();
|
||||
|
||||
@ -135,29 +136,19 @@ public abstract class AbstractElasticsearchRepositoryQuery implements Repository
|
||||
|
||||
public Query createQuery(Object[] parameters) {
|
||||
|
||||
ParametersParameterAccessor parameterAccessor = getParameterAccessor(parameters);
|
||||
ElasticsearchParametersParameterAccessor parameterAccessor = getParameterAccessor(parameters);
|
||||
ResultProcessor resultProcessor = queryMethod.getResultProcessor().withDynamicProjection(parameterAccessor);
|
||||
Class<?> returnedType = resultProcessor.getReturnedType().getDomainType();
|
||||
|
||||
Query query = createQuery(parameterAccessor);
|
||||
|
||||
var query = createQuery(parameterAccessor);
|
||||
Assert.notNull(query, "unsupported query");
|
||||
|
||||
if (queryMethod.hasAnnotatedHighlight()) {
|
||||
query.setHighlightQuery(queryMethod.getAnnotatedHighlightQuery());
|
||||
}
|
||||
|
||||
var sourceFilter = queryMethod.getSourceFilter(parameterAccessor,
|
||||
elasticsearchOperations.getElasticsearchConverter());
|
||||
if (sourceFilter != null) {
|
||||
query.addSourceFilter(sourceFilter);
|
||||
}
|
||||
queryMethod.addMethodParameter(query, parameterAccessor, elasticsearchOperations.getElasticsearchConverter());
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
private ParametersParameterAccessor getParameterAccessor(Object[] parameters) {
|
||||
return new ParametersParameterAccessor(queryMethod.getParameters(), parameters);
|
||||
private ElasticsearchParametersParameterAccessor getParameterAccessor(Object[] parameters) {
|
||||
return new ElasticsearchParametersParameterAccessor(queryMethod, parameters);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@ -185,5 +176,5 @@ public abstract class AbstractElasticsearchRepositoryQuery implements Repository
|
||||
return result;
|
||||
}
|
||||
|
||||
protected abstract Query createQuery(ParametersParameterAccessor accessor);
|
||||
protected abstract BaseQuery createQuery(ElasticsearchParametersParameterAccessor accessor);
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ import org.springframework.data.elasticsearch.core.SearchHitSupport;
|
||||
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
|
||||
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
|
||||
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||
import org.springframework.data.elasticsearch.core.query.BaseQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.ByQueryResponse;
|
||||
import org.springframework.data.elasticsearch.core.query.Query;
|
||||
import org.springframework.data.elasticsearch.repository.query.ReactiveElasticsearchQueryExecution.ResultProcessingConverter;
|
||||
@ -35,6 +36,7 @@ import org.springframework.data.repository.query.ParameterAccessor;
|
||||
import org.springframework.data.repository.query.QueryMethod;
|
||||
import org.springframework.data.repository.query.RepositoryQuery;
|
||||
import org.springframework.data.repository.query.ResultProcessor;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* AbstractElasticsearchRepositoryQuery
|
||||
@ -79,7 +81,7 @@ abstract class AbstractReactiveElasticsearchRepositoryQuery implements Repositor
|
||||
return Mono.defer(() -> (Mono<Object>) execute(parameterAccessor));
|
||||
}
|
||||
|
||||
private Object execute(ElasticsearchParameterAccessor parameterAccessor) {
|
||||
private Object execute(ElasticsearchParametersParameterAccessor parameterAccessor) {
|
||||
|
||||
ResultProcessor processor = queryMethod.getResultProcessor().withDynamicProjection(parameterAccessor);
|
||||
var returnedType = processor.getReturnedType();
|
||||
@ -90,17 +92,10 @@ abstract class AbstractReactiveElasticsearchRepositoryQuery implements Repositor
|
||||
typeToRead = queryMethod.unwrappedReturnType;
|
||||
}
|
||||
|
||||
Query query = createQuery(parameterAccessor);
|
||||
var query = createQuery(parameterAccessor);
|
||||
Assert.notNull(query, "unsupported query");
|
||||
|
||||
if (queryMethod.hasAnnotatedHighlight()) {
|
||||
query.setHighlightQuery(queryMethod.getAnnotatedHighlightQuery());
|
||||
}
|
||||
|
||||
var sourceFilter = queryMethod.getSourceFilter(parameterAccessor,
|
||||
elasticsearchOperations.getElasticsearchConverter());
|
||||
if (sourceFilter != null) {
|
||||
query.addSourceFilter(sourceFilter);
|
||||
}
|
||||
queryMethod.addMethodParameter(query, parameterAccessor, elasticsearchOperations.getElasticsearchConverter());
|
||||
|
||||
String indexName = queryMethod.getEntityInformation().getIndexName();
|
||||
IndexCoordinates index = IndexCoordinates.of(indexName);
|
||||
@ -111,18 +106,18 @@ abstract class AbstractReactiveElasticsearchRepositoryQuery implements Repositor
|
||||
return execution.execute(query, domainType, typeToRead, index);
|
||||
}
|
||||
|
||||
private ReactiveElasticsearchQueryExecution getExecution(ElasticsearchParameterAccessor accessor,
|
||||
Converter<Object, Object> resultProcessing) {
|
||||
return new ResultProcessingExecution(getExecutionToWrap(accessor, elasticsearchOperations), resultProcessing);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link Query} instance using the given {@link ParameterAccessor}
|
||||
*
|
||||
* @param accessor must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
protected abstract Query createQuery(ElasticsearchParameterAccessor accessor);
|
||||
protected abstract BaseQuery createQuery(ElasticsearchParameterAccessor accessor);
|
||||
|
||||
private ReactiveElasticsearchQueryExecution getExecution(ElasticsearchParameterAccessor accessor,
|
||||
Converter<Object, Object> resultProcessing) {
|
||||
return new ResultProcessingExecution(getExecutionToWrap(accessor, elasticsearchOperations), resultProcessing);
|
||||
}
|
||||
|
||||
private ReactiveElasticsearchQueryExecution getExecutionToWrap(ElasticsearchParameterAccessor accessor,
|
||||
ReactiveElasticsearchOperations operations) {
|
||||
|
@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2019-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.repository.query;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.data.elasticsearch.core.query.RuntimeField;
|
||||
import org.springframework.data.elasticsearch.core.query.ScriptedField;
|
||||
import org.springframework.data.repository.query.Parameter;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
|
||||
/**
|
||||
* Custom {@link Parameter} implementation adding specific types to the special ones. Refactored from being defined in
|
||||
* {@link ElasticsearchParameters}.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 5.2
|
||||
*/
|
||||
class ElasticsearchParameter extends Parameter {
|
||||
|
||||
/**
|
||||
* Creates a new {@link ElasticsearchParameter}.
|
||||
*
|
||||
* @param parameter must not be {@literal null}.
|
||||
*/
|
||||
ElasticsearchParameter(MethodParameter parameter, TypeInformation<?> domainType) {
|
||||
super(parameter, domainType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSpecialParameter() {
|
||||
return super.isSpecialParameter() || isScriptedFieldParameter() || isRuntimeFieldParameter();
|
||||
}
|
||||
|
||||
public Boolean isScriptedFieldParameter() {
|
||||
return ScriptedField.class.isAssignableFrom(getType());
|
||||
}
|
||||
|
||||
public Boolean isRuntimeFieldParameter() {
|
||||
return RuntimeField.class.isAssignableFrom(getType());
|
||||
}
|
||||
}
|
@ -16,13 +16,12 @@
|
||||
package org.springframework.data.elasticsearch.repository.query;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.data.elasticsearch.repository.query.ElasticsearchParameters.ElasticsearchParameter;
|
||||
import org.springframework.data.geo.Distance;
|
||||
import org.springframework.data.repository.query.Parameter;
|
||||
import org.springframework.data.repository.query.Parameters;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
@ -31,39 +30,47 @@ import org.springframework.data.repository.query.Parameters;
|
||||
*/
|
||||
public class ElasticsearchParameters extends Parameters<ElasticsearchParameters, ElasticsearchParameter> {
|
||||
|
||||
public ElasticsearchParameters(Method method) {
|
||||
super(method);
|
||||
private final List<ElasticsearchParameter> scriptedFields = new ArrayList<>();
|
||||
private final List<ElasticsearchParameter> runtimeFields = new ArrayList<>();
|
||||
|
||||
public ElasticsearchParameters(Method method, TypeInformation<?> domainType) {
|
||||
|
||||
super(method, parameter -> new ElasticsearchParameter(parameter, domainType));
|
||||
|
||||
int parameterCount = method.getParameterCount();
|
||||
for (int i = 0; i < parameterCount; i++) {
|
||||
MethodParameter methodParameter = new MethodParameter(method, i);
|
||||
var parameter = parameterFactory(methodParameter, domainType);
|
||||
|
||||
if (parameter.isScriptedFieldParameter()) {
|
||||
scriptedFields.add(parameter);
|
||||
}
|
||||
|
||||
if (parameter.isRuntimeFieldParameter()) {
|
||||
runtimeFields.add(parameter);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private ElasticsearchParameter parameterFactory(MethodParameter methodParameter, TypeInformation<?> domainType) {
|
||||
return new ElasticsearchParameter(methodParameter, domainType);
|
||||
}
|
||||
|
||||
private ElasticsearchParameters(List<ElasticsearchParameter> parameters) {
|
||||
super(parameters);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ElasticsearchParameter createParameter(MethodParameter parameter) {
|
||||
return new ElasticsearchParameter(parameter);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ElasticsearchParameters createFrom(List<ElasticsearchParameter> parameters) {
|
||||
return new ElasticsearchParameters(parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom {@link Parameter} implementation adding parameters of type {@link Distance} to the special ones.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
class ElasticsearchParameter extends Parameter {
|
||||
|
||||
/**
|
||||
* Creates a new {@link ElasticsearchParameter}.
|
||||
*
|
||||
* @param parameter must not be {@literal null}.
|
||||
*/
|
||||
ElasticsearchParameter(MethodParameter parameter) {
|
||||
super(parameter);
|
||||
}
|
||||
List<ElasticsearchParameter> getScriptedFields() {
|
||||
return scriptedFields;
|
||||
}
|
||||
|
||||
List<ElasticsearchParameter> getRuntimeFields() {
|
||||
return runtimeFields;
|
||||
}
|
||||
}
|
||||
|
@ -15,9 +15,6 @@
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.repository.query;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.repository.query.ParametersParameterAccessor;
|
||||
|
||||
/**
|
||||
@ -27,7 +24,7 @@ import org.springframework.data.repository.query.ParametersParameterAccessor;
|
||||
class ElasticsearchParametersParameterAccessor extends ParametersParameterAccessor
|
||||
implements ElasticsearchParameterAccessor {
|
||||
|
||||
private final List<Object> values;
|
||||
private final Object[] values;
|
||||
|
||||
/**
|
||||
* Creates a new {@link ElasticsearchParametersParameterAccessor}.
|
||||
@ -38,11 +35,11 @@ class ElasticsearchParametersParameterAccessor extends ParametersParameterAccess
|
||||
ElasticsearchParametersParameterAccessor(ElasticsearchQueryMethod method, Object... values) {
|
||||
|
||||
super(method.getParameters(), values);
|
||||
this.values = Arrays.asList(values);
|
||||
this.values = values;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] getValues() {
|
||||
return values.toArray();
|
||||
return values;
|
||||
}
|
||||
}
|
||||
|
@ -19,9 +19,10 @@ import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
|
||||
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
|
||||
import org.springframework.data.elasticsearch.core.query.BaseQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.Query;
|
||||
import org.springframework.data.elasticsearch.core.query.RuntimeField;
|
||||
import org.springframework.data.elasticsearch.core.query.ScriptedField;
|
||||
import org.springframework.data.elasticsearch.repository.query.parser.ElasticsearchQueryCreator;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.repository.query.ParametersParameterAccessor;
|
||||
import org.springframework.data.repository.query.parser.PartTree;
|
||||
|
||||
/**
|
||||
@ -60,12 +61,11 @@ public class ElasticsearchPartQuery extends AbstractElasticsearchRepositoryQuery
|
||||
return tree.isExistsProjection();
|
||||
}
|
||||
|
||||
protected Query createQuery(ParametersParameterAccessor accessor) {
|
||||
protected BaseQuery createQuery(ElasticsearchParametersParameterAccessor accessor) {
|
||||
|
||||
BaseQuery query = new ElasticsearchQueryCreator(tree, accessor, mappingContext).createQuery();
|
||||
|
||||
if (tree.isLimiting()) {
|
||||
// noinspection ConstantConditions
|
||||
if (tree.getMaxResults() != null) {
|
||||
query.setMaxResults(tree.getMaxResults());
|
||||
}
|
||||
|
||||
|
@ -34,14 +34,19 @@ import org.springframework.data.elasticsearch.core.SearchPage;
|
||||
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
|
||||
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
|
||||
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
|
||||
import org.springframework.data.elasticsearch.core.query.BaseQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
|
||||
import org.springframework.data.elasticsearch.core.query.FetchSourceFilterBuilder;
|
||||
import org.springframework.data.elasticsearch.core.query.HighlightQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.RuntimeField;
|
||||
import org.springframework.data.elasticsearch.core.query.ScriptedField;
|
||||
import org.springframework.data.elasticsearch.core.query.SourceFilter;
|
||||
import org.springframework.data.elasticsearch.repository.support.StringQueryUtil;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.projection.ProjectionFactory;
|
||||
import org.springframework.data.repository.core.RepositoryMetadata;
|
||||
import org.springframework.data.repository.query.ParameterAccessor;
|
||||
import org.springframework.data.repository.query.Parameters;
|
||||
import org.springframework.data.repository.query.QueryMethod;
|
||||
import org.springframework.data.repository.util.QueryExecutionConverters;
|
||||
import org.springframework.data.repository.util.ReactiveWrapperConverters;
|
||||
@ -70,7 +75,7 @@ public class ElasticsearchQueryMethod extends QueryMethod {
|
||||
// base class uses them in order to use our variables
|
||||
protected final Method method;
|
||||
protected final Class<?> unwrappedReturnType;
|
||||
private Boolean unwrappedReturnTypeFromSearchHit = null;
|
||||
@Nullable private Boolean unwrappedReturnTypeFromSearchHit = null;
|
||||
|
||||
private final MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext;
|
||||
@Nullable private ElasticsearchEntityMetadata<?> metadata;
|
||||
@ -97,6 +102,11 @@ public class ElasticsearchQueryMethod extends QueryMethod {
|
||||
verifyCountQueryTypes();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Parameters<?, ?> createParameters(Method method, TypeInformation<?> domainType) {
|
||||
return new ElasticsearchParameters(method, domainType);
|
||||
}
|
||||
|
||||
protected void verifyCountQueryTypes() {
|
||||
|
||||
if (hasCountQueryAnnotation()) {
|
||||
@ -363,6 +373,49 @@ public class ElasticsearchQueryMethod extends QueryMethod {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void addMethodParameter(BaseQuery query, ElasticsearchParametersParameterAccessor parameterAccessor,
|
||||
ElasticsearchConverter elasticsearchConverter) {
|
||||
|
||||
if (hasAnnotatedHighlight()) {
|
||||
query.setHighlightQuery(getAnnotatedHighlightQuery());
|
||||
}
|
||||
|
||||
var sourceFilter = getSourceFilter(parameterAccessor, elasticsearchConverter);
|
||||
if (sourceFilter != null) {
|
||||
query.addSourceFilter(sourceFilter);
|
||||
}
|
||||
|
||||
if (parameterAccessor.getParameters() instanceof ElasticsearchParameters methodParameters) {
|
||||
var values = parameterAccessor.getValues();
|
||||
|
||||
methodParameters.getScriptedFields().forEach(elasticsearchParameter -> {
|
||||
var index = elasticsearchParameter.getIndex();
|
||||
|
||||
if (index >= 0 && index < values.length) {
|
||||
query.addScriptedField((ScriptedField) values[index]);
|
||||
}
|
||||
});
|
||||
|
||||
methodParameters.getRuntimeFields().forEach(elasticsearchParameter -> {
|
||||
var index = elasticsearchParameter.getIndex();
|
||||
|
||||
if (index >= 0 && index < values.length) {
|
||||
var runtimeField = (RuntimeField) values[index];
|
||||
query.addRuntimeField(runtimeField);
|
||||
query.addFields(runtimeField.getName());
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
var needToAddSourceFilter = sourceFilter == null
|
||||
&& !(methodParameters.getRuntimeFields().isEmpty()
|
||||
&& methodParameters.getScriptedFields().isEmpty());
|
||||
if (needToAddSourceFilter) {
|
||||
query.addSourceFilter(FetchSourceFilter.of(b -> b.withIncludes("*")));
|
||||
}
|
||||
}
|
||||
}
|
||||
// endregion
|
||||
|
||||
}
|
||||
|
@ -16,10 +16,10 @@
|
||||
package org.springframework.data.elasticsearch.repository.query;
|
||||
|
||||
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
|
||||
import org.springframework.data.elasticsearch.core.query.BaseQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.Query;
|
||||
import org.springframework.data.elasticsearch.core.query.StringQuery;
|
||||
import org.springframework.data.elasticsearch.repository.support.StringQueryUtil;
|
||||
import org.springframework.data.repository.query.ParametersParameterAccessor;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@ -57,7 +57,7 @@ public class ElasticsearchStringQuery extends AbstractElasticsearchRepositoryQue
|
||||
return false;
|
||||
}
|
||||
|
||||
protected Query createQuery(ParametersParameterAccessor parameterAccessor) {
|
||||
protected BaseQuery createQuery(ElasticsearchParametersParameterAccessor parameterAccessor) {
|
||||
|
||||
String queryString = new StringQueryUtil(elasticsearchOperations.getElasticsearchConverter().getConversionService())
|
||||
.replacePlaceholders(this.queryString, parameterAccessor);
|
||||
|
@ -81,7 +81,7 @@ class ReactiveElasticsearchParametersParameterAccessor extends ElasticsearchPara
|
||||
@Override
|
||||
public Object[] getValues() {
|
||||
|
||||
Object[] result = new Object[getValues().length];
|
||||
Object[] result = new Object[super.getValues().length];
|
||||
for (int i = 0; i < result.length; i++) {
|
||||
result[i] = getValue(i);
|
||||
}
|
||||
|
@ -30,7 +30,6 @@ import org.springframework.data.domain.Slice;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
|
||||
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
|
||||
import org.springframework.data.elasticsearch.repository.query.ElasticsearchParameters.ElasticsearchParameter;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.projection.ProjectionFactory;
|
||||
import org.springframework.data.repository.core.RepositoryMetadata;
|
||||
@ -103,8 +102,8 @@ public class ReactiveElasticsearchQueryMethod extends ElasticsearchQueryMethod {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ElasticsearchParameters createParameters(Method method) {
|
||||
return new ElasticsearchParameters(method);
|
||||
protected ElasticsearchParameters createParameters(Method method, TypeInformation<?> domainType) {
|
||||
return new ElasticsearchParameters(method, domainType);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -16,8 +16,8 @@
|
||||
package org.springframework.data.elasticsearch.repository.query;
|
||||
|
||||
import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations;
|
||||
import org.springframework.data.elasticsearch.core.query.BaseQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.CriteriaQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.Query;
|
||||
import org.springframework.data.elasticsearch.repository.query.parser.ElasticsearchQueryCreator;
|
||||
import org.springframework.data.repository.query.ResultProcessor;
|
||||
import org.springframework.data.repository.query.parser.PartTree;
|
||||
@ -40,7 +40,7 @@ public class ReactivePartTreeElasticsearchQuery extends AbstractReactiveElastics
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Query createQuery(ElasticsearchParameterAccessor accessor) {
|
||||
protected BaseQuery createQuery(ElasticsearchParameterAccessor accessor) {
|
||||
CriteriaQuery query = new ElasticsearchQueryCreator(tree, accessor, getMappingContext()).createQuery();
|
||||
|
||||
if (tree.isLimiting()) {
|
||||
|
@ -45,6 +45,7 @@ import org.springframework.data.elasticsearch.core.query.IndexQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.Query;
|
||||
import org.springframework.data.elasticsearch.core.query.RescorerQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.ScriptData;
|
||||
import org.springframework.data.elasticsearch.core.query.ScriptType;
|
||||
import org.springframework.data.elasticsearch.core.query.ScriptedField;
|
||||
import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchTemplateConfiguration;
|
||||
import org.springframework.data.elasticsearch.utils.IndexNameProvider;
|
||||
|
@ -1630,7 +1630,7 @@ public abstract class ElasticsearchIntegrationTests {
|
||||
final Query query = operations.matchAllQuery();
|
||||
|
||||
final UpdateQuery updateQuery = UpdateQuery.builder(query)
|
||||
.withScriptType(org.springframework.data.elasticsearch.core.ScriptType.INLINE)
|
||||
.withScriptType(ScriptType.INLINE)
|
||||
.withScript("ctx._source['message'] = params['newMessage']").withLang("painless")
|
||||
.withParams(Collections.singletonMap("newMessage", messageAfterUpdate)).withAbortOnVersionConflict(true)
|
||||
.build();
|
||||
|
@ -1,200 +0,0 @@
|
||||
/*
|
||||
* Copyright 2021-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Order;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.annotation.ReadOnlyProperty;
|
||||
import org.springframework.data.elasticsearch.annotations.DateFormat;
|
||||
import org.springframework.data.elasticsearch.annotations.Document;
|
||||
import org.springframework.data.elasticsearch.annotations.Field;
|
||||
import org.springframework.data.elasticsearch.annotations.FieldType;
|
||||
import org.springframework.data.elasticsearch.annotations.Mapping;
|
||||
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||
import org.springframework.data.elasticsearch.core.query.Criteria;
|
||||
import org.springframework.data.elasticsearch.core.query.CriteriaQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.FetchSourceFilterBuilder;
|
||||
import org.springframework.data.elasticsearch.core.query.Query;
|
||||
import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest;
|
||||
import org.springframework.data.elasticsearch.utils.IndexNameProvider;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* @author Peter-Josef Meisch
|
||||
* @author cdalxndr
|
||||
*/
|
||||
@SpringIntegrationTest
|
||||
public abstract class RuntimeFieldsIntegrationTests {
|
||||
|
||||
@Autowired private ElasticsearchOperations operations;
|
||||
@Autowired protected IndexNameProvider indexNameProvider;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
|
||||
indexNameProvider.increment();
|
||||
operations.indexOps(SomethingToBuy.class).createWithMapping();
|
||||
operations.indexOps(Person.class).createWithMapping();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(java.lang.Integer.MAX_VALUE)
|
||||
void cleanup() {
|
||||
operations.indexOps(IndexCoordinates.of(indexNameProvider.getPrefix() + "*")).delete();
|
||||
}
|
||||
|
||||
@Test // #1971
|
||||
@DisplayName("should use runtime-field from query in search")
|
||||
void shouldUseRuntimeFieldFromQueryInSearch() {
|
||||
|
||||
insert("1", "item 1", 13.5);
|
||||
insert("2", "item 2", 15);
|
||||
Query query = new CriteriaQuery(new Criteria("priceWithTax").greaterThanEqual(16.5));
|
||||
RuntimeField runtimeField = new RuntimeField("priceWithTax", "double", "emit(doc['price'].value * 1.19)");
|
||||
query.addRuntimeField(runtimeField);
|
||||
|
||||
SearchHits<SomethingToBuy> searchHits = operations.search(query, SomethingToBuy.class);
|
||||
|
||||
assertThat(searchHits.getTotalHits()).isEqualTo(1);
|
||||
assertThat(searchHits.getSearchHit(0).getId()).isEqualTo("2");
|
||||
}
|
||||
|
||||
@Test // #2267
|
||||
@DisplayName("should use runtime-field without script")
|
||||
void shouldUseRuntimeFieldWithoutScript() {
|
||||
|
||||
insert("1", "11", 10);
|
||||
Query query = new CriteriaQuery(new Criteria("description").matches(11.0));
|
||||
RuntimeField runtimeField = new RuntimeField("description", "double");
|
||||
query.addRuntimeField(runtimeField);
|
||||
|
||||
SearchHits<SomethingToBuy> searchHits = operations.search(query, SomethingToBuy.class);
|
||||
|
||||
assertThat(searchHits.getTotalHits()).isEqualTo(1);
|
||||
assertThat(searchHits.getSearchHit(0).getId()).isEqualTo("1");
|
||||
}
|
||||
|
||||
@Test // #2431
|
||||
@DisplayName("should return value from runtime field defined in mapping")
|
||||
void shouldReturnValueFromRuntimeFieldDefinedInMapping() {
|
||||
|
||||
var person = new Person();
|
||||
var years = 10;
|
||||
person.setBirthDate(LocalDate.now().minusDays(years * 365 + 100));
|
||||
operations.save(person);
|
||||
var query = Query.findAll();
|
||||
query.addFields("age");
|
||||
query.addSourceFilter(new FetchSourceFilterBuilder().withIncludes("*").build());
|
||||
|
||||
var searchHits = operations.search(query, Person.class);
|
||||
|
||||
assertThat(searchHits.getTotalHits()).isEqualTo(1);
|
||||
assertThat(searchHits.getSearchHit(0).getContent().getAge()).isEqualTo(years);
|
||||
}
|
||||
|
||||
private void insert(String id, String description, double price) {
|
||||
SomethingToBuy entity = new SomethingToBuy();
|
||||
entity.setId(id);
|
||||
entity.setDescription(description);
|
||||
entity.setPrice(price);
|
||||
operations.save(entity);
|
||||
}
|
||||
|
||||
@Document(indexName = "#{@indexNameProvider.indexName()}-something")
|
||||
private static class SomethingToBuy {
|
||||
private @Id @Nullable String id;
|
||||
|
||||
@Nullable
|
||||
@Field(type = FieldType.Text) private String description;
|
||||
|
||||
@Nullable
|
||||
@Field(type = FieldType.Double) private Double price;
|
||||
|
||||
@Nullable
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(@Nullable String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(@Nullable String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Double getPrice() {
|
||||
return price;
|
||||
}
|
||||
|
||||
public void setPrice(@Nullable Double price) {
|
||||
this.price = price;
|
||||
}
|
||||
}
|
||||
|
||||
@Document(indexName = "#{@indexNameProvider.indexName()}-person")
|
||||
@Mapping(runtimeFieldsPath = "/runtime-fields-person.json")
|
||||
public class Person {
|
||||
@Nullable private String id;
|
||||
|
||||
@Field(type = FieldType.Date, format = DateFormat.basic_date)
|
||||
@Nullable private LocalDate birthDate;
|
||||
|
||||
@ReadOnlyProperty // do not write to prevent ES from automapping
|
||||
@Nullable private Integer age;
|
||||
|
||||
@Nullable
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(@Nullable String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public LocalDate getBirthDate() {
|
||||
return birthDate;
|
||||
}
|
||||
|
||||
public void setBirthDate(@Nullable LocalDate birthDate) {
|
||||
this.birthDate = birthDate;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getAge() {
|
||||
return age;
|
||||
}
|
||||
|
||||
public void setAge(@Nullable Integer age) {
|
||||
this.age = age;
|
||||
}
|
||||
}
|
||||
}
|
@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core;
|
||||
package org.springframework.data.elasticsearch.core.query;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
@ -0,0 +1,27 @@
|
||||
package org.springframework.data.elasticsearch.core.query.scriptedandruntimefields;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.data.elasticsearch.junit.jupiter.ReactiveElasticsearchTemplateConfiguration;
|
||||
import org.springframework.data.elasticsearch.repository.config.EnableReactiveElasticsearchRepositories;
|
||||
import org.springframework.data.elasticsearch.utils.IndexNameProvider;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
|
||||
/**
|
||||
* @since 5.2
|
||||
*/
|
||||
@ContextConfiguration(classes = ReactiveScriptedAndRuntimeFieldsELCIntegrationTests.Config.class)
|
||||
public class ReactiveScriptedAndRuntimeFieldsELCIntegrationTests
|
||||
extends ReactiveScriptedAndRuntimeFieldsIntegrationTests {
|
||||
|
||||
@Configuration
|
||||
@Import({ ReactiveElasticsearchTemplateConfiguration.class })
|
||||
@EnableReactiveElasticsearchRepositories(considerNestedRepositories = true)
|
||||
static class Config {
|
||||
@Bean
|
||||
IndexNameProvider indexNameProvider() {
|
||||
return new IndexNameProvider("reactive-scripted-runtime");
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,416 @@
|
||||
/*
|
||||
* Copyright 2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core.query.scriptedandruntimefields;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Order;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.annotation.ReadOnlyProperty;
|
||||
import org.springframework.data.elasticsearch.annotations.DateFormat;
|
||||
import org.springframework.data.elasticsearch.annotations.Document;
|
||||
import org.springframework.data.elasticsearch.annotations.Field;
|
||||
import org.springframework.data.elasticsearch.annotations.FieldType;
|
||||
import org.springframework.data.elasticsearch.annotations.Mapping;
|
||||
import org.springframework.data.elasticsearch.annotations.ScriptedField;
|
||||
import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations;
|
||||
import org.springframework.data.elasticsearch.core.SearchHit;
|
||||
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||
import org.springframework.data.elasticsearch.core.query.Criteria;
|
||||
import org.springframework.data.elasticsearch.core.query.CriteriaQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.FetchSourceFilterBuilder;
|
||||
import org.springframework.data.elasticsearch.core.query.Query;
|
||||
import org.springframework.data.elasticsearch.core.query.RuntimeField;
|
||||
import org.springframework.data.elasticsearch.core.query.ScriptData;
|
||||
import org.springframework.data.elasticsearch.core.query.ScriptType;
|
||||
import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest;
|
||||
import org.springframework.data.elasticsearch.repository.ReactiveElasticsearchRepository;
|
||||
import org.springframework.data.elasticsearch.utils.IndexNameProvider;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* @author Peter-Josef Meisch
|
||||
*/
|
||||
@SpringIntegrationTest
|
||||
public abstract class ReactiveScriptedAndRuntimeFieldsIntegrationTests {
|
||||
|
||||
@Autowired private ReactiveElasticsearchOperations operations;
|
||||
@Autowired protected IndexNameProvider indexNameProvider;
|
||||
@Autowired private ReactiveSARRepository repository;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
|
||||
indexNameProvider.increment();
|
||||
operations.indexOps(SomethingToBuy.class).createWithMapping().block();
|
||||
operations.indexOps(Person.class).createWithMapping().block();
|
||||
operations.indexOps(SAREntity.class).createWithMapping().block();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(Integer.MAX_VALUE)
|
||||
void cleanup() {
|
||||
operations.indexOps(IndexCoordinates.of(indexNameProvider.getPrefix() + '*')).delete().block();
|
||||
}
|
||||
|
||||
@Test // #1971
|
||||
@DisplayName("should use runtime-field from query in search")
|
||||
void shouldUseRuntimeFieldFromQueryInSearch() {
|
||||
|
||||
insert("1", "item 1", 13.5);
|
||||
insert("2", "item 2", 15);
|
||||
Query query = new CriteriaQuery(new Criteria("priceWithTax").greaterThanEqual(16.5));
|
||||
RuntimeField runtimeField = new RuntimeField("priceWithTax", "double", "emit(doc['price'].value * 1.19)");
|
||||
query.addRuntimeField(runtimeField);
|
||||
|
||||
List<SearchHit<SomethingToBuy>> searchHits = operations.search(query, SomethingToBuy.class).collectList().block();
|
||||
|
||||
assertThat(searchHits.size()).isEqualTo(1);
|
||||
var searchHit = searchHits.get(0);
|
||||
assertThat(searchHit.getId()).isEqualTo("2");
|
||||
var foundEntity = searchHit.getContent();
|
||||
assertThat(foundEntity.getDescription()).isEqualTo("item 2");
|
||||
}
|
||||
|
||||
@Test // #2267
|
||||
@DisplayName("should use runtime-field without script")
|
||||
void shouldUseRuntimeFieldWithoutScript() {
|
||||
|
||||
insert("1", "11", 10);
|
||||
Query query = new CriteriaQuery(new Criteria("description").matches(11.0));
|
||||
RuntimeField runtimeField = new RuntimeField("description", "double");
|
||||
query.addRuntimeField(runtimeField);
|
||||
|
||||
List<SearchHit<SomethingToBuy>> searchHits = operations.search(query, SomethingToBuy.class).collectList().block();
|
||||
|
||||
assertThat(searchHits.size()).isEqualTo(1);
|
||||
var searchHit = searchHits.get(0);
|
||||
assertThat(searchHit.getId()).isEqualTo("1");
|
||||
assertThat(searchHit.getContent().getDescription()).isEqualTo("11");
|
||||
}
|
||||
|
||||
@Test // #2431
|
||||
@DisplayName("should return value from runtime field defined in mapping")
|
||||
void shouldReturnValueFromRuntimeFieldDefinedInMapping() {
|
||||
|
||||
var person = new Person();
|
||||
var years = 10;
|
||||
var birthDate = LocalDate.now().minusDays(years * 365 + 100);
|
||||
person.setBirthDate(birthDate);
|
||||
operations.save(person).block();
|
||||
var query = Query.findAll();
|
||||
query.addFields("age");
|
||||
query.addSourceFilter(new FetchSourceFilterBuilder().withIncludes("*").build());
|
||||
|
||||
var searchHits = operations.search(query, Person.class).collectList().block();
|
||||
|
||||
assertThat(searchHits.size()).isEqualTo(1);
|
||||
var foundPerson = searchHits.get(0).getContent();
|
||||
assertThat(foundPerson.getAge()).isEqualTo(years);
|
||||
assertThat(foundPerson.getBirthDate()).isEqualTo(birthDate);
|
||||
}
|
||||
|
||||
@Test // #2035
|
||||
@DisplayName("should use repository method with ScriptedField parameters")
|
||||
void shouldUseRepositoryMethodWithScriptedFieldParameters() {
|
||||
|
||||
var entity = new SAREntity();
|
||||
entity.setId("42");
|
||||
entity.setValue(3);
|
||||
|
||||
repository.save(entity).block();
|
||||
|
||||
org.springframework.data.elasticsearch.core.query.ScriptedField scriptedField1 = getScriptedField("scriptedValue1",
|
||||
2);
|
||||
org.springframework.data.elasticsearch.core.query.ScriptedField scriptedField2 = getScriptedField("scriptedValue2",
|
||||
3);
|
||||
|
||||
var searchHits = repository.findByValue(3, scriptedField1, scriptedField2).collectList().block();
|
||||
|
||||
assertThat(searchHits.size()).isEqualTo(1);
|
||||
var foundEntity = searchHits.get(0).getContent();
|
||||
assertThat(foundEntity.value).isEqualTo(3);
|
||||
assertThat(foundEntity.getScriptedValue1()).isEqualTo(6);
|
||||
assertThat(foundEntity.getScriptedValue2()).isEqualTo(9);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static org.springframework.data.elasticsearch.core.query.ScriptedField getScriptedField(String fieldName,
|
||||
int factor) {
|
||||
return org.springframework.data.elasticsearch.core.query.ScriptedField.of(
|
||||
fieldName,
|
||||
ScriptData.of(b -> b
|
||||
.withType(ScriptType.INLINE)
|
||||
.withScript("doc['value'].size() > 0 ? doc['value'].value * params['factor'] : 0")
|
||||
.withParams(Map.of("factor", factor))));
|
||||
}
|
||||
|
||||
@Test // #2035
|
||||
@DisplayName("should use repository string query method with ScriptedField parameters")
|
||||
void shouldUseRepositoryStringQueryMethodWithScriptedFieldParameters() {
|
||||
|
||||
var entity = new SAREntity();
|
||||
entity.setId("42");
|
||||
entity.setValue(3);
|
||||
|
||||
repository.save(entity).block();
|
||||
|
||||
org.springframework.data.elasticsearch.core.query.ScriptedField scriptedField1 = getScriptedField("scriptedValue1",
|
||||
2);
|
||||
org.springframework.data.elasticsearch.core.query.ScriptedField scriptedField2 = getScriptedField("scriptedValue2",
|
||||
3);
|
||||
|
||||
var searchHits = repository.findWithScriptedFields(3, scriptedField1, scriptedField2).collectList().block();
|
||||
|
||||
assertThat(searchHits.size()).isEqualTo(1);
|
||||
var foundEntity = searchHits.get(0).getContent();
|
||||
assertThat(foundEntity.value).isEqualTo(3);
|
||||
assertThat(foundEntity.getScriptedValue1()).isEqualTo(6);
|
||||
assertThat(foundEntity.getScriptedValue2()).isEqualTo(9);
|
||||
}
|
||||
|
||||
@Test // #2035
|
||||
@DisplayName("should use repository method with RuntimeField parameters")
|
||||
void shouldUseRepositoryMethodWithRuntimeFieldParameters() {
|
||||
|
||||
var entity = new SAREntity();
|
||||
entity.setId("42");
|
||||
entity.setValue(3);
|
||||
|
||||
repository.save(entity).block();
|
||||
|
||||
var runtimeField1 = getRuntimeField("scriptedValue1", 3);
|
||||
var runtimeField2 = getRuntimeField("scriptedValue2", 4);
|
||||
|
||||
var searchHits = repository.findByValue(3, runtimeField1, runtimeField2).collectList().block();
|
||||
|
||||
assertThat(searchHits.size()).isEqualTo(1);
|
||||
var foundEntity = searchHits.get(0).getContent();
|
||||
assertThat(foundEntity.value).isEqualTo(3);
|
||||
assertThat(foundEntity.getScriptedValue1()).isEqualTo(9);
|
||||
assertThat(foundEntity.getScriptedValue2()).isEqualTo(12);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static RuntimeField getRuntimeField(String fieldName, int factor) {
|
||||
return new RuntimeField(
|
||||
fieldName,
|
||||
"long",
|
||||
String.format("emit(doc['value'].size() > 0 ? doc['value'].value * %d : 0)", factor));
|
||||
}
|
||||
|
||||
@Test // #2035
|
||||
@DisplayName("should use repository string query method with RuntimeField parameters")
|
||||
void shouldUseRepositoryStringQueryMethodWithRuntimeFieldParameters() {
|
||||
|
||||
var entity = new SAREntity();
|
||||
entity.setId("42");
|
||||
entity.setValue(3);
|
||||
|
||||
repository.save(entity).block();
|
||||
|
||||
var runtimeField1 = getRuntimeField("scriptedValue1", 3);
|
||||
var runtimeField2 = getRuntimeField("scriptedValue2", 4);
|
||||
|
||||
var searchHits = repository.findWithRuntimeFields(3, runtimeField1, runtimeField2).collectList().block();
|
||||
|
||||
assertThat(searchHits.size()).isEqualTo(1);
|
||||
var foundEntity = searchHits.get(0).getContent();
|
||||
assertThat(foundEntity.value).isEqualTo(3);
|
||||
assertThat(foundEntity.getScriptedValue1()).isEqualTo(9);
|
||||
assertThat(foundEntity.getScriptedValue2()).isEqualTo(12);
|
||||
}
|
||||
|
||||
private void insert(String id, String description, double price) {
|
||||
SomethingToBuy entity = new SomethingToBuy();
|
||||
entity.setId(id);
|
||||
entity.setDescription(description);
|
||||
entity.setPrice(price);
|
||||
operations.save(entity).block();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@Document(indexName = "#{@indexNameProvider.indexName()}-something-to-by")
|
||||
private static class SomethingToBuy {
|
||||
private @Id @Nullable String id;
|
||||
|
||||
@Nullable
|
||||
@Field(type = FieldType.Text) private String description;
|
||||
|
||||
@Nullable
|
||||
@Field(type = FieldType.Double) private Double price;
|
||||
|
||||
@Nullable
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(@Nullable String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(@Nullable String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Double getPrice() {
|
||||
return price;
|
||||
}
|
||||
|
||||
public void setPrice(@Nullable Double price) {
|
||||
this.price = price;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@Document(indexName = "#{@indexNameProvider.indexName()}-person")
|
||||
@Mapping(runtimeFieldsPath = "/runtime-fields-person.json")
|
||||
public static class Person {
|
||||
@Nullable private String id;
|
||||
|
||||
@Field(type = FieldType.Date, format = DateFormat.basic_date)
|
||||
@Nullable private LocalDate birthDate;
|
||||
|
||||
@ReadOnlyProperty // do not write to prevent ES from automapping
|
||||
@Nullable private Integer age;
|
||||
|
||||
@Nullable
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(@Nullable String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public LocalDate getBirthDate() {
|
||||
return birthDate;
|
||||
}
|
||||
|
||||
public void setBirthDate(@Nullable LocalDate birthDate) {
|
||||
this.birthDate = birthDate;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getAge() {
|
||||
return age;
|
||||
}
|
||||
|
||||
public void setAge(@Nullable Integer age) {
|
||||
this.age = age;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@Document(indexName = "#{@indexNameProvider.indexName()}-sar")
|
||||
public static class SAREntity {
|
||||
@Nullable private String id;
|
||||
@Field(type = FieldType.Integer)
|
||||
@Nullable Integer value;
|
||||
@ScriptedField
|
||||
@Nullable Integer scriptedValue1;
|
||||
@ScriptedField
|
||||
@Nullable Integer scriptedValue2;
|
||||
|
||||
@Nullable
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(@Nullable String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(@Nullable Integer value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getScriptedValue1() {
|
||||
return scriptedValue1;
|
||||
}
|
||||
|
||||
public void setScriptedValue1(@Nullable Integer scriptedValue1) {
|
||||
this.scriptedValue1 = scriptedValue1;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getScriptedValue2() {
|
||||
return scriptedValue2;
|
||||
}
|
||||
|
||||
public void setScriptedValue2(@Nullable Integer scriptedValue2) {
|
||||
this.scriptedValue2 = scriptedValue2;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("SpringDataRepositoryMethodReturnTypeInspection")
|
||||
public interface ReactiveSARRepository extends ReactiveElasticsearchRepository<SAREntity, String> {
|
||||
Flux<SearchHit<SAREntity>> findByValue(Integer value,
|
||||
org.springframework.data.elasticsearch.core.query.ScriptedField scriptedField1,
|
||||
org.springframework.data.elasticsearch.core.query.ScriptedField scriptedField2);
|
||||
|
||||
@org.springframework.data.elasticsearch.annotations.Query("""
|
||||
{
|
||||
"term": {
|
||||
"value": {
|
||||
"value": "?0"
|
||||
}
|
||||
}
|
||||
}
|
||||
""")
|
||||
Flux<SearchHit<SAREntity>> findWithScriptedFields(Integer value,
|
||||
org.springframework.data.elasticsearch.core.query.ScriptedField scriptedField1,
|
||||
org.springframework.data.elasticsearch.core.query.ScriptedField scriptedField2);
|
||||
|
||||
Flux<SearchHit<SAREntity>> findByValue(Integer value, RuntimeField runtimeField1, RuntimeField runtimeField2);
|
||||
|
||||
@org.springframework.data.elasticsearch.annotations.Query("""
|
||||
{
|
||||
"term": {
|
||||
"value": {
|
||||
"value": "?0"
|
||||
}
|
||||
}
|
||||
}
|
||||
""")
|
||||
Flux<SearchHit<SAREntity>> findWithRuntimeFields(Integer value, RuntimeField runtimeField1,
|
||||
RuntimeField runtimeField2);
|
||||
}
|
||||
}
|
@ -13,12 +13,13 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core;
|
||||
package org.springframework.data.elasticsearch.core.query.scriptedandruntimefields;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchTemplateConfiguration;
|
||||
import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories;
|
||||
import org.springframework.data.elasticsearch.utils.IndexNameProvider;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
|
||||
@ -26,15 +27,16 @@ import org.springframework.test.context.ContextConfiguration;
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 4.4
|
||||
*/
|
||||
@ContextConfiguration(classes = { RuntimeFieldsELCIntegrationTests.Config.class })
|
||||
public class RuntimeFieldsELCIntegrationTests extends RuntimeFieldsIntegrationTests {
|
||||
@ContextConfiguration(classes = { ScriptedAndRuntimeFieldsELCIntegrationTests.Config.class })
|
||||
public class ScriptedAndRuntimeFieldsELCIntegrationTests extends ScriptedAndRuntimeFieldsIntegrationTests {
|
||||
|
||||
@Configuration
|
||||
@Import({ ElasticsearchTemplateConfiguration.class })
|
||||
@EnableElasticsearchRepositories(considerNestedRepositories = true)
|
||||
static class Config {
|
||||
@Bean
|
||||
IndexNameProvider indexNameProvider() {
|
||||
return new IndexNameProvider("runtime-fields-rest-template");
|
||||
return new IndexNameProvider("scripted-runtime");
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,413 @@
|
||||
/*
|
||||
* Copyright 2021-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core.query.scriptedandruntimefields;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.Map;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Order;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.annotation.ReadOnlyProperty;
|
||||
import org.springframework.data.elasticsearch.annotations.DateFormat;
|
||||
import org.springframework.data.elasticsearch.annotations.Document;
|
||||
import org.springframework.data.elasticsearch.annotations.Field;
|
||||
import org.springframework.data.elasticsearch.annotations.FieldType;
|
||||
import org.springframework.data.elasticsearch.annotations.Mapping;
|
||||
import org.springframework.data.elasticsearch.annotations.ScriptedField;
|
||||
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
|
||||
import org.springframework.data.elasticsearch.core.SearchHits;
|
||||
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||
import org.springframework.data.elasticsearch.core.query.Criteria;
|
||||
import org.springframework.data.elasticsearch.core.query.CriteriaQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.FetchSourceFilterBuilder;
|
||||
import org.springframework.data.elasticsearch.core.query.Query;
|
||||
import org.springframework.data.elasticsearch.core.query.RuntimeField;
|
||||
import org.springframework.data.elasticsearch.core.query.ScriptData;
|
||||
import org.springframework.data.elasticsearch.core.query.ScriptType;
|
||||
import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest;
|
||||
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
|
||||
import org.springframework.data.elasticsearch.utils.IndexNameProvider;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* @author Peter-Josef Meisch
|
||||
* @author cdalxndr
|
||||
*/
|
||||
@SpringIntegrationTest
|
||||
public abstract class ScriptedAndRuntimeFieldsIntegrationTests {
|
||||
|
||||
@Autowired private ElasticsearchOperations operations;
|
||||
@Autowired protected IndexNameProvider indexNameProvider;
|
||||
@Autowired private SARRepository repository;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
|
||||
indexNameProvider.increment();
|
||||
operations.indexOps(SomethingToBuy.class).createWithMapping();
|
||||
operations.indexOps(Person.class).createWithMapping();
|
||||
operations.indexOps(SAREntity.class).createWithMapping();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(java.lang.Integer.MAX_VALUE)
|
||||
void cleanup() {
|
||||
operations.indexOps(IndexCoordinates.of(indexNameProvider.getPrefix() + '*')).delete();
|
||||
}
|
||||
|
||||
@Test // #1971
|
||||
@DisplayName("should use runtime-field from query in search")
|
||||
void shouldUseRuntimeFieldFromQueryInSearch() {
|
||||
|
||||
insert("1", "item 1", 13.5);
|
||||
insert("2", "item 2", 15);
|
||||
Query query = new CriteriaQuery(new Criteria("priceWithTax").greaterThanEqual(16.5));
|
||||
RuntimeField runtimeField = new RuntimeField("priceWithTax", "double", "emit(doc['price'].value * 1.19)");
|
||||
query.addRuntimeField(runtimeField);
|
||||
|
||||
SearchHits<SomethingToBuy> searchHits = operations.search(query, SomethingToBuy.class);
|
||||
|
||||
assertThat(searchHits.getTotalHits()).isEqualTo(1);
|
||||
var searchHit = searchHits.getSearchHit(0);
|
||||
assertThat(searchHit.getId()).isEqualTo("2");
|
||||
var foundEntity = searchHit.getContent();
|
||||
assertThat(foundEntity.getDescription()).isEqualTo("item 2");
|
||||
}
|
||||
|
||||
@Test // #2267
|
||||
@DisplayName("should use runtime-field without script")
|
||||
void shouldUseRuntimeFieldWithoutScript() {
|
||||
|
||||
insert("1", "11", 10);
|
||||
Query query = new CriteriaQuery(new Criteria("description").matches(11.0));
|
||||
RuntimeField runtimeField = new RuntimeField("description", "double");
|
||||
query.addRuntimeField(runtimeField);
|
||||
|
||||
SearchHits<SomethingToBuy> searchHits = operations.search(query, SomethingToBuy.class);
|
||||
|
||||
assertThat(searchHits.getTotalHits()).isEqualTo(1);
|
||||
var searchHit = searchHits.getSearchHit(0);
|
||||
assertThat(searchHit.getId()).isEqualTo("1");
|
||||
assertThat(searchHit.getContent().getDescription()).isEqualTo("11");
|
||||
}
|
||||
|
||||
@Test // #2431
|
||||
@DisplayName("should return value from runtime field defined in mapping")
|
||||
void shouldReturnValueFromRuntimeFieldDefinedInMapping() {
|
||||
|
||||
var person = new Person();
|
||||
var years = 10;
|
||||
var birthDate = LocalDate.now().minusDays(years * 365 + 100);
|
||||
person.setBirthDate(birthDate);
|
||||
operations.save(person);
|
||||
var query = Query.findAll();
|
||||
query.addFields("age");
|
||||
query.addSourceFilter(new FetchSourceFilterBuilder().withIncludes("*").build());
|
||||
|
||||
var searchHits = operations.search(query, Person.class);
|
||||
|
||||
assertThat(searchHits.getTotalHits()).isEqualTo(1);
|
||||
var foundPerson = searchHits.getSearchHit(0).getContent();
|
||||
assertThat(foundPerson.getAge()).isEqualTo(years);
|
||||
assertThat(foundPerson.getBirthDate()).isEqualTo(birthDate);
|
||||
}
|
||||
|
||||
@Test // #2035
|
||||
@DisplayName("should use repository method with ScriptedField parameters")
|
||||
void shouldUseRepositoryMethodWithScriptedFieldParameters() {
|
||||
|
||||
var entity = new SAREntity();
|
||||
entity.setId("42");
|
||||
entity.setValue(3);
|
||||
|
||||
repository.save(entity);
|
||||
|
||||
org.springframework.data.elasticsearch.core.query.ScriptedField scriptedField1 = getScriptedField("scriptedValue1",
|
||||
2);
|
||||
org.springframework.data.elasticsearch.core.query.ScriptedField scriptedField2 = getScriptedField("scriptedValue2",
|
||||
3);
|
||||
|
||||
var searchHits = repository.findByValue(3, scriptedField1, scriptedField2);
|
||||
|
||||
assertThat(searchHits.getTotalHits()).isEqualTo(1);
|
||||
var foundEntity = searchHits.getSearchHit(0).getContent();
|
||||
assertThat(foundEntity.value).isEqualTo(3);
|
||||
assertThat(foundEntity.getScriptedValue1()).isEqualTo(6);
|
||||
assertThat(foundEntity.getScriptedValue2()).isEqualTo(9);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static org.springframework.data.elasticsearch.core.query.ScriptedField getScriptedField(String fieldName,
|
||||
int factor) {
|
||||
return org.springframework.data.elasticsearch.core.query.ScriptedField.of(
|
||||
fieldName,
|
||||
ScriptData.of(b -> b
|
||||
.withType(ScriptType.INLINE)
|
||||
.withScript("doc['value'].size() > 0 ? doc['value'].value * params['factor'] : 0")
|
||||
.withParams(Map.of("factor", factor))));
|
||||
}
|
||||
|
||||
@Test // #2035
|
||||
@DisplayName("should use repository string query method with ScriptedField parameters")
|
||||
void shouldUseRepositoryStringQueryMethodWithScriptedFieldParameters() {
|
||||
|
||||
var entity = new SAREntity();
|
||||
entity.setId("42");
|
||||
entity.setValue(3);
|
||||
|
||||
repository.save(entity);
|
||||
|
||||
org.springframework.data.elasticsearch.core.query.ScriptedField scriptedField1 = getScriptedField("scriptedValue1",
|
||||
2);
|
||||
org.springframework.data.elasticsearch.core.query.ScriptedField scriptedField2 = getScriptedField("scriptedValue2",
|
||||
3);
|
||||
|
||||
var searchHits = repository.findWithScriptedFields(3, scriptedField1, scriptedField2);
|
||||
|
||||
assertThat(searchHits.getTotalHits()).isEqualTo(1);
|
||||
var foundEntity = searchHits.getSearchHit(0).getContent();
|
||||
assertThat(foundEntity.value).isEqualTo(3);
|
||||
assertThat(foundEntity.getScriptedValue1()).isEqualTo(6);
|
||||
assertThat(foundEntity.getScriptedValue2()).isEqualTo(9);
|
||||
}
|
||||
|
||||
@Test // #2035
|
||||
@DisplayName("should use repository method with RuntimeField parameters")
|
||||
void shouldUseRepositoryMethodWithRuntimeFieldParameters() {
|
||||
|
||||
var entity = new SAREntity();
|
||||
entity.setId("42");
|
||||
entity.setValue(3);
|
||||
|
||||
repository.save(entity);
|
||||
|
||||
var runtimeField1 = getRuntimeField("scriptedValue1", 3);
|
||||
var runtimeField2 = getRuntimeField("scriptedValue2", 4);
|
||||
|
||||
var searchHits = repository.findByValue(3, runtimeField1, runtimeField2);
|
||||
|
||||
assertThat(searchHits.getTotalHits()).isEqualTo(1);
|
||||
var foundEntity = searchHits.getSearchHit(0).getContent();
|
||||
assertThat(foundEntity.value).isEqualTo(3);
|
||||
assertThat(foundEntity.getScriptedValue1()).isEqualTo(9);
|
||||
assertThat(foundEntity.getScriptedValue2()).isEqualTo(12);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static RuntimeField getRuntimeField(String fieldName, int factor) {
|
||||
return new RuntimeField(
|
||||
fieldName,
|
||||
"long",
|
||||
String.format("emit(doc['value'].size() > 0 ? doc['value'].value * %d : 0)", factor));
|
||||
}
|
||||
|
||||
@Test // #2035
|
||||
@DisplayName("should use repository string query method with RuntimeField parameters")
|
||||
void shouldUseRepositoryStringQueryMethodWithRuntimeFieldParameters() {
|
||||
|
||||
var entity = new SAREntity();
|
||||
entity.setId("42");
|
||||
entity.setValue(3);
|
||||
|
||||
repository.save(entity);
|
||||
|
||||
var runtimeField1 = getRuntimeField("scriptedValue1", 3);
|
||||
var runtimeField2 = getRuntimeField("scriptedValue2", 4);
|
||||
|
||||
var searchHits = repository.findWithRuntimeFields(3, runtimeField1, runtimeField2);
|
||||
|
||||
assertThat(searchHits.getTotalHits()).isEqualTo(1);
|
||||
var foundEntity = searchHits.getSearchHit(0).getContent();
|
||||
assertThat(foundEntity.value).isEqualTo(3);
|
||||
assertThat(foundEntity.getScriptedValue1()).isEqualTo(9);
|
||||
assertThat(foundEntity.getScriptedValue2()).isEqualTo(12);
|
||||
}
|
||||
|
||||
private void insert(String id, String description, double price) {
|
||||
SomethingToBuy entity = new SomethingToBuy();
|
||||
entity.setId(id);
|
||||
entity.setDescription(description);
|
||||
entity.setPrice(price);
|
||||
operations.save(entity);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@Document(indexName = "#{@indexNameProvider.indexName()}-something-to-by")
|
||||
private static class SomethingToBuy {
|
||||
private @Id @Nullable String id;
|
||||
|
||||
@Nullable
|
||||
@Field(type = FieldType.Text) private String description;
|
||||
|
||||
@Nullable
|
||||
@Field(type = FieldType.Double) private Double price;
|
||||
|
||||
@Nullable
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(@Nullable String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(@Nullable String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Double getPrice() {
|
||||
return price;
|
||||
}
|
||||
|
||||
public void setPrice(@Nullable Double price) {
|
||||
this.price = price;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@Document(indexName = "#{@indexNameProvider.indexName()}-person")
|
||||
@Mapping(runtimeFieldsPath = "/runtime-fields-person.json")
|
||||
public static class Person {
|
||||
@Nullable private String id;
|
||||
|
||||
@Field(type = FieldType.Date, format = DateFormat.basic_date)
|
||||
@Nullable private LocalDate birthDate;
|
||||
|
||||
@ReadOnlyProperty // do not write to prevent ES from automapping
|
||||
@Nullable private Integer age;
|
||||
|
||||
@Nullable
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(@Nullable String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public LocalDate getBirthDate() {
|
||||
return birthDate;
|
||||
}
|
||||
|
||||
public void setBirthDate(@Nullable LocalDate birthDate) {
|
||||
this.birthDate = birthDate;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getAge() {
|
||||
return age;
|
||||
}
|
||||
|
||||
public void setAge(@Nullable Integer age) {
|
||||
this.age = age;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@Document(indexName = "#{@indexNameProvider.indexName()}-sar")
|
||||
public static class SAREntity {
|
||||
@Nullable private String id;
|
||||
@Field(type = FieldType.Integer)
|
||||
@Nullable Integer value;
|
||||
@ScriptedField
|
||||
@Nullable Integer scriptedValue1;
|
||||
@ScriptedField
|
||||
@Nullable Integer scriptedValue2;
|
||||
|
||||
@Nullable
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(@Nullable String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(@Nullable Integer value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getScriptedValue1() {
|
||||
return scriptedValue1;
|
||||
}
|
||||
|
||||
public void setScriptedValue1(@Nullable Integer scriptedValue1) {
|
||||
this.scriptedValue1 = scriptedValue1;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getScriptedValue2() {
|
||||
return scriptedValue2;
|
||||
}
|
||||
|
||||
public void setScriptedValue2(@Nullable Integer scriptedValue2) {
|
||||
this.scriptedValue2 = scriptedValue2;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("SpringDataRepositoryMethodReturnTypeInspection")
|
||||
public interface SARRepository extends ElasticsearchRepository<SAREntity, String> {
|
||||
SearchHits<SAREntity> findByValue(Integer value,
|
||||
org.springframework.data.elasticsearch.core.query.ScriptedField scriptedField1,
|
||||
org.springframework.data.elasticsearch.core.query.ScriptedField scriptedField2);
|
||||
|
||||
@org.springframework.data.elasticsearch.annotations.Query("""
|
||||
{
|
||||
"term": {
|
||||
"value": {
|
||||
"value": "?0"
|
||||
}
|
||||
}
|
||||
}
|
||||
""")
|
||||
SearchHits<SAREntity> findWithScriptedFields(Integer value,
|
||||
org.springframework.data.elasticsearch.core.query.ScriptedField scriptedField1,
|
||||
org.springframework.data.elasticsearch.core.query.ScriptedField scriptedField2);
|
||||
|
||||
SearchHits<SAREntity> findByValue(Integer value, RuntimeField runtimeField1, RuntimeField runtimeField2);
|
||||
|
||||
@org.springframework.data.elasticsearch.annotations.Query("""
|
||||
{
|
||||
"term": {
|
||||
"value": {
|
||||
"value": "?0"
|
||||
}
|
||||
}
|
||||
}
|
||||
""")
|
||||
SearchHits<SAREntity> findWithRuntimeFields(Integer value, RuntimeField runtimeField1, RuntimeField runtimeField2);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user