mirror of
https://github.com/spring-projects/spring-data-elasticsearch.git
synced 2025-06-29 15:22:11 +00:00
Implement SourceFilters annotation.
Original Pull Request #2254 Closes #1280 Closes #2062 Closes #2146 Closes #2147 Closes #2151
This commit is contained in:
parent
954d8b0f97
commit
cf135f4cdd
@ -15,8 +15,14 @@
|
|||||||
*/
|
*/
|
||||||
package org.springframework.data.elasticsearch.annotations;
|
package org.springframework.data.elasticsearch.annotations;
|
||||||
|
|
||||||
|
import java.lang.annotation.Documented;
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
import org.springframework.core.annotation.AliasFor;
|
||||||
import org.springframework.data.annotation.QueryAnnotation;
|
import org.springframework.data.annotation.QueryAnnotation;
|
||||||
import java.lang.annotation.*;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Query
|
* Query
|
||||||
@ -34,10 +40,18 @@ import java.lang.annotation.*;
|
|||||||
public @interface Query {
|
public @interface Query {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Elasticsearch query to be used when executing query. May contain placeholders eg. ?0
|
* @return Elasticsearch query to be used when executing query. May contain placeholders eg. ?0. Alias for query.
|
||||||
*/
|
*/
|
||||||
|
@AliasFor("query")
|
||||||
String value() default "";
|
String value() default "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Elasticsearch query to be used when executing query. May contain placeholders eg. ?0. Alias for value
|
||||||
|
* @since 5.0
|
||||||
|
*/
|
||||||
|
@AliasFor("value")
|
||||||
|
String query() default "";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Named Query Named looked up by repository.
|
* Named Query Named looked up by repository.
|
||||||
*
|
*
|
||||||
|
@ -0,0 +1,73 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2022 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.annotations;
|
||||||
|
|
||||||
|
import java.lang.annotation.Documented;
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This annotation can be placed on repository methods to define the properties that should be requested from
|
||||||
|
* Elasticsearch when the method is run.
|
||||||
|
*
|
||||||
|
* @author Alexander Torres
|
||||||
|
* @author Peter-Josef Meisch
|
||||||
|
* @since 5.0
|
||||||
|
*/
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
|
||||||
|
@Documented
|
||||||
|
public @interface SourceFilters {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Properties to be requested from Elasticsearch to be included in the response. These can be passed in as literals
|
||||||
|
* like
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* {@code @SourceFilters(includes = {"property1", "property2"})}
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* or as a parameterized value
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* {@code @SourceFilters(includes = "?0")}
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* when the list of properties is passed as a function parameter.
|
||||||
|
*/
|
||||||
|
String[] includes() default "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Properties to be requested from Elasticsearch to be excluded in the response. These can be passed in as literals
|
||||||
|
* like
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* {@code @SourceFilters(excludes = {"property1", "property2"})}
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* or as a parameterized value
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* {@code @SourceFilters(excludes = "?0")}
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* when the list of properties is passed as a function parameter.
|
||||||
|
*/
|
||||||
|
String[] excludes() default "";
|
||||||
|
}
|
@ -16,6 +16,9 @@
|
|||||||
package org.springframework.data.elasticsearch.repository.query;
|
package org.springframework.data.elasticsearch.repository.query;
|
||||||
|
|
||||||
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
|
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
|
||||||
|
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
|
||||||
|
import org.springframework.data.elasticsearch.core.query.Query;
|
||||||
|
import org.springframework.data.repository.query.ParameterAccessor;
|
||||||
import org.springframework.data.repository.query.QueryMethod;
|
import org.springframework.data.repository.query.QueryMethod;
|
||||||
import org.springframework.data.repository.query.RepositoryQuery;
|
import org.springframework.data.repository.query.RepositoryQuery;
|
||||||
|
|
||||||
@ -31,12 +34,14 @@ public abstract class AbstractElasticsearchRepositoryQuery implements Repository
|
|||||||
|
|
||||||
protected static final int DEFAULT_STREAM_BATCH_SIZE = 500;
|
protected static final int DEFAULT_STREAM_BATCH_SIZE = 500;
|
||||||
protected ElasticsearchQueryMethod queryMethod;
|
protected ElasticsearchQueryMethod queryMethod;
|
||||||
protected ElasticsearchOperations elasticsearchOperations;
|
protected final ElasticsearchOperations elasticsearchOperations;
|
||||||
|
protected final ElasticsearchConverter elasticsearchConverter;
|
||||||
|
|
||||||
public AbstractElasticsearchRepositoryQuery(ElasticsearchQueryMethod queryMethod,
|
public AbstractElasticsearchRepositoryQuery(ElasticsearchQueryMethod queryMethod,
|
||||||
ElasticsearchOperations elasticsearchOperations) {
|
ElasticsearchOperations elasticsearchOperations) {
|
||||||
this.queryMethod = queryMethod;
|
this.queryMethod = queryMethod;
|
||||||
this.elasticsearchOperations = elasticsearchOperations;
|
this.elasticsearchOperations = elasticsearchOperations;
|
||||||
|
this.elasticsearchConverter = elasticsearchOperations.getElasticsearchConverter();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -49,4 +54,19 @@ public abstract class AbstractElasticsearchRepositoryQuery implements Repository
|
|||||||
* @since 4.2
|
* @since 4.2
|
||||||
*/
|
*/
|
||||||
public abstract boolean isCountQuery();
|
public abstract boolean isCountQuery();
|
||||||
|
|
||||||
|
protected void prepareQuery(Query query, Class<?> clazz, ParameterAccessor parameterAccessor) {
|
||||||
|
|
||||||
|
elasticsearchConverter.updateQuery(query, clazz);
|
||||||
|
|
||||||
|
if (queryMethod.hasAnnotatedHighlight()) {
|
||||||
|
query.setHighlightQuery(queryMethod.getAnnotatedHighlightQuery());
|
||||||
|
}
|
||||||
|
|
||||||
|
var sourceFilter = queryMethod.getSourceFilter(parameterAccessor,
|
||||||
|
elasticsearchOperations.getElasticsearchConverter());
|
||||||
|
if (sourceFilter != null) {
|
||||||
|
query.addSourceFilter(sourceFilter);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,7 @@ import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersiste
|
|||||||
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||||
import org.springframework.data.elasticsearch.core.query.ByQueryResponse;
|
import org.springframework.data.elasticsearch.core.query.ByQueryResponse;
|
||||||
import org.springframework.data.elasticsearch.core.query.Query;
|
import org.springframework.data.elasticsearch.core.query.Query;
|
||||||
|
import org.springframework.data.elasticsearch.core.query.SourceFilter;
|
||||||
import org.springframework.data.elasticsearch.repository.query.ReactiveElasticsearchQueryExecution.ResultProcessingConverter;
|
import org.springframework.data.elasticsearch.repository.query.ReactiveElasticsearchQueryExecution.ResultProcessingConverter;
|
||||||
import org.springframework.data.elasticsearch.repository.query.ReactiveElasticsearchQueryExecution.ResultProcessingExecution;
|
import org.springframework.data.elasticsearch.repository.query.ReactiveElasticsearchQueryExecution.ResultProcessingExecution;
|
||||||
import org.springframework.data.mapping.context.MappingContext;
|
import org.springframework.data.mapping.context.MappingContext;
|
||||||
@ -88,6 +89,12 @@ abstract class AbstractReactiveElasticsearchRepositoryQuery implements Repositor
|
|||||||
query.setHighlightQuery(queryMethod.getAnnotatedHighlightQuery());
|
query.setHighlightQuery(queryMethod.getAnnotatedHighlightQuery());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var sourceFilter = queryMethod.getSourceFilter(parameterAccessor,
|
||||||
|
elasticsearchOperations.getElasticsearchConverter());
|
||||||
|
if (sourceFilter != null) {
|
||||||
|
query.addSourceFilter(sourceFilter);
|
||||||
|
}
|
||||||
|
|
||||||
Class<?> targetType = processor.getReturnedType().getTypeToRead();
|
Class<?> targetType = processor.getReturnedType().getTypeToRead();
|
||||||
String indexName = queryMethod.getEntityInformation().getIndexName();
|
String indexName = queryMethod.getEntityInformation().getIndexName();
|
||||||
IndexCoordinates index = IndexCoordinates.of(indexName);
|
IndexCoordinates index = IndexCoordinates.of(indexName);
|
||||||
|
@ -23,7 +23,6 @@ import org.springframework.data.elasticsearch.core.SearchHitSupport;
|
|||||||
import org.springframework.data.elasticsearch.core.SearchHits;
|
import org.springframework.data.elasticsearch.core.SearchHits;
|
||||||
import org.springframework.data.elasticsearch.core.SearchHitsImpl;
|
import org.springframework.data.elasticsearch.core.SearchHitsImpl;
|
||||||
import org.springframework.data.elasticsearch.core.TotalHitsRelation;
|
import org.springframework.data.elasticsearch.core.TotalHitsRelation;
|
||||||
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
|
|
||||||
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
|
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
|
||||||
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||||
import org.springframework.data.elasticsearch.core.query.CriteriaQuery;
|
import org.springframework.data.elasticsearch.core.query.CriteriaQuery;
|
||||||
@ -49,13 +48,11 @@ import org.springframework.util.ClassUtils;
|
|||||||
public class ElasticsearchPartQuery extends AbstractElasticsearchRepositoryQuery {
|
public class ElasticsearchPartQuery extends AbstractElasticsearchRepositoryQuery {
|
||||||
|
|
||||||
private final PartTree tree;
|
private final PartTree tree;
|
||||||
private final ElasticsearchConverter elasticsearchConverter;
|
|
||||||
private final MappingContext<?, ElasticsearchPersistentProperty> mappingContext;
|
private final MappingContext<?, ElasticsearchPersistentProperty> mappingContext;
|
||||||
|
|
||||||
public ElasticsearchPartQuery(ElasticsearchQueryMethod method, ElasticsearchOperations elasticsearchOperations) {
|
public ElasticsearchPartQuery(ElasticsearchQueryMethod method, ElasticsearchOperations elasticsearchOperations) {
|
||||||
super(method, elasticsearchOperations);
|
super(method, elasticsearchOperations);
|
||||||
this.tree = new PartTree(queryMethod.getName(), queryMethod.getResultProcessor().getReturnedType().getDomainType());
|
this.tree = new PartTree(queryMethod.getName(), queryMethod.getResultProcessor().getReturnedType().getDomainType());
|
||||||
this.elasticsearchConverter = elasticsearchOperations.getElasticsearchConverter();
|
|
||||||
this.mappingContext = elasticsearchConverter.getMappingContext();
|
this.mappingContext = elasticsearchConverter.getMappingContext();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,18 +63,16 @@ public class ElasticsearchPartQuery extends AbstractElasticsearchRepositoryQuery
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object execute(Object[] parameters) {
|
public Object execute(Object[] parameters) {
|
||||||
Class<?> clazz = queryMethod.getResultProcessor().getReturnedType().getDomainType();
|
|
||||||
ParametersParameterAccessor accessor = new ParametersParameterAccessor(queryMethod.getParameters(), parameters);
|
|
||||||
|
|
||||||
CriteriaQuery query = createQuery(accessor);
|
Class<?> clazz = queryMethod.getResultProcessor().getReturnedType().getDomainType();
|
||||||
|
ParametersParameterAccessor parameterAccessor = new ParametersParameterAccessor(queryMethod.getParameters(),
|
||||||
|
parameters);
|
||||||
|
|
||||||
|
CriteriaQuery query = createQuery(parameterAccessor);
|
||||||
|
|
||||||
Assert.notNull(query, "unsupported query");
|
Assert.notNull(query, "unsupported query");
|
||||||
|
|
||||||
elasticsearchConverter.updateQuery(query, clazz);
|
prepareQuery(query, clazz, parameterAccessor);
|
||||||
|
|
||||||
if (queryMethod.hasAnnotatedHighlight()) {
|
|
||||||
query.setHighlightQuery(queryMethod.getAnnotatedHighlightQuery());
|
|
||||||
}
|
|
||||||
|
|
||||||
IndexCoordinates index = elasticsearchOperations.getIndexCoordinatesFor(clazz);
|
IndexCoordinates index = elasticsearchOperations.getIndexCoordinatesFor(clazz);
|
||||||
|
|
||||||
@ -89,11 +84,11 @@ public class ElasticsearchPartQuery extends AbstractElasticsearchRepositoryQuery
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (tree.isDelete()) {
|
if (tree.isDelete()) {
|
||||||
result = countOrGetDocumentsForDelete(query, accessor);
|
result = countOrGetDocumentsForDelete(query, parameterAccessor);
|
||||||
elasticsearchOperations.delete(query, clazz, index);
|
elasticsearchOperations.delete(query, clazz, index);
|
||||||
elasticsearchOperations.indexOps(index).refresh();
|
elasticsearchOperations.indexOps(index).refresh();
|
||||||
} else if (queryMethod.isPageQuery()) {
|
} else if (queryMethod.isPageQuery()) {
|
||||||
query.setPageable(accessor.getPageable());
|
query.setPageable(parameterAccessor.getPageable());
|
||||||
SearchHits<?> searchHits = elasticsearchOperations.search(query, clazz, index);
|
SearchHits<?> searchHits = elasticsearchOperations.search(query, clazz, index);
|
||||||
if (queryMethod.isSearchPageMethod()) {
|
if (queryMethod.isSearchPageMethod()) {
|
||||||
result = SearchHitSupport.searchPageFor(searchHits, query.getPageable());
|
result = SearchHitSupport.searchPageFor(searchHits, query.getPageable());
|
||||||
@ -101,15 +96,15 @@ public class ElasticsearchPartQuery extends AbstractElasticsearchRepositoryQuery
|
|||||||
result = SearchHitSupport.unwrapSearchHits(SearchHitSupport.searchPageFor(searchHits, query.getPageable()));
|
result = SearchHitSupport.unwrapSearchHits(SearchHitSupport.searchPageFor(searchHits, query.getPageable()));
|
||||||
}
|
}
|
||||||
} else if (queryMethod.isStreamQuery()) {
|
} else if (queryMethod.isStreamQuery()) {
|
||||||
if (accessor.getPageable().isUnpaged()) {
|
if (parameterAccessor.getPageable().isUnpaged()) {
|
||||||
query.setPageable(PageRequest.of(0, DEFAULT_STREAM_BATCH_SIZE));
|
query.setPageable(PageRequest.of(0, DEFAULT_STREAM_BATCH_SIZE));
|
||||||
} else {
|
} else {
|
||||||
query.setPageable(accessor.getPageable());
|
query.setPageable(parameterAccessor.getPageable());
|
||||||
}
|
}
|
||||||
result = StreamUtils.createStreamFromIterator(elasticsearchOperations.searchForStream(query, clazz, index));
|
result = StreamUtils.createStreamFromIterator(elasticsearchOperations.searchForStream(query, clazz, index));
|
||||||
} else if (queryMethod.isCollectionQuery()) {
|
} else if (queryMethod.isCollectionQuery()) {
|
||||||
|
|
||||||
if (accessor.getPageable().isUnpaged()) {
|
if (parameterAccessor.getPageable().isUnpaged()) {
|
||||||
int itemCount = (int) elasticsearchOperations.count(query, clazz, index);
|
int itemCount = (int) elasticsearchOperations.count(query, clazz, index);
|
||||||
|
|
||||||
if (itemCount == 0) {
|
if (itemCount == 0) {
|
||||||
@ -119,7 +114,7 @@ public class ElasticsearchPartQuery extends AbstractElasticsearchRepositoryQuery
|
|||||||
query.setPageable(PageRequest.of(0, Math.max(1, itemCount)));
|
query.setPageable(PageRequest.of(0, Math.max(1, itemCount)));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
query.setPageable(accessor.getPageable());
|
query.setPageable(parameterAccessor.getPageable());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result == null) {
|
if (result == null) {
|
||||||
|
@ -17,22 +17,31 @@ package org.springframework.data.elasticsearch.repository.query;
|
|||||||
|
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.lang.reflect.ParameterizedType;
|
import java.lang.reflect.ParameterizedType;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||||
import org.springframework.data.elasticsearch.annotations.Highlight;
|
import org.springframework.data.elasticsearch.annotations.Highlight;
|
||||||
import org.springframework.data.elasticsearch.annotations.Query;
|
import org.springframework.data.elasticsearch.annotations.Query;
|
||||||
|
import org.springframework.data.elasticsearch.annotations.SourceFilters;
|
||||||
import org.springframework.data.elasticsearch.core.SearchHit;
|
import org.springframework.data.elasticsearch.core.SearchHit;
|
||||||
import org.springframework.data.elasticsearch.core.SearchHits;
|
import org.springframework.data.elasticsearch.core.SearchHits;
|
||||||
import org.springframework.data.elasticsearch.core.SearchPage;
|
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.ElasticsearchPersistentEntity;
|
||||||
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
|
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
|
||||||
|
import org.springframework.data.elasticsearch.core.query.FetchSourceFilterBuilder;
|
||||||
import org.springframework.data.elasticsearch.core.query.HighlightQuery;
|
import org.springframework.data.elasticsearch.core.query.HighlightQuery;
|
||||||
|
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.mapping.context.MappingContext;
|
||||||
import org.springframework.data.projection.ProjectionFactory;
|
import org.springframework.data.projection.ProjectionFactory;
|
||||||
import org.springframework.data.repository.core.RepositoryMetadata;
|
import org.springframework.data.repository.core.RepositoryMetadata;
|
||||||
|
import org.springframework.data.repository.query.ParameterAccessor;
|
||||||
import org.springframework.data.repository.query.QueryMethod;
|
import org.springframework.data.repository.query.QueryMethod;
|
||||||
import org.springframework.data.util.Lazy;
|
import org.springframework.data.util.Lazy;
|
||||||
import org.springframework.data.util.TypeInformation;
|
import org.springframework.data.util.TypeInformation;
|
||||||
@ -49,16 +58,19 @@ import org.springframework.util.ClassUtils;
|
|||||||
* @author Mark Paluch
|
* @author Mark Paluch
|
||||||
* @author Christoph Strobl
|
* @author Christoph Strobl
|
||||||
* @author Peter-Josef Meisch
|
* @author Peter-Josef Meisch
|
||||||
|
* @author Alexander Torres
|
||||||
*/
|
*/
|
||||||
public class ElasticsearchQueryMethod extends QueryMethod {
|
public class ElasticsearchQueryMethod extends QueryMethod {
|
||||||
|
|
||||||
private final MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext;
|
private final MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext;
|
||||||
private @Nullable ElasticsearchEntityMetadata<?> metadata;
|
@Nullable private ElasticsearchEntityMetadata<?> metadata;
|
||||||
protected final Method method; // private in base class, but needed here and in derived classes as well
|
protected final Method method; // private in base class, but needed here and in derived classes as well
|
||||||
@Nullable private final Query queryAnnotation;
|
@Nullable private final Query queryAnnotation;
|
||||||
@Nullable private final Highlight highlightAnnotation;
|
@Nullable private final Highlight highlightAnnotation;
|
||||||
private final Lazy<HighlightQuery> highlightQueryLazy = Lazy.of(this::createAnnotatedHighlightQuery);
|
private final Lazy<HighlightQuery> highlightQueryLazy = Lazy.of(this::createAnnotatedHighlightQuery);
|
||||||
|
|
||||||
|
@Nullable private final SourceFilters sourceFilters;
|
||||||
|
|
||||||
public ElasticsearchQueryMethod(Method method, RepositoryMetadata repositoryMetadata, ProjectionFactory factory,
|
public ElasticsearchQueryMethod(Method method, RepositoryMetadata repositoryMetadata, ProjectionFactory factory,
|
||||||
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext) {
|
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext) {
|
||||||
|
|
||||||
@ -70,6 +82,7 @@ public class ElasticsearchQueryMethod extends QueryMethod {
|
|||||||
this.mappingContext = mappingContext;
|
this.mappingContext = mappingContext;
|
||||||
this.queryAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, Query.class);
|
this.queryAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, Query.class);
|
||||||
this.highlightAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, Highlight.class);
|
this.highlightAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, Highlight.class);
|
||||||
|
this.sourceFilters = AnnotatedElementUtils.findMergedAnnotation(method, SourceFilters.class);
|
||||||
|
|
||||||
verifyCountQueryTypes();
|
verifyCountQueryTypes();
|
||||||
}
|
}
|
||||||
@ -92,8 +105,9 @@ public class ElasticsearchQueryMethod extends QueryMethod {
|
|||||||
/**
|
/**
|
||||||
* @return the query String. Must not be {@literal null} when {@link #hasAnnotatedQuery()} returns true
|
* @return the query String. Must not be {@literal null} when {@link #hasAnnotatedQuery()} returns true
|
||||||
*/
|
*/
|
||||||
|
@Nullable
|
||||||
public String getAnnotatedQuery() {
|
public String getAnnotatedQuery() {
|
||||||
return queryAnnotation.value();
|
return queryAnnotation != null ? queryAnnotation.value() : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -246,4 +260,86 @@ public class ElasticsearchQueryMethod extends QueryMethod {
|
|||||||
return queryAnnotation != null && queryAnnotation.count();
|
return queryAnnotation != null && queryAnnotation.count();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {@literal true} if the method is annotated with {@link SourceFilters}.
|
||||||
|
* @since 5.0
|
||||||
|
*/
|
||||||
|
public boolean hasSourceFilters() {
|
||||||
|
return sourceFilters != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the {@link SourceFilters} annotation for this method.
|
||||||
|
* @since 5.0
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public SourceFilters getSourceFilters() {
|
||||||
|
return sourceFilters;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uses the sourceFilters property to create a {@link SourceFilter} to be added to a
|
||||||
|
* {@link org.springframework.data.elasticsearch.core.query.Query}
|
||||||
|
*
|
||||||
|
* @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
|
||||||
|
* @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) {
|
||||||
|
|
||||||
|
if (sourceFilters == null || (sourceFilters.includes().length == 0 && sourceFilters.excludes().length == 0)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
ElasticsearchPersistentEntity<?> persistentEntity = converter.getMappingContext()
|
||||||
|
.getPersistentEntity(getEntityInformation().getJavaType());
|
||||||
|
|
||||||
|
StringQueryUtil stringQueryUtil = new StringQueryUtil(converter.getConversionService());
|
||||||
|
FetchSourceFilterBuilder fetchSourceFilterBuilder = new FetchSourceFilterBuilder();
|
||||||
|
|
||||||
|
if (sourceFilters.includes().length > 0) {
|
||||||
|
fetchSourceFilterBuilder
|
||||||
|
.withIncludes(mapParameters(sourceFilters.includes(), parameterAccessor, stringQueryUtil, persistentEntity));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sourceFilters.excludes().length > 0) {
|
||||||
|
fetchSourceFilterBuilder
|
||||||
|
.withExcludes(mapParameters(sourceFilters.excludes(), parameterAccessor, stringQueryUtil, persistentEntity));
|
||||||
|
}
|
||||||
|
|
||||||
|
return fetchSourceFilterBuilder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String[] mapParameters(String[] source, ParameterAccessor parameterAccessor, StringQueryUtil stringQueryUtil,
|
||||||
|
@Nullable ElasticsearchPersistentEntity<?> persistentEntity) {
|
||||||
|
|
||||||
|
List<String> unmappedFieldNames = new ArrayList<>();
|
||||||
|
|
||||||
|
for (String s : source) {
|
||||||
|
|
||||||
|
if (!s.isBlank()) {
|
||||||
|
String fieldName = stringQueryUtil.replacePlaceholders(s, parameterAccessor);
|
||||||
|
// this could be "[\"foo\",\"bar\"]", must be split
|
||||||
|
if (fieldName.startsWith("[") && fieldName.endsWith("]")) {
|
||||||
|
unmappedFieldNames.addAll( //
|
||||||
|
Arrays.asList(fieldName.substring(1, fieldName.length() - 2) //
|
||||||
|
.replaceAll("\\\"", "") //
|
||||||
|
.split(","))); //
|
||||||
|
} else {
|
||||||
|
unmappedFieldNames.add(fieldName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return unmappedFieldNames.stream().map(fieldName -> {
|
||||||
|
ElasticsearchPersistentProperty property = persistentEntity != null
|
||||||
|
? persistentEntity.getPersistentProperty(fieldName)
|
||||||
|
: null;
|
||||||
|
return property != null ? property.getFieldName() : fieldName;
|
||||||
|
}).toArray(String[]::new);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@ import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
|
|||||||
import org.springframework.data.elasticsearch.core.SearchHitSupport;
|
import org.springframework.data.elasticsearch.core.SearchHitSupport;
|
||||||
import org.springframework.data.elasticsearch.core.SearchHits;
|
import org.springframework.data.elasticsearch.core.SearchHits;
|
||||||
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||||
|
import org.springframework.data.elasticsearch.core.query.Query;
|
||||||
import org.springframework.data.elasticsearch.core.query.StringQuery;
|
import org.springframework.data.elasticsearch.core.query.StringQuery;
|
||||||
import org.springframework.data.elasticsearch.repository.support.StringQueryUtil;
|
import org.springframework.data.elasticsearch.repository.support.StringQueryUtil;
|
||||||
import org.springframework.data.repository.query.ParametersParameterAccessor;
|
import org.springframework.data.repository.query.ParametersParameterAccessor;
|
||||||
@ -38,13 +39,13 @@ import org.springframework.util.Assert;
|
|||||||
*/
|
*/
|
||||||
public class ElasticsearchStringQuery extends AbstractElasticsearchRepositoryQuery {
|
public class ElasticsearchStringQuery extends AbstractElasticsearchRepositoryQuery {
|
||||||
|
|
||||||
private String query;
|
private final String queryString;
|
||||||
|
|
||||||
public ElasticsearchStringQuery(ElasticsearchQueryMethod queryMethod, ElasticsearchOperations elasticsearchOperations,
|
public ElasticsearchStringQuery(ElasticsearchQueryMethod queryMethod, ElasticsearchOperations elasticsearchOperations,
|
||||||
String query) {
|
String queryString) {
|
||||||
super(queryMethod, elasticsearchOperations);
|
super(queryMethod, elasticsearchOperations);
|
||||||
Assert.notNull(query, "Query cannot be empty");
|
Assert.notNull(queryString, "Query cannot be empty");
|
||||||
this.query = query;
|
this.queryString = queryString;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -56,40 +57,42 @@ public class ElasticsearchStringQuery extends AbstractElasticsearchRepositoryQue
|
|||||||
public Object execute(Object[] parameters) {
|
public Object execute(Object[] parameters) {
|
||||||
|
|
||||||
Class<?> clazz = queryMethod.getResultProcessor().getReturnedType().getDomainType();
|
Class<?> clazz = queryMethod.getResultProcessor().getReturnedType().getDomainType();
|
||||||
ParametersParameterAccessor accessor = new ParametersParameterAccessor(queryMethod.getParameters(), parameters);
|
ParametersParameterAccessor parameterAccessor = new ParametersParameterAccessor(queryMethod.getParameters(),
|
||||||
|
parameters);
|
||||||
|
|
||||||
StringQuery stringQuery = createQuery(accessor);
|
Query query = createQuery(parameterAccessor);
|
||||||
|
Assert.notNull(query, "unsupported query");
|
||||||
Assert.notNull(stringQuery, "unsupported query");
|
|
||||||
|
|
||||||
if (queryMethod.hasAnnotatedHighlight()) {
|
if (queryMethod.hasAnnotatedHighlight()) {
|
||||||
stringQuery.setHighlightQuery(queryMethod.getAnnotatedHighlightQuery());
|
query.setHighlightQuery(queryMethod.getAnnotatedHighlightQuery());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
prepareQuery(query, clazz, parameterAccessor);
|
||||||
|
|
||||||
IndexCoordinates index = elasticsearchOperations.getIndexCoordinatesFor(clazz);
|
IndexCoordinates index = elasticsearchOperations.getIndexCoordinatesFor(clazz);
|
||||||
|
|
||||||
Object result = null;
|
Object result;
|
||||||
|
|
||||||
if (isCountQuery()) {
|
if (isCountQuery()) {
|
||||||
result = elasticsearchOperations.count(stringQuery, clazz, index);
|
result = elasticsearchOperations.count(query, clazz, index);
|
||||||
} else if (queryMethod.isPageQuery()) {
|
} else if (queryMethod.isPageQuery()) {
|
||||||
stringQuery.setPageable(accessor.getPageable());
|
query.setPageable(parameterAccessor.getPageable());
|
||||||
SearchHits<?> searchHits = elasticsearchOperations.search(stringQuery, clazz, index);
|
SearchHits<?> searchHits = elasticsearchOperations.search(query, clazz, index);
|
||||||
if (queryMethod.isSearchPageMethod()) {
|
if (queryMethod.isSearchPageMethod()) {
|
||||||
result = SearchHitSupport.searchPageFor(searchHits, stringQuery.getPageable());
|
result = SearchHitSupport.searchPageFor(searchHits, query.getPageable());
|
||||||
} else {
|
} else {
|
||||||
result = SearchHitSupport
|
result = SearchHitSupport.unwrapSearchHits(SearchHitSupport.searchPageFor(searchHits, query.getPageable()));
|
||||||
.unwrapSearchHits(SearchHitSupport.searchPageFor(searchHits, stringQuery.getPageable()));
|
|
||||||
}
|
}
|
||||||
} else if (queryMethod.isStreamQuery()) {
|
} else if (queryMethod.isStreamQuery()) {
|
||||||
stringQuery.setPageable(
|
query.setPageable(parameterAccessor.getPageable().isPaged() ? parameterAccessor.getPageable()
|
||||||
accessor.getPageable().isPaged() ? accessor.getPageable() : PageRequest.of(0, DEFAULT_STREAM_BATCH_SIZE));
|
: PageRequest.of(0, DEFAULT_STREAM_BATCH_SIZE));
|
||||||
result = StreamUtils.createStreamFromIterator(elasticsearchOperations.searchForStream(stringQuery, clazz, index));
|
result = StreamUtils.createStreamFromIterator(elasticsearchOperations.searchForStream(query, clazz, index));
|
||||||
} else if (queryMethod.isCollectionQuery()) {
|
} else if (queryMethod.isCollectionQuery()) {
|
||||||
stringQuery.setPageable(accessor.getPageable().isPaged() ? accessor.getPageable() : Pageable.unpaged());
|
query.setPageable(
|
||||||
result = elasticsearchOperations.search(stringQuery, clazz, index);
|
parameterAccessor.getPageable().isPaged() ? parameterAccessor.getPageable() : Pageable.unpaged());
|
||||||
|
result = elasticsearchOperations.search(query, clazz, index);
|
||||||
} else {
|
} else {
|
||||||
result = elasticsearchOperations.searchOne(stringQuery, clazz, index);
|
result = elasticsearchOperations.searchOne(query, clazz, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (queryMethod.isNotSearchHitMethod() && queryMethod.isNotSearchPageMethod())
|
return (queryMethod.isNotSearchHitMethod() && queryMethod.isNotSearchPageMethod())
|
||||||
@ -99,7 +102,7 @@ public class ElasticsearchStringQuery extends AbstractElasticsearchRepositoryQue
|
|||||||
|
|
||||||
protected StringQuery createQuery(ParametersParameterAccessor parameterAccessor) {
|
protected StringQuery createQuery(ParametersParameterAccessor parameterAccessor) {
|
||||||
String queryString = new StringQueryUtil(elasticsearchOperations.getElasticsearchConverter().getConversionService())
|
String queryString = new StringQueryUtil(elasticsearchOperations.getElasticsearchConverter().getConversionService())
|
||||||
.replacePlaceholders(this.query, parameterAccessor);
|
.replacePlaceholders(this.queryString, parameterAccessor);
|
||||||
return new StringQuery(queryString);
|
return new StringQuery(queryString);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,6 +48,7 @@ import org.springframework.data.elasticsearch.annotations.Field;
|
|||||||
import org.springframework.data.elasticsearch.annotations.Highlight;
|
import org.springframework.data.elasticsearch.annotations.Highlight;
|
||||||
import org.springframework.data.elasticsearch.annotations.HighlightField;
|
import org.springframework.data.elasticsearch.annotations.HighlightField;
|
||||||
import org.springframework.data.elasticsearch.annotations.Query;
|
import org.springframework.data.elasticsearch.annotations.Query;
|
||||||
|
import org.springframework.data.elasticsearch.annotations.SourceFilters;
|
||||||
import org.springframework.data.elasticsearch.core.AbstractElasticsearchTemplate;
|
import org.springframework.data.elasticsearch.core.AbstractElasticsearchTemplate;
|
||||||
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
|
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
|
||||||
import org.springframework.data.elasticsearch.core.SearchHit;
|
import org.springframework.data.elasticsearch.core.SearchHit;
|
||||||
@ -80,12 +81,11 @@ import org.springframework.lang.Nullable;
|
|||||||
@SpringIntegrationTest
|
@SpringIntegrationTest
|
||||||
public abstract class CustomMethodRepositoryIntegrationTests implements NewElasticsearchClientDevelopment {
|
public abstract class CustomMethodRepositoryIntegrationTests implements NewElasticsearchClientDevelopment {
|
||||||
|
|
||||||
|
@Autowired ElasticsearchOperations operations;
|
||||||
@Autowired private IndexNameProvider indexNameProvider;
|
@Autowired private IndexNameProvider indexNameProvider;
|
||||||
@Autowired private SampleCustomMethodRepository repository;
|
@Autowired private SampleCustomMethodRepository repository;
|
||||||
@Autowired private SampleStreamingCustomMethodRepository streamingRepository;
|
@Autowired private SampleStreamingCustomMethodRepository streamingRepository;
|
||||||
|
|
||||||
@Autowired ElasticsearchOperations operations;
|
|
||||||
|
|
||||||
boolean rhlcWithCluster8() {
|
boolean rhlcWithCluster8() {
|
||||||
var clusterVersion = ((AbstractElasticsearchTemplate) operations).getClusterVersion();
|
var clusterVersion = ((AbstractElasticsearchTemplate) operations).getClusterVersion();
|
||||||
return (oldElasticsearchClient() && clusterVersion != null && clusterVersion.startsWith("8"));
|
return (oldElasticsearchClient() && clusterVersion != null && clusterVersion.startsWith("8"));
|
||||||
@ -1675,6 +1675,94 @@ public abstract class CustomMethodRepositoryIntegrationTests implements NewElast
|
|||||||
assertThat(returnedIds).containsAll(ids);
|
assertThat(returnedIds).containsAll(ids);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test // #2146
|
||||||
|
@DisplayName("should use sourceIncludes from annotation")
|
||||||
|
void shouldUseSourceIncludesFromAnnotation() {
|
||||||
|
|
||||||
|
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.searchWithSourceFilterIncludesAnnotation();
|
||||||
|
|
||||||
|
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 sourceIncludes from parameter")
|
||||||
|
void shouldUseSourceIncludesFromParameter() {
|
||||||
|
|
||||||
|
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.searchBy(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() {
|
||||||
|
|
||||||
|
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.searchWithSourceFilterExcludesAnnotation();
|
||||||
|
|
||||||
|
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 source excludes from parameter")
|
||||||
|
void shouldUseSourceExcludesFromParameter() {
|
||||||
|
|
||||||
|
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.findBy(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<SampleEntity> createSampleEntities(String type, int numberOfEntities) {
|
private List<SampleEntity> createSampleEntities(String type, int numberOfEntities) {
|
||||||
|
|
||||||
List<SampleEntity> entities = new ArrayList<>();
|
List<SampleEntity> entities = new ArrayList<>();
|
||||||
@ -1690,93 +1778,6 @@ public abstract class CustomMethodRepositoryIntegrationTests implements NewElast
|
|||||||
return entities;
|
return entities;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Document(indexName = "#{@indexNameProvider.indexName()}")
|
|
||||||
static class SampleEntity {
|
|
||||||
@Nullable
|
|
||||||
@Id private String id;
|
|
||||||
@Nullable
|
|
||||||
@Field(type = Text, store = true, fielddata = true) private String type;
|
|
||||||
@Nullable
|
|
||||||
@Field(type = Text, store = true, fielddata = true) private String message;
|
|
||||||
@Nullable
|
|
||||||
@Field(type = Keyword) private String keyword;
|
|
||||||
@Nullable private int rate;
|
|
||||||
@Nullable private boolean available;
|
|
||||||
@Nullable private GeoPoint location;
|
|
||||||
@Nullable
|
|
||||||
@Version private Long version;
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public String getId() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setId(@Nullable String id) {
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public String getType() {
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setType(@Nullable String type) {
|
|
||||||
this.type = type;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public String getMessage() {
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMessage(@Nullable String message) {
|
|
||||||
this.message = message;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public String getKeyword() {
|
|
||||||
return keyword;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setKeyword(@Nullable String keyword) {
|
|
||||||
this.keyword = keyword;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getRate() {
|
|
||||||
return rate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setRate(int rate) {
|
|
||||||
this.rate = rate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isAvailable() {
|
|
||||||
return available;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAvailable(boolean available) {
|
|
||||||
this.available = available;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public GeoPoint getLocation() {
|
|
||||||
return location;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setLocation(@Nullable GeoPoint location) {
|
|
||||||
this.location = location;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public java.lang.Long getVersion() {
|
|
||||||
return version;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setVersion(@Nullable java.lang.Long version) {
|
|
||||||
this.version = version;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Rizwan Idrees
|
* @author Rizwan Idrees
|
||||||
* @author Mohsin Husen
|
* @author Mohsin Husen
|
||||||
@ -1911,11 +1912,30 @@ public abstract class CustomMethodRepositoryIntegrationTests implements NewElast
|
|||||||
|
|
||||||
@Query("{\"ids\" : {\"values\" : ?0 }}")
|
@Query("{\"ids\" : {\"values\" : ?0 }}")
|
||||||
List<SampleEntity> getByIds(Collection<String> ids);
|
List<SampleEntity> getByIds(Collection<String> ids);
|
||||||
|
|
||||||
|
@Query("""
|
||||||
|
{
|
||||||
|
"match_all": {}
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
@SourceFilters(includes = { "message", "customFieldNameMessage" })
|
||||||
|
SearchHits<SampleEntity> searchWithSourceFilterIncludesAnnotation();
|
||||||
|
|
||||||
|
@SourceFilters(includes = "?0")
|
||||||
|
SearchHits<SampleEntity> searchBy(Collection<String> sourceIncludes);
|
||||||
|
|
||||||
|
@Query("""
|
||||||
|
{
|
||||||
|
"match_all": {}
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
@SourceFilters(excludes = { "type", "keyword" })
|
||||||
|
SearchHits<SampleEntity> searchWithSourceFilterExcludesAnnotation();
|
||||||
|
|
||||||
|
@SourceFilters(excludes = "?0")
|
||||||
|
SearchHits<SampleEntity> findBy(Collection<String> sourceExcludes);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Rasmus Faber-Espensen
|
|
||||||
*/
|
|
||||||
public interface SampleStreamingCustomMethodRepository extends ElasticsearchRepository<SampleEntity, String> {
|
public interface SampleStreamingCustomMethodRepository extends ElasticsearchRepository<SampleEntity, String> {
|
||||||
Stream<SampleEntity> findByType(String type);
|
Stream<SampleEntity> findByType(String type);
|
||||||
|
|
||||||
@ -1928,4 +1948,103 @@ public abstract class CustomMethodRepositoryIntegrationTests implements NewElast
|
|||||||
Stream<SearchHit<SampleEntity>> streamSearchHitsByType(String type);
|
Stream<SearchHit<SampleEntity>> streamSearchHitsByType(String type);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Document(indexName = "#{@indexNameProvider.indexName()}")
|
||||||
|
static class SampleEntity {
|
||||||
|
@Nullable
|
||||||
|
@Id private String id;
|
||||||
|
@Nullable
|
||||||
|
@Field(type = Text, store = true, fielddata = true) private String type;
|
||||||
|
@Nullable
|
||||||
|
@Field(type = Text, store = true, fielddata = true) private String message;
|
||||||
|
@Nullable
|
||||||
|
@Field(type = Keyword) private String keyword;
|
||||||
|
@Nullable private int rate;
|
||||||
|
@Nullable private boolean available;
|
||||||
|
@Nullable private GeoPoint location;
|
||||||
|
@Nullable
|
||||||
|
@Version private Long version;
|
||||||
|
|
||||||
|
@Field(name = "custom_field_name", type = Text)
|
||||||
|
@Nullable private String customFieldNameMessage;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(@Nullable String id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public String getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setType(@Nullable String type) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public String getMessage() {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMessage(@Nullable String message) {
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public String getCustomFieldNameMessage() {
|
||||||
|
return customFieldNameMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCustomFieldNameMessage(@Nullable String customFieldNameMessage) {
|
||||||
|
this.customFieldNameMessage = customFieldNameMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public String getKeyword() {
|
||||||
|
return keyword;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setKeyword(@Nullable String keyword) {
|
||||||
|
this.keyword = keyword;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getRate() {
|
||||||
|
return rate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRate(int rate) {
|
||||||
|
this.rate = rate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAvailable() {
|
||||||
|
return available;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAvailable(boolean available) {
|
||||||
|
this.available = available;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public GeoPoint getLocation() {
|
||||||
|
return location;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLocation(@Nullable GeoPoint location) {
|
||||||
|
this.location = location;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public java.lang.Long getVersion() {
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setVersion(@Nullable java.lang.Long version) {
|
||||||
|
this.version = version;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,7 @@ import reactor.core.publisher.Mono;
|
|||||||
import reactor.test.StepVerifier;
|
import reactor.test.StepVerifier;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
@ -47,6 +48,7 @@ import org.springframework.data.elasticsearch.annotations.FieldType;
|
|||||||
import org.springframework.data.elasticsearch.annotations.Highlight;
|
import org.springframework.data.elasticsearch.annotations.Highlight;
|
||||||
import org.springframework.data.elasticsearch.annotations.HighlightField;
|
import org.springframework.data.elasticsearch.annotations.HighlightField;
|
||||||
import org.springframework.data.elasticsearch.annotations.Query;
|
import org.springframework.data.elasticsearch.annotations.Query;
|
||||||
|
import org.springframework.data.elasticsearch.annotations.SourceFilters;
|
||||||
import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations;
|
import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations;
|
||||||
import org.springframework.data.elasticsearch.core.SearchHit;
|
import org.springframework.data.elasticsearch.core.SearchHit;
|
||||||
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||||
@ -634,6 +636,98 @@ abstract class SimpleReactiveElasticsearchRepositoryIntegrationTests {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test // #2146
|
||||||
|
@DisplayName("should use sourceIncludes from annotation")
|
||||||
|
void shouldUseSourceIncludesFromAnnotation() {
|
||||||
|
|
||||||
|
var entity = new SampleEntity();
|
||||||
|
entity.setId("42");
|
||||||
|
entity.setMessage("message");
|
||||||
|
entity.setCustomFieldNameMessage("customFieldNameMessage");
|
||||||
|
entity.setType("type");
|
||||||
|
entity.setKeyword("keyword");
|
||||||
|
repository.save(entity).block();
|
||||||
|
|
||||||
|
repository.searchWithSourceFilterIncludesAnnotation() //
|
||||||
|
.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 sourceIncludes from parameter")
|
||||||
|
void shouldUseSourceIncludesFromParameter() {
|
||||||
|
|
||||||
|
var entity = new SampleEntity();
|
||||||
|
entity.setId("42");
|
||||||
|
entity.setMessage("message");
|
||||||
|
entity.setCustomFieldNameMessage("customFieldNameMessage");
|
||||||
|
entity.setType("type");
|
||||||
|
entity.setKeyword("keyword");
|
||||||
|
repository.save(entity).block();
|
||||||
|
|
||||||
|
repository.searchBy(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() {
|
||||||
|
|
||||||
|
var entity = new SampleEntity();
|
||||||
|
entity.setId("42");
|
||||||
|
entity.setMessage("message");
|
||||||
|
entity.setCustomFieldNameMessage("customFieldNameMessage");
|
||||||
|
entity.setType("type");
|
||||||
|
entity.setKeyword("keyword");
|
||||||
|
repository.save(entity).block();
|
||||||
|
|
||||||
|
repository.searchWithSourceFilterExcludesAnnotation() //
|
||||||
|
.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 source excludes from parameter")
|
||||||
|
void shouldUseSourceExcludesFromParameter() {
|
||||||
|
|
||||||
|
var entity = new SampleEntity();
|
||||||
|
entity.setId("42");
|
||||||
|
entity.setMessage("message");
|
||||||
|
entity.setCustomFieldNameMessage("customFieldNameMessage");
|
||||||
|
entity.setType("type");
|
||||||
|
entity.setKeyword("keyword");
|
||||||
|
repository.save(entity).block();
|
||||||
|
|
||||||
|
repository.findBy(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();
|
||||||
|
}
|
||||||
|
|
||||||
Mono<Void> bulkIndex(SampleEntity... entities) {
|
Mono<Void> bulkIndex(SampleEntity... entities) {
|
||||||
return operations.saveAll(Arrays.asList(entities), IndexCoordinates.of(indexNameProvider.indexName())).then();
|
return operations.saveAll(Arrays.asList(entities), IndexCoordinates.of(indexNameProvider.indexName())).then();
|
||||||
}
|
}
|
||||||
@ -683,6 +777,27 @@ abstract class SimpleReactiveElasticsearchRepositoryIntegrationTests {
|
|||||||
@Query("{\"bool\": {\"must\": [{ \"terms\": { \"message\": ?0 } }, { \"terms\": { \"rate\": ?1 } }] } }")
|
@Query("{\"bool\": {\"must\": [{ \"terms\": { \"message\": ?0 } }, { \"terms\": { \"rate\": ?1 } }] } }")
|
||||||
Flux<SampleEntity> findAllViaAnnotatedQueryByMessageInAndRatesIn(List<String> messages, List<Integer> rates);
|
Flux<SampleEntity> findAllViaAnnotatedQueryByMessageInAndRatesIn(List<String> messages, List<Integer> rates);
|
||||||
|
|
||||||
|
@Query(query = """
|
||||||
|
{
|
||||||
|
"match_all": {}
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
@SourceFilters(includes = { "message", "customFieldNameMessage" })
|
||||||
|
Flux<SampleEntity> searchWithSourceFilterIncludesAnnotation();
|
||||||
|
|
||||||
|
@SourceFilters(includes = "?0")
|
||||||
|
Flux<SampleEntity> searchBy(Collection<String> sourceIncludes);
|
||||||
|
|
||||||
|
@Query("""
|
||||||
|
{
|
||||||
|
"match_all": {}
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
@SourceFilters(excludes = { "type", "keyword" })
|
||||||
|
Flux<SampleEntity> searchWithSourceFilterExcludesAnnotation();
|
||||||
|
|
||||||
|
@SourceFilters(excludes = "?0")
|
||||||
|
Flux<SampleEntity> findBy(Collection<String> sourceExcludes);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Document(indexName = "#{@indexNameProvider.indexName()}")
|
@Document(indexName = "#{@indexNameProvider.indexName()}")
|
||||||
@ -693,10 +808,15 @@ abstract class SimpleReactiveElasticsearchRepositoryIntegrationTests {
|
|||||||
@Field(type = FieldType.Text, store = true, fielddata = true) private String type;
|
@Field(type = FieldType.Text, store = true, fielddata = true) private String type;
|
||||||
@Nullable
|
@Nullable
|
||||||
@Field(type = FieldType.Text, store = true, fielddata = true) private String message;
|
@Field(type = FieldType.Text, store = true, fielddata = true) private String message;
|
||||||
|
@Nullable
|
||||||
|
@Field(type = FieldType.Keyword) private String keyword;
|
||||||
|
|
||||||
@Nullable private int rate;
|
@Nullable private int rate;
|
||||||
@Nullable private boolean available;
|
@Nullable private boolean available;
|
||||||
@Nullable
|
@Nullable
|
||||||
@Version private Long version;
|
@Version private Long version;
|
||||||
|
@Field(name = "custom_field_name", type = FieldType.Text)
|
||||||
|
@Nullable private String customFieldNameMessage;
|
||||||
|
|
||||||
public SampleEntity() {}
|
public SampleEntity() {}
|
||||||
|
|
||||||
@ -733,6 +853,15 @@ abstract class SimpleReactiveElasticsearchRepositoryIntegrationTests {
|
|||||||
this.type = type;
|
this.type = type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public String getKeyword() {
|
||||||
|
return keyword;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setKeyword(@Nullable String keyword) {
|
||||||
|
this.keyword = keyword;
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public String getMessage() {
|
public String getMessage() {
|
||||||
return message;
|
return message;
|
||||||
@ -766,5 +895,14 @@ abstract class SimpleReactiveElasticsearchRepositoryIntegrationTests {
|
|||||||
public void setVersion(@Nullable java.lang.Long version) {
|
public void setVersion(@Nullable java.lang.Long version) {
|
||||||
this.version = version;
|
this.version = version;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public String getCustomFieldNameMessage() {
|
||||||
|
return customFieldNameMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCustomFieldNameMessage(@Nullable String customFieldNameMessage) {
|
||||||
|
this.customFieldNameMessage = customFieldNameMessage;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user