diff --git a/src/main/java/org/springframework/data/elasticsearch/core/document/Document.java b/src/main/java/org/springframework/data/elasticsearch/core/document/Document.java index d365e9cb2..b1ddccbf8 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/document/Document.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/document/Document.java @@ -30,12 +30,12 @@ import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** - * A representation of a Elasticsearch document as extended {@link StringObjectMap Map}. All iterators preserve original + * A representation of an Elasticsearch document as extended {@link StringObjectMap Map}. All iterators preserve original * insertion order. *

* Document does not allow {@code null} keys. It allows {@literal null} values. *

- * Implementing classes can bei either mutable or immutable. In case a subclass is immutable, its methods may throw + * Implementing classes can be either mutable or immutable. In case a subclass is immutable, its methods may throw * {@link UnsupportedOperationException} when calling modifying methods. * * @author Mark Paluch diff --git a/src/main/java/org/springframework/data/elasticsearch/repository/query/AbstractElasticsearchRepositoryQuery.java b/src/main/java/org/springframework/data/elasticsearch/repository/query/AbstractElasticsearchRepositoryQuery.java index 80848540b..c42e1a034 100644 --- a/src/main/java/org/springframework/data/elasticsearch/repository/query/AbstractElasticsearchRepositoryQuery.java +++ b/src/main/java/org/springframework/data/elasticsearch/repository/query/AbstractElasticsearchRepositoryQuery.java @@ -25,6 +25,7 @@ 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; +import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider; import org.springframework.data.repository.query.RepositoryQuery; import org.springframework.data.repository.query.ResultProcessor; import org.springframework.data.util.StreamUtils; @@ -47,12 +48,15 @@ public abstract class AbstractElasticsearchRepositoryQuery implements Repository protected ElasticsearchQueryMethod queryMethod; protected final ElasticsearchOperations elasticsearchOperations; protected final ElasticsearchConverter elasticsearchConverter; + protected final QueryMethodEvaluationContextProvider evaluationContextProvider; public AbstractElasticsearchRepositoryQuery(ElasticsearchQueryMethod queryMethod, - ElasticsearchOperations elasticsearchOperations) { + ElasticsearchOperations elasticsearchOperations, + QueryMethodEvaluationContextProvider evaluationContextProvider) { this.queryMethod = queryMethod; this.elasticsearchOperations = elasticsearchOperations; this.elasticsearchConverter = elasticsearchOperations.getElasticsearchConverter(); + this.evaluationContextProvider = evaluationContextProvider; } @Override @@ -128,7 +132,8 @@ public abstract class AbstractElasticsearchRepositoryQuery implements Repository var query = createQuery(parameterAccessor); Assert.notNull(query, "unsupported query"); - queryMethod.addMethodParameter(query, parameterAccessor, elasticsearchOperations.getElasticsearchConverter()); + queryMethod.addMethodParameter(query, parameterAccessor, elasticsearchOperations.getElasticsearchConverter(), + evaluationContextProvider); return query; } diff --git a/src/main/java/org/springframework/data/elasticsearch/repository/query/AbstractReactiveElasticsearchRepositoryQuery.java b/src/main/java/org/springframework/data/elasticsearch/repository/query/AbstractReactiveElasticsearchRepositoryQuery.java index 02826cbe5..8544b7648 100644 --- a/src/main/java/org/springframework/data/elasticsearch/repository/query/AbstractReactiveElasticsearchRepositoryQuery.java +++ b/src/main/java/org/springframework/data/elasticsearch/repository/query/AbstractReactiveElasticsearchRepositoryQuery.java @@ -34,6 +34,7 @@ import org.springframework.data.elasticsearch.repository.query.ReactiveElasticse import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.repository.query.ParameterAccessor; import org.springframework.data.repository.query.QueryMethod; +import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider; import org.springframework.data.repository.query.RepositoryQuery; import org.springframework.data.repository.query.ResultProcessor; import org.springframework.util.Assert; @@ -50,12 +51,15 @@ abstract class AbstractReactiveElasticsearchRepositoryQuery implements Repositor protected final ReactiveElasticsearchQueryMethod queryMethod; private final ReactiveElasticsearchOperations elasticsearchOperations; + protected final QueryMethodEvaluationContextProvider evaluationContextProvider; AbstractReactiveElasticsearchRepositoryQuery(ReactiveElasticsearchQueryMethod queryMethod, - ReactiveElasticsearchOperations elasticsearchOperations) { + ReactiveElasticsearchOperations elasticsearchOperations, + QueryMethodEvaluationContextProvider evaluationContextProvider) { this.queryMethod = queryMethod; this.elasticsearchOperations = elasticsearchOperations; + this.evaluationContextProvider = evaluationContextProvider; } /* @@ -96,7 +100,8 @@ abstract class AbstractReactiveElasticsearchRepositoryQuery implements Repositor var query = createQuery(parameterAccessor); Assert.notNull(query, "unsupported query"); - queryMethod.addMethodParameter(query, parameterAccessor, elasticsearchOperations.getElasticsearchConverter()); + queryMethod.addMethodParameter(query, parameterAccessor, elasticsearchOperations.getElasticsearchConverter(), + evaluationContextProvider); String indexName = queryMethod.getEntityInformation().getIndexName(); IndexCoordinates index = IndexCoordinates.of(indexName); diff --git a/src/main/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchPartQuery.java b/src/main/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchPartQuery.java index 3c5303ad8..cee8e00d2 100644 --- a/src/main/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchPartQuery.java +++ b/src/main/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchPartQuery.java @@ -18,11 +18,9 @@ package org.springframework.data.elasticsearch.repository.query; 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.QueryMethodEvaluationContextProvider; import org.springframework.data.repository.query.parser.PartTree; /** @@ -34,14 +32,16 @@ import org.springframework.data.repository.query.parser.PartTree; * @author Mark Paluch * @author Rasmus Faber-Espensen * @author Peter-Josef Meisch + * @author Haibo Liu */ public class ElasticsearchPartQuery extends AbstractElasticsearchRepositoryQuery { private final PartTree tree; private final MappingContext mappingContext; - public ElasticsearchPartQuery(ElasticsearchQueryMethod method, ElasticsearchOperations elasticsearchOperations) { - super(method, elasticsearchOperations); + public ElasticsearchPartQuery(ElasticsearchQueryMethod method, ElasticsearchOperations elasticsearchOperations, + QueryMethodEvaluationContextProvider evaluationContextProvider) { + super(method, elasticsearchOperations, evaluationContextProvider); this.tree = new PartTree(queryMethod.getName(), queryMethod.getResultProcessor().getReturnedType().getDomainType()); this.mappingContext = elasticsearchConverter.getMappingContext(); } diff --git a/src/main/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchQueryMethod.java b/src/main/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchQueryMethod.java index 2f98e8276..64b272d44 100644 --- a/src/main/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchQueryMethod.java +++ b/src/main/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchQueryMethod.java @@ -24,6 +24,7 @@ import java.util.List; import java.util.stream.Stream; import org.springframework.core.annotation.AnnotatedElementUtils; +import org.springframework.core.convert.ConversionService; import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.data.elasticsearch.annotations.Highlight; import org.springframework.data.elasticsearch.annotations.Query; @@ -41,13 +42,13 @@ 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.elasticsearch.repository.support.QueryStringProcessor; 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.query.QueryMethodEvaluationContextProvider; import org.springframework.data.repository.util.QueryExecutionConverters; import org.springframework.data.repository.util.ReactiveWrapperConverters; import org.springframework.data.util.TypeInformation; @@ -146,9 +147,7 @@ public class ElasticsearchQueryMethod extends QueryMethod { Assert.isTrue(hasAnnotatedHighlight(), "no Highlight annotation present on " + getName()); Assert.notNull(highlightAnnotation, "highlightAnnotation must not be null"); - return new HighlightQuery( - highlightConverter.convert(highlightAnnotation), - getDomainClass()); + return new HighlightQuery(highlightConverter.convert(highlightAnnotation), getDomainClass()); } /** @@ -288,42 +287,46 @@ public class ElasticsearchQueryMethod extends QueryMethod { * @param parameterAccessor the accessor with the query method parameter details * @param converter {@link ElasticsearchConverter} needed to convert entity property names to the Elasticsearch field * names and for parameter conversion when the includes or excludes are defined as parameters + * @param evaluationContextProvider to provide an evaluation context for SpEL evaluation * @return source filter with includes and excludes for a query, {@literal null} when no {@link SourceFilters} * annotation was set on the method. * @since 5.0 */ @Nullable - SourceFilter getSourceFilter(ParameterAccessor parameterAccessor, ElasticsearchConverter converter) { + SourceFilter getSourceFilter(ElasticsearchParametersParameterAccessor parameterAccessor, + ElasticsearchConverter converter, + QueryMethodEvaluationContextProvider evaluationContextProvider) { if (sourceFilters == null || (sourceFilters.includes().length == 0 && sourceFilters.excludes().length == 0)) { return null; } - StringQueryUtil stringQueryUtil = new StringQueryUtil(converter.getConversionService()); + ConversionService conversionService = converter.getConversionService(); FetchSourceFilterBuilder fetchSourceFilterBuilder = new FetchSourceFilterBuilder(); if (sourceFilters.includes().length > 0) { - fetchSourceFilterBuilder - .withIncludes(mapParameters(sourceFilters.includes(), parameterAccessor, stringQueryUtil)); + fetchSourceFilterBuilder.withIncludes(mapParameters(sourceFilters.includes(), parameterAccessor, + conversionService, evaluationContextProvider)); } if (sourceFilters.excludes().length > 0) { - fetchSourceFilterBuilder - .withExcludes(mapParameters(sourceFilters.excludes(), parameterAccessor, stringQueryUtil)); + fetchSourceFilterBuilder.withExcludes(mapParameters(sourceFilters.excludes(), parameterAccessor, + conversionService, evaluationContextProvider)); } return fetchSourceFilterBuilder.build(); } - private String[] mapParameters(String[] source, ParameterAccessor parameterAccessor, - StringQueryUtil stringQueryUtil) { + private String[] mapParameters(String[] source, ElasticsearchParametersParameterAccessor parameterAccessor, + ConversionService conversionService, QueryMethodEvaluationContextProvider evaluationContextProvider) { List fieldNames = new ArrayList<>(); for (String s : source) { if (!s.isBlank()) { - String fieldName = stringQueryUtil.replacePlaceholders(s, parameterAccessor); + String fieldName = new QueryStringProcessor(s, this, conversionService, evaluationContextProvider) + .createQuery(parameterAccessor); // this could be "[\"foo\",\"bar\"]", must be split if (fieldName.startsWith("[") && fieldName.endsWith("]")) { // noinspection RegExpRedundantEscape @@ -367,14 +370,16 @@ public class ElasticsearchQueryMethod extends QueryMethod { } void addMethodParameter(BaseQuery query, ElasticsearchParametersParameterAccessor parameterAccessor, - ElasticsearchConverter elasticsearchConverter) { + ElasticsearchConverter elasticsearchConverter, + QueryMethodEvaluationContextProvider evaluationContextProvider) { if (hasAnnotatedHighlight()) { - query.setHighlightQuery( - getAnnotatedHighlightQuery(new HighlightConverter(parameterAccessor, elasticsearchConverter))); + var highlightQuery = getAnnotatedHighlightQuery(new HighlightConverter(parameterAccessor, + elasticsearchConverter.getConversionService(), evaluationContextProvider, this)); + query.setHighlightQuery(highlightQuery); } - var sourceFilter = getSourceFilter(parameterAccessor, elasticsearchConverter); + var sourceFilter = getSourceFilter(parameterAccessor, elasticsearchConverter, evaluationContextProvider); if (sourceFilter != null) { query.addSourceFilter(sourceFilter); } diff --git a/src/main/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchStringQuery.java b/src/main/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchStringQuery.java index b558dacb4..1ba765a00 100644 --- a/src/main/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchStringQuery.java +++ b/src/main/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchStringQuery.java @@ -19,8 +19,7 @@ import org.springframework.core.convert.ConversionService; import org.springframework.data.elasticsearch.core.ElasticsearchOperations; import org.springframework.data.elasticsearch.core.query.BaseQuery; import org.springframework.data.elasticsearch.core.query.StringQuery; -import org.springframework.data.elasticsearch.repository.support.StringQueryUtil; -import org.springframework.data.elasticsearch.repository.support.spel.QueryStringSpELEvaluator; +import org.springframework.data.elasticsearch.repository.support.QueryStringProcessor; import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider; import org.springframework.util.Assert; @@ -37,17 +36,15 @@ import org.springframework.util.Assert; public class ElasticsearchStringQuery extends AbstractElasticsearchRepositoryQuery { private final String queryString; - private final QueryMethodEvaluationContextProvider evaluationContextProvider; public ElasticsearchStringQuery(ElasticsearchQueryMethod queryMethod, ElasticsearchOperations elasticsearchOperations, String queryString, QueryMethodEvaluationContextProvider evaluationContextProvider) { - super(queryMethod, elasticsearchOperations); + super(queryMethod, elasticsearchOperations, evaluationContextProvider); Assert.notNull(queryString, "Query cannot be empty"); Assert.notNull(evaluationContextProvider, "ExpressionEvaluationContextProvider must not be null"); this.queryString = queryString; - this.evaluationContextProvider = evaluationContextProvider; } @Override @@ -66,15 +63,12 @@ public class ElasticsearchStringQuery extends AbstractElasticsearchRepositoryQue } protected BaseQuery createQuery(ElasticsearchParametersParameterAccessor parameterAccessor) { - ConversionService conversionService = elasticsearchOperations.getElasticsearchConverter().getConversionService(); - var replacedString = new StringQueryUtil(conversionService).replacePlaceholders(this.queryString, - parameterAccessor); - var evaluator = new QueryStringSpELEvaluator(replacedString, parameterAccessor, queryMethod, - evaluationContextProvider, conversionService); - var query = new StringQuery(evaluator.evaluate()); - query.addSort(parameterAccessor.getSort()); - return query; + var processed = new QueryStringProcessor(queryString, queryMethod, conversionService, evaluationContextProvider) + .createQuery(parameterAccessor); + + return new StringQuery(processed) + .addSort(parameterAccessor.getSort()); } } diff --git a/src/main/java/org/springframework/data/elasticsearch/repository/query/HighlightConverter.java b/src/main/java/org/springframework/data/elasticsearch/repository/query/HighlightConverter.java index 6486607aa..8e74b0a30 100644 --- a/src/main/java/org/springframework/data/elasticsearch/repository/query/HighlightConverter.java +++ b/src/main/java/org/springframework/data/elasticsearch/repository/query/HighlightConverter.java @@ -18,13 +18,15 @@ package org.springframework.data.elasticsearch.repository.query; import java.util.Arrays; import java.util.List; -import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter; +import org.springframework.core.convert.ConversionService; import org.springframework.data.elasticsearch.core.query.Query; import org.springframework.data.elasticsearch.core.query.StringQuery; import org.springframework.data.elasticsearch.core.query.highlight.Highlight; import org.springframework.data.elasticsearch.core.query.highlight.HighlightField; import org.springframework.data.elasticsearch.core.query.highlight.HighlightParameters; -import org.springframework.data.elasticsearch.repository.support.StringQueryUtil; +import org.springframework.data.elasticsearch.repository.support.QueryStringProcessor; +import org.springframework.data.repository.query.QueryMethod; +import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider; import org.springframework.util.Assert; /** @@ -35,12 +37,24 @@ import org.springframework.util.Assert; public class HighlightConverter { private final ElasticsearchParametersParameterAccessor parameterAccessor; - private final ElasticsearchConverter elasticsearchConverter; + private final ConversionService conversionService; + private final QueryMethodEvaluationContextProvider evaluationContextProvider; + private final QueryMethod queryMethod; HighlightConverter(ElasticsearchParametersParameterAccessor parameterAccessor, - ElasticsearchConverter elasticsearchConverter) { + ConversionService conversionService, + QueryMethodEvaluationContextProvider evaluationContextProvider, + QueryMethod queryMethod) { + + Assert.notNull(parameterAccessor, "parameterAccessor must not be null"); + Assert.notNull(conversionService, "conversionService must not be null"); + Assert.notNull(evaluationContextProvider, "evaluationContextProvider must not be null"); + Assert.notNull(queryMethod, "queryMethod must not be null"); + this.parameterAccessor = parameterAccessor; - this.elasticsearchConverter = elasticsearchConverter; + this.conversionService = conversionService; + this.evaluationContextProvider = evaluationContextProvider; + this.queryMethod = queryMethod; } /** @@ -58,10 +72,10 @@ public class HighlightConverter { // replace placeholders in highlight query with actual parameters Query highlightQuery = null; if (!parameters.highlightQuery().value().isEmpty()) { - String rawString = parameters.highlightQuery().value(); - String queryString = new StringQueryUtil(elasticsearchConverter.getConversionService()) - .replacePlaceholders(rawString, parameterAccessor); - highlightQuery = new StringQuery(queryString); + String rawQuery = parameters.highlightQuery().value(); + String query = new QueryStringProcessor(rawQuery, queryMethod, conversionService, evaluationContextProvider) + .createQuery(parameterAccessor); + highlightQuery = new StringQuery(query); } HighlightParameters highlightParameters = HighlightParameters.builder() // diff --git a/src/main/java/org/springframework/data/elasticsearch/repository/query/ReactiveElasticsearchStringQuery.java b/src/main/java/org/springframework/data/elasticsearch/repository/query/ReactiveElasticsearchStringQuery.java index 368577c47..c15b4dfb2 100644 --- a/src/main/java/org/springframework/data/elasticsearch/repository/query/ReactiveElasticsearchStringQuery.java +++ b/src/main/java/org/springframework/data/elasticsearch/repository/query/ReactiveElasticsearchStringQuery.java @@ -19,10 +19,8 @@ import org.springframework.core.convert.ConversionService; import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations; import org.springframework.data.elasticsearch.core.query.BaseQuery; import org.springframework.data.elasticsearch.core.query.StringQuery; -import org.springframework.data.elasticsearch.repository.support.StringQueryUtil; -import org.springframework.data.elasticsearch.repository.support.spel.QueryStringSpELEvaluator; +import org.springframework.data.elasticsearch.repository.support.QueryStringProcessor; import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider; -import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.util.Assert; /** @@ -37,16 +35,14 @@ public class ReactiveElasticsearchStringQuery extends AbstractReactiveElasticsea private final QueryMethodEvaluationContextProvider evaluationContextProvider; public ReactiveElasticsearchStringQuery(ReactiveElasticsearchQueryMethod queryMethod, - ReactiveElasticsearchOperations operations, SpelExpressionParser expressionParser, - QueryMethodEvaluationContextProvider evaluationContextProvider) { + ReactiveElasticsearchOperations operations, QueryMethodEvaluationContextProvider evaluationContextProvider) { - this(queryMethod.getAnnotatedQuery(), queryMethod, operations, expressionParser, evaluationContextProvider); + this(queryMethod.getAnnotatedQuery(), queryMethod, operations, evaluationContextProvider); } public ReactiveElasticsearchStringQuery(String query, ReactiveElasticsearchQueryMethod queryMethod, - ReactiveElasticsearchOperations operations, SpelExpressionParser expressionParser, - QueryMethodEvaluationContextProvider evaluationContextProvider) { - super(queryMethod, operations); + ReactiveElasticsearchOperations operations, QueryMethodEvaluationContextProvider evaluationContextProvider) { + super(queryMethod, operations, evaluationContextProvider); Assert.notNull(query, "query must not be null"); Assert.notNull(evaluationContextProvider, "evaluationContextProvider must not be null"); @@ -57,15 +53,11 @@ public class ReactiveElasticsearchStringQuery extends AbstractReactiveElasticsea @Override protected BaseQuery createQuery(ElasticsearchParametersParameterAccessor parameterAccessor) { - String queryString = new StringQueryUtil( - getElasticsearchOperations().getElasticsearchConverter().getConversionService()) - .replacePlaceholders(this.query, parameterAccessor); - ConversionService conversionService = getElasticsearchOperations().getElasticsearchConverter() .getConversionService(); - QueryStringSpELEvaluator evaluator = new QueryStringSpELEvaluator(queryString, parameterAccessor, queryMethod, - evaluationContextProvider, conversionService); - return new StringQuery(evaluator.evaluate()); + String processed = new QueryStringProcessor(query, queryMethod, conversionService, evaluationContextProvider) + .createQuery(parameterAccessor); + return new StringQuery(processed); } @Override diff --git a/src/main/java/org/springframework/data/elasticsearch/repository/query/ReactivePartTreeElasticsearchQuery.java b/src/main/java/org/springframework/data/elasticsearch/repository/query/ReactivePartTreeElasticsearchQuery.java index 1fd7f1308..1da8c29a6 100644 --- a/src/main/java/org/springframework/data/elasticsearch/repository/query/ReactivePartTreeElasticsearchQuery.java +++ b/src/main/java/org/springframework/data/elasticsearch/repository/query/ReactivePartTreeElasticsearchQuery.java @@ -19,12 +19,14 @@ import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperatio import org.springframework.data.elasticsearch.core.query.BaseQuery; import org.springframework.data.elasticsearch.core.query.CriteriaQuery; import org.springframework.data.elasticsearch.repository.query.parser.ElasticsearchQueryCreator; +import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider; import org.springframework.data.repository.query.ResultProcessor; import org.springframework.data.repository.query.parser.PartTree; /** * @author Christoph Strobl * @author Peter-Josef Meisch + * @author Haibo Liu * @since 3.2 */ public class ReactivePartTreeElasticsearchQuery extends AbstractReactiveElasticsearchRepositoryQuery { @@ -32,8 +34,9 @@ public class ReactivePartTreeElasticsearchQuery extends AbstractReactiveElastics private final PartTree tree; public ReactivePartTreeElasticsearchQuery(ReactiveElasticsearchQueryMethod queryMethod, - ReactiveElasticsearchOperations elasticsearchOperations) { - super(queryMethod, elasticsearchOperations); + ReactiveElasticsearchOperations elasticsearchOperations, + QueryMethodEvaluationContextProvider evaluationContextProvider) { + super(queryMethod, elasticsearchOperations, evaluationContextProvider); ResultProcessor processor = queryMethod.getResultProcessor(); this.tree = new PartTree(queryMethod.getName(), processor.getReturnedType().getDomainType()); diff --git a/src/main/java/org/springframework/data/elasticsearch/repository/support/ElasticsearchRepositoryFactory.java b/src/main/java/org/springframework/data/elasticsearch/repository/support/ElasticsearchRepositoryFactory.java index 698183a43..b7c5f29ec 100644 --- a/src/main/java/org/springframework/data/elasticsearch/repository/support/ElasticsearchRepositoryFactory.java +++ b/src/main/java/org/springframework/data/elasticsearch/repository/support/ElasticsearchRepositoryFactory.java @@ -128,7 +128,7 @@ public class ElasticsearchRepositoryFactory extends RepositoryFactorySupport { return new ElasticsearchStringQuery(queryMethod, elasticsearchOperations, queryMethod.getAnnotatedQuery(), evaluationContextProvider); } - return new ElasticsearchPartQuery(queryMethod, elasticsearchOperations); + return new ElasticsearchPartQuery(queryMethod, elasticsearchOperations, evaluationContextProvider); } } diff --git a/src/main/java/org/springframework/data/elasticsearch/repository/support/StringQueryUtil.java b/src/main/java/org/springframework/data/elasticsearch/repository/support/QueryStringPlaceholderReplacer.java similarity index 87% rename from src/main/java/org/springframework/data/elasticsearch/repository/support/StringQueryUtil.java rename to src/main/java/org/springframework/data/elasticsearch/repository/support/QueryStringPlaceholderReplacer.java index ff429a26d..7d93623cd 100644 --- a/src/main/java/org/springframework/data/elasticsearch/repository/support/StringQueryUtil.java +++ b/src/main/java/org/springframework/data/elasticsearch/repository/support/QueryStringPlaceholderReplacer.java @@ -26,23 +26,32 @@ import org.springframework.util.Assert; import org.springframework.util.NumberUtils; /** + * To replace the placeholders like `?0`, `?1, `?2` of the query string. + * * @author Peter-Josef Meisch * @author Niklas Herder * @author Haibo Liu */ -final public class StringQueryUtil { +final public class QueryStringPlaceholderReplacer { private static final Pattern PARAMETER_PLACEHOLDER = Pattern.compile("\\?(\\d+)"); private final ConversionService conversionService; - public StringQueryUtil(ConversionService conversionService) { + public QueryStringPlaceholderReplacer(ConversionService conversionService) { Assert.notNull(conversionService, "conversionService must not be null"); this.conversionService = ElasticsearchQueryValueConversionService.getInstance(conversionService); } + /** + * Replace the placeholders of the query string. + * + * @param input raw query string + * @param accessor parameter info + * @return a plain string with placeholders replaced + */ public String replacePlaceholders(String input, ParameterAccessor accessor) { Matcher matcher = PARAMETER_PLACEHOLDER.matcher(input); diff --git a/src/main/java/org/springframework/data/elasticsearch/repository/support/QueryStringProcessor.java b/src/main/java/org/springframework/data/elasticsearch/repository/support/QueryStringProcessor.java new file mode 100644 index 000000000..8ac45acb7 --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/repository/support/QueryStringProcessor.java @@ -0,0 +1,67 @@ +/* + * Copyright 2024 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.support; + +import org.springframework.core.convert.ConversionService; +import org.springframework.data.elasticsearch.repository.query.ElasticsearchParametersParameterAccessor; +import org.springframework.data.elasticsearch.repository.support.spel.QueryStringSpELEvaluator; +import org.springframework.data.repository.query.QueryMethod; +import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider; +import org.springframework.util.Assert; + +/** + * To process query strings with placeholder replacement and SpEL evaluation by {@link QueryStringPlaceholderReplacer} + * and {@link QueryStringSpELEvaluator}. + * + * @since 5.3 + * @author Haibo Liu + */ +public class QueryStringProcessor { + + private final String query; + private final QueryMethod queryMethod; + private final ConversionService conversionService; + private final QueryMethodEvaluationContextProvider evaluationContextProvider; + + public QueryStringProcessor(String query, QueryMethod queryMethod, ConversionService conversionService, + QueryMethodEvaluationContextProvider evaluationContextProvider) { + + Assert.notNull(query, "query must not be null"); + Assert.notNull(queryMethod, "queryMethod must not be null"); + Assert.notNull(conversionService, "conversionService must not be null"); + Assert.notNull(evaluationContextProvider, "evaluationContextProvider must not be null"); + + this.query = query; + this.queryMethod = queryMethod; + this.conversionService = conversionService; + this.evaluationContextProvider = evaluationContextProvider; + } + + /** + * Process the query string with placeholder replacement and SpEL evaluation. + * + * @param parameterAccessor parameter info + * @return processed string + */ + public String createQuery(ElasticsearchParametersParameterAccessor parameterAccessor) { + String queryString = new QueryStringPlaceholderReplacer(conversionService) + .replacePlaceholders(query, parameterAccessor); + + QueryStringSpELEvaluator evaluator = new QueryStringSpELEvaluator(queryString, parameterAccessor, queryMethod, + evaluationContextProvider, conversionService); + return evaluator.evaluate(); + } +} diff --git a/src/main/java/org/springframework/data/elasticsearch/repository/support/ReactiveElasticsearchRepositoryFactory.java b/src/main/java/org/springframework/data/elasticsearch/repository/support/ReactiveElasticsearchRepositoryFactory.java index e3f31093c..d4f65a7aa 100644 --- a/src/main/java/org/springframework/data/elasticsearch/repository/support/ReactiveElasticsearchRepositoryFactory.java +++ b/src/main/java/org/springframework/data/elasticsearch/repository/support/ReactiveElasticsearchRepositoryFactory.java @@ -39,7 +39,6 @@ import org.springframework.data.repository.query.QueryLookupStrategy.Key; import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider; import org.springframework.data.repository.query.ReactiveQueryByExampleExecutor; import org.springframework.data.repository.query.RepositoryQuery; -import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.lang.Nullable; import org.springframework.util.Assert; @@ -50,12 +49,11 @@ import org.springframework.util.Assert; * @author Christoph Strobl * @author Ivan Greene * @author Ezequiel AntĂșnez Camacho + * @author Haibo Liu * @since 3.2 */ public class ReactiveElasticsearchRepositoryFactory extends ReactiveRepositoryFactorySupport { - private static final SpelExpressionParser EXPRESSION_PARSER = new SpelExpressionParser(); - private final ReactiveElasticsearchOperations operations; private final MappingContext, ElasticsearchPersistentProperty> mappingContext; @@ -163,13 +161,12 @@ public class ReactiveElasticsearchRepositoryFactory extends ReactiveRepositoryFa if (namedQueries.hasQuery(namedQueryName)) { String namedQuery = namedQueries.getQuery(namedQueryName); - return new ReactiveElasticsearchStringQuery(namedQuery, queryMethod, operations, EXPRESSION_PARSER, + return new ReactiveElasticsearchStringQuery(namedQuery, queryMethod, operations, evaluationContextProvider); } else if (queryMethod.hasAnnotatedQuery()) { - return new ReactiveElasticsearchStringQuery(queryMethod, operations, EXPRESSION_PARSER, - evaluationContextProvider); + return new ReactiveElasticsearchStringQuery(queryMethod, operations, evaluationContextProvider); } else { - return new ReactivePartTreeElasticsearchQuery(queryMethod, operations); + return new ReactivePartTreeElasticsearchQuery(queryMethod, operations, evaluationContextProvider); } } } diff --git a/src/test/java/org/springframework/data/elasticsearch/core/query/ElasticsearchPartQueryIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/query/ElasticsearchPartQueryIntegrationTests.java index b41eee474..42ffa5f39 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/query/ElasticsearchPartQueryIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/query/ElasticsearchPartQueryIntegrationTests.java @@ -36,6 +36,7 @@ import org.springframework.data.elasticsearch.repository.query.ElasticsearchPart import org.springframework.data.elasticsearch.repository.query.ElasticsearchQueryMethod; import org.springframework.data.projection.SpelAwareProxyProjectionFactory; import org.springframework.data.repository.core.support.DefaultRepositoryMetadata; +import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider; import org.springframework.lang.Nullable; /** @@ -44,6 +45,7 @@ import org.springframework.lang.Nullable; * kept package private. * * @author Peter-Josef Meisch + * @author Haibo Liu */ @SpringIntegrationTest public abstract class ElasticsearchPartQueryIntegrationTests { @@ -647,7 +649,8 @@ public abstract class ElasticsearchPartQueryIntegrationTests { ElasticsearchQueryMethod queryMethod = new ElasticsearchQueryMethod(method, new DefaultRepositoryMetadata(SampleRepository.class), new SpelAwareProxyProjectionFactory(), operations.getElasticsearchConverter().getMappingContext()); - ElasticsearchPartQuery partQuery = new ElasticsearchPartQuery(queryMethod, operations); + ElasticsearchPartQuery partQuery = new ElasticsearchPartQuery(queryMethod, operations, + QueryMethodEvaluationContextProvider.DEFAULT); Query query = partQuery.createQuery(parameters); return buildQueryString(query, Book.class); } diff --git a/src/test/java/org/springframework/data/elasticsearch/repositories/custommethod/CustomMethodRepositoryIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/repositories/custommethod/CustomMethodRepositoryIntegrationTests.java index 078cdb684..730c55116 100644 --- a/src/test/java/org/springframework/data/elasticsearch/repositories/custommethod/CustomMethodRepositoryIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/repositories/custommethod/CustomMethodRepositoryIntegrationTests.java @@ -1740,6 +1740,26 @@ public abstract class CustomMethodRepositoryIntegrationTests { assertThat(highlightXyzHit.getHighlightField("type")).hasSize(1).contains("abc xyz"); } + @Test + void shouldReturnDifferentHighlightsOnAnnotatedStringQueryMethodSpEL() { + List entities = createSampleEntities("abc xyz", 2); + repository.saveAll(entities); + + // when + SearchHits highlightAbcHits = repository.queryByStringWithSeparateHighlightSpEL("abc", "abc"); + + assertThat(highlightAbcHits.getTotalHits()).isEqualTo(2); + SearchHit highlightAbcHit = highlightAbcHits.getSearchHit(0); + assertThat(highlightAbcHit.getHighlightField("type")).hasSize(1).contains("abc xyz"); + + // when + SearchHits highlightXyzHits = repository.queryByStringWithSeparateHighlightSpEL("abc", "xyz"); + + assertThat(highlightXyzHits.getTotalHits()).isEqualTo(2); + SearchHit highlightXyzHit = highlightXyzHits.getSearchHit(0); + assertThat(highlightXyzHit.getHighlightField("type")).hasSize(1).contains("abc xyz"); + } + @Test // DATAES-734 void shouldUseGeoSortParameter() { GeoPoint munich = new GeoPoint(48.137154, 11.5761247); @@ -1938,6 +1958,28 @@ public abstract class CustomMethodRepositoryIntegrationTests { assertThat(foundEntity.getKeyword()).isNull(); } + @Test + @DisplayName("should use sourceIncludes from parameter SpEL") + void shouldUseSourceIncludesFromParameterSpEL() { + + SampleEntity entity = new SampleEntity(); + entity.setId("42"); + entity.setMessage("message"); + entity.setCustomFieldNameMessage("customFieldNameMessage"); + entity.setType("type"); + entity.setKeyword("keyword"); + repository.save(entity); + + var searchHits = repository.queryBy(List.of("message", "customFieldNameMessage")); + + assertThat(searchHits.hasSearchHits()).isTrue(); + var foundEntity = searchHits.getSearchHit(0).getContent(); + assertThat(foundEntity.getMessage()).isEqualTo("message"); + assertThat(foundEntity.getCustomFieldNameMessage()).isEqualTo("customFieldNameMessage"); + assertThat(foundEntity.getType()).isNull(); + assertThat(foundEntity.getKeyword()).isNull(); + } + @Test // #2146 @DisplayName("should use sourceExcludes from annotation") void shouldUseSourceExcludesFromAnnotation() { @@ -1982,6 +2024,28 @@ public abstract class CustomMethodRepositoryIntegrationTests { assertThat(foundEntity.getKeyword()).isNull(); } + @Test + @DisplayName("should use source excludes from parameter SpEL") + void shouldUseSourceExcludesFromParameterSpEL() { + + SampleEntity entity = new SampleEntity(); + entity.setId("42"); + entity.setMessage("message"); + entity.setCustomFieldNameMessage("customFieldNameMessage"); + entity.setType("type"); + entity.setKeyword("keyword"); + repository.save(entity); + + var searchHits = repository.getBy(List.of("type", "keyword")); + + assertThat(searchHits.hasSearchHits()).isTrue(); + var foundEntity = searchHits.getSearchHit(0).getContent(); + assertThat(foundEntity.getMessage()).isEqualTo("message"); + assertThat(foundEntity.getCustomFieldNameMessage()).isEqualTo("customFieldNameMessage"); + assertThat(foundEntity.getType()).isNull(); + assertThat(foundEntity.getKeyword()).isNull(); + } + private List createSampleEntities(String type, int numberOfEntities) { List entities = new ArrayList<>(); @@ -2276,6 +2340,37 @@ public abstract class CustomMethodRepositoryIntegrationTests { """))) SearchHits queryByStringWithSeparateHighlight(String type, String highlight); + @Query(""" + { + "bool":{ + "must":[ + { + "match":{ + "type":"#{#type}" + } + } + ] + } + } + """) + @Highlight( + fields = { @HighlightField(name = "type") }, + parameters = @HighlightParameters( + highlightQuery = @Query(""" + { + "bool":{ + "must":[ + { + "match":{ + "type":"#{#highlight}" + } + } + ] + } + } + """))) + SearchHits queryByStringWithSeparateHighlightSpEL(String type, String highlight); + List> queryByMessage(String message); Stream> readByMessage(String message); @@ -2307,6 +2402,9 @@ public abstract class CustomMethodRepositoryIntegrationTests { @SourceFilters(includes = "?0") SearchHits searchBy(Collection sourceIncludes); + @SourceFilters(includes = "#{#sourceIncludes}") + SearchHits queryBy(Collection sourceIncludes); + @Query(""" { "match_all": {} @@ -2317,6 +2415,9 @@ public abstract class CustomMethodRepositoryIntegrationTests { @SourceFilters(excludes = "?0") SearchHits findBy(Collection sourceExcludes); + + @SourceFilters(excludes = "#{#sourceExcludes}") + SearchHits getBy(Collection sourceExcludes); } public interface SampleStreamingCustomMethodRepository extends ElasticsearchRepository { diff --git a/src/test/java/org/springframework/data/elasticsearch/repository/query/ReactiveElasticsearchStringQueryUnitTests.java b/src/test/java/org/springframework/data/elasticsearch/repository/query/ReactiveElasticsearchStringQueryUnitTests.java index e649fe792..e51674ab8 100644 --- a/src/test/java/org/springframework/data/elasticsearch/repository/query/ReactiveElasticsearchStringQueryUnitTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/repository/query/ReactiveElasticsearchStringQueryUnitTests.java @@ -52,7 +52,6 @@ import org.springframework.data.projection.SpelAwareProxyProjectionFactory; import org.springframework.data.repository.Repository; import org.springframework.data.repository.core.support.DefaultRepositoryMetadata; import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider; -import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.lang.Nullable; /** @@ -63,8 +62,6 @@ import org.springframework.lang.Nullable; @ExtendWith(MockitoExtension.class) public class ReactiveElasticsearchStringQueryUnitTests extends ElasticsearchStringQueryUnitTestBase { - SpelExpressionParser PARSER = new SpelExpressionParser(); - @Mock ReactiveElasticsearchOperations operations; @BeforeEach @@ -377,7 +374,7 @@ public class ReactiveElasticsearchStringQueryUnitTests extends ElasticsearchStri } private ReactiveElasticsearchStringQuery queryForMethod(ReactiveElasticsearchQueryMethod queryMethod) { - return new ReactiveElasticsearchStringQuery(queryMethod, operations, PARSER, + return new ReactiveElasticsearchStringQuery(queryMethod, operations, QueryMethodEvaluationContextProvider.DEFAULT); } diff --git a/src/test/java/org/springframework/data/elasticsearch/repository/support/SimpleReactiveElasticsearchRepositoryIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/repository/support/SimpleReactiveElasticsearchRepositoryIntegrationTests.java index a516d48c6..2ade46a6d 100644 --- a/src/test/java/org/springframework/data/elasticsearch/repository/support/SimpleReactiveElasticsearchRepositoryIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/repository/support/SimpleReactiveElasticsearchRepositoryIntegrationTests.java @@ -51,6 +51,7 @@ import org.springframework.data.elasticsearch.annotations.Field; import org.springframework.data.elasticsearch.annotations.FieldType; import org.springframework.data.elasticsearch.annotations.Highlight; import org.springframework.data.elasticsearch.annotations.HighlightField; +import org.springframework.data.elasticsearch.annotations.HighlightParameters; import org.springframework.data.elasticsearch.annotations.Query; import org.springframework.data.elasticsearch.annotations.SourceFilters; import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations; @@ -436,6 +437,60 @@ abstract class SimpleReactiveElasticsearchRepositoryIntegrationTests { .verifyComplete(); } + @Test + void shouldReturnDifferentHighlightsOnAnnotatedStringQueryMethod() { + + bulkIndex(new SampleEntity("id-one", "abc xyz"), // + new SampleEntity("id-two", "abc xyz"), // + new SampleEntity("id-three", "abc xyz")) // + .block(); + + repository.queryByMessageWithSeparateHighlight("abc", "abc") // + .as(StepVerifier::create) // + .expectNextMatches(searchHit -> { + List hitHighlightField = searchHit.getHighlightField("message"); + return hitHighlightField.size() == 1 && hitHighlightField.get(0).equals("abc xyz"); + }) // + .expectNextCount(2) // + .verifyComplete(); + + repository.queryByMessageWithSeparateHighlight("abc", "xyz") // + .as(StepVerifier::create) // + .expectNextMatches(searchHit -> { + List hitHighlightField = searchHit.getHighlightField("message"); + return hitHighlightField.size() == 1 && hitHighlightField.get(0).equals("abc xyz"); + }) // + .expectNextCount(2) // + .verifyComplete(); + } + + @Test + void shouldReturnDifferentHighlightsOnAnnotatedStringQueryMethodSpEL() { + + bulkIndex(new SampleEntity("id-one", "abc xyz"), // + new SampleEntity("id-two", "abc xyz"), // + new SampleEntity("id-three", "abc xyz")) // + .block(); + + repository.queryByMessageWithSeparateHighlightSpEL("abc", "abc") // + .as(StepVerifier::create) // + .expectNextMatches(searchHit -> { + List hitHighlightField = searchHit.getHighlightField("message"); + return hitHighlightField.size() == 1 && hitHighlightField.get(0).equals("abc xyz"); + }) // + .expectNextCount(2) // + .verifyComplete(); + + repository.queryByMessageWithSeparateHighlightSpEL("abc", "xyz") // + .as(StepVerifier::create) // + .expectNextMatches(searchHit -> { + List hitHighlightField = searchHit.getHighlightField("message"); + return hitHighlightField.size() == 1 && hitHighlightField.get(0).equals("abc xyz"); + }) // + .expectNextCount(2) // + .verifyComplete(); + } + @Test // DATAES-519, DATAES-767, DATAES-822 void countShouldErrorWhenIndexDoesNotExist() { @@ -860,6 +915,29 @@ abstract class SimpleReactiveElasticsearchRepositoryIntegrationTests { .verifyComplete(); } + @Test + @DisplayName("should use sourceIncludes from parameter SpEL") + void shouldUseSourceIncludesFromParameterSpEL() { + + var entity = new SampleEntity(); + entity.setId("42"); + entity.setMessage("message"); + entity.setCustomFieldNameMessage("customFieldNameMessage"); + entity.setType("type"); + entity.setKeyword("keyword"); + repository.save(entity).block(); + + repository.queryBy(List.of("message", "customFieldNameMessage")) // + .as(StepVerifier::create) // + .consumeNextWith(foundEntity -> { // + assertThat(foundEntity.getMessage()).isEqualTo("message"); // + assertThat(foundEntity.getCustomFieldNameMessage()).isEqualTo("customFieldNameMessage"); // + assertThat(foundEntity.getType()).isNull(); // + assertThat(foundEntity.getKeyword()).isNull(); // + }) // + .verifyComplete(); + } + @Test // #2146 @DisplayName("should use sourceExcludes from annotation") void shouldUseSourceExcludesFromAnnotation() { @@ -906,6 +984,29 @@ abstract class SimpleReactiveElasticsearchRepositoryIntegrationTests { .verifyComplete(); } + @Test + @DisplayName("should use source excludes from parameter SpEL") + void shouldUseSourceExcludesFromParameterSpEL() { + + var entity = new SampleEntity(); + entity.setId("42"); + entity.setMessage("message"); + entity.setCustomFieldNameMessage("customFieldNameMessage"); + entity.setType("type"); + entity.setKeyword("keyword"); + repository.save(entity).block(); + + repository.getBy(List.of("type", "keyword")) // + .as(StepVerifier::create) // + .consumeNextWith(foundEntity -> { // + assertThat(foundEntity.getMessage()).isEqualTo("message"); // + assertThat(foundEntity.getCustomFieldNameMessage()).isEqualTo("customFieldNameMessage"); // + assertThat(foundEntity.getType()).isNull(); // + assertThat(foundEntity.getKeyword()).isNull(); // + }) // + .verifyComplete(); + } + @Test // #2496 @DisplayName("should save data from Flux and return saved data in a flux") void shouldSaveDataFromFluxAndReturnSavedDataInAFlux() { @@ -947,6 +1048,68 @@ abstract class SimpleReactiveElasticsearchRepositoryIntegrationTests { @Highlight(fields = { @HighlightField(name = "message") }) Flux> queryByMessageWithString(String message); + @Query(""" + { + "bool":{ + "must":[ + { + "match":{ + "message":"?0" + } + } + ] + } + } + """) + @Highlight( + fields = { @HighlightField(name = "message") }, + parameters = @HighlightParameters( + highlightQuery = @Query(""" + { + "bool":{ + "must":[ + { + "match":{ + "message":"?1" + } + } + ] + } + } + """))) + Flux> queryByMessageWithSeparateHighlight(String message, String highlight); + + @Query(""" + { + "bool":{ + "must":[ + { + "match":{ + "message":"#{#message}" + } + } + ] + } + } + """) + @Highlight( + fields = { @HighlightField(name = "message") }, + parameters = @HighlightParameters( + highlightQuery = @Query(""" + { + "bool":{ + "must":[ + { + "match":{ + "message":"#{#highlight}" + } + } + ] + } + } + """))) + Flux> queryByMessageWithSeparateHighlightSpEL(String message, String highlight); + @Query("{ \"bool\" : { \"must\" : { \"term\" : { \"message\" : \"?0\" } } } }") Flux findAllViaAnnotatedQueryByMessageLike(String message); @@ -1083,6 +1246,9 @@ abstract class SimpleReactiveElasticsearchRepositoryIntegrationTests { @SourceFilters(includes = "?0") Flux searchBy(Collection sourceIncludes); + @SourceFilters(includes = "#{#sourceIncludes}") + Flux queryBy(Collection sourceIncludes); + @Query(""" { "match_all": {} @@ -1093,6 +1259,9 @@ abstract class SimpleReactiveElasticsearchRepositoryIntegrationTests { @SourceFilters(excludes = "?0") Flux findBy(Collection sourceExcludes); + + @SourceFilters(excludes = "#{#sourceExcludes}") + Flux getBy(Collection sourceExcludes); } @Document(indexName = "#{@indexNameProvider.indexName()}")