From fe8c4f12ed537efd17b1fd95b42309297684a0c5 Mon Sep 17 00:00:00 2001 From: Peter-Josef Meisch Date: Sun, 7 Feb 2021 20:21:02 +0100 Subject: [PATCH] Add @CountQuery annotation. Original Pull Request #1682 Closes #1156 --- .../elasticsearch/annotations/CountQuery.java | 39 ++++++++ .../data/elasticsearch/annotations/Query.java | 18 +++- .../AbstractElasticsearchRepositoryQuery.java | 7 ++ ...tReactiveElasticsearchRepositoryQuery.java | 2 +- .../query/ElasticsearchPartQuery.java | 5 + .../query/ElasticsearchQueryMethod.java | 43 +++++++-- .../query/ElasticsearchStringQuery.java | 10 +- .../ReactiveElasticsearchQueryMethod.java | 17 +++- .../ReactiveElasticsearchStringQuery.java | 2 +- ...NamingStrategyIntegrationTemplateTest.java | 16 +++- .../CustomMethodRepositoryBaseTests.java | 30 ++++++ .../ElasticsearchQueryMethodUnitTests.java | 92 +++++++++++++++++++ ...tiveElasticsearchQueryMethodUnitTests.java | 24 ++++- ...asticsearchRepositoryIntegrationTests.java | 4 +- ...eReactiveElasticsearchRepositoryTests.java | 22 ++++- 15 files changed, 308 insertions(+), 23 deletions(-) create mode 100644 src/main/java/org/springframework/data/elasticsearch/annotations/CountQuery.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchQueryMethodUnitTests.java rename src/test/java/org/springframework/data/elasticsearch/repository/support/{simple => }/SimpleElasticsearchRepositoryIntegrationTests.java (99%) diff --git a/src/main/java/org/springframework/data/elasticsearch/annotations/CountQuery.java b/src/main/java/org/springframework/data/elasticsearch/annotations/CountQuery.java new file mode 100644 index 000000000..6440d89ec --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/annotations/CountQuery.java @@ -0,0 +1,39 @@ +/* + * Copyright 2021 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; + +import org.springframework.core.annotation.AliasFor; + +/** + * Alias for a @Query annotation with the count parameter set to true. + * + * @author Peter-Josef Meisch + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE }) +@Documented +@Query(count = true) +public @interface CountQuery { + + @AliasFor(annotation = Query.class) + String value() default ""; +} diff --git a/src/main/java/org/springframework/data/elasticsearch/annotations/Query.java b/src/main/java/org/springframework/data/elasticsearch/annotations/Query.java index 34671eed3..3c14197d8 100644 --- a/src/main/java/org/springframework/data/elasticsearch/annotations/Query.java +++ b/src/main/java/org/springframework/data/elasticsearch/annotations/Query.java @@ -22,24 +22,32 @@ import java.lang.annotation.*; * * @author Rizwan Idrees * @author Mohsin Husen + * @author Peter-Josef Meisch */ @Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.METHOD) +@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) @Documented public @interface Query { /** - * Elasticsearch query to be used when executing query. May contain placeholders eg. ?0 - * - * @return + * @return Elasticsearch query to be used when executing query. May contain placeholders eg. ?0 */ String value() default ""; /** * Named Query Named looked up by repository. * - * @return + * @deprecated since 4.2, not implemented and used anywhere */ String name() default ""; + + /** + * Returns whether the query defined should be executed as count projection. + * + * @return {@literal false} by default. + * @since 4.2 + */ + boolean count() default false; } + 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 e1fb982b1..b3ce881ee 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 @@ -24,6 +24,7 @@ import org.springframework.data.repository.query.RepositoryQuery; * * @author Rizwan Idrees * @author Mohsin Husen + * @author Peter-Josef Meisch */ public abstract class AbstractElasticsearchRepositoryQuery implements RepositoryQuery { @@ -42,4 +43,10 @@ public abstract class AbstractElasticsearchRepositoryQuery implements Repository public QueryMethod getQueryMethod() { return queryMethod; } + + /** + * @return {@literal true} if this is a count query + * @since 4.2 + */ + public abstract boolean isCountQuery(); } 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 1bfa60af3..ed6cd8866 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 @@ -43,7 +43,7 @@ import org.springframework.data.repository.query.ResultProcessor; */ abstract class AbstractReactiveElasticsearchRepositoryQuery implements RepositoryQuery { - private final ReactiveElasticsearchQueryMethod queryMethod; + protected final ReactiveElasticsearchQueryMethod queryMethod; private final ReactiveElasticsearchOperations elasticsearchOperations; AbstractReactiveElasticsearchRepositoryQuery(ReactiveElasticsearchQueryMethod queryMethod, 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 0c5a86faa..286e78f49 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 @@ -59,6 +59,11 @@ public class ElasticsearchPartQuery extends AbstractElasticsearchRepositoryQuery this.mappingContext = elasticsearchConverter.getMappingContext(); } + @Override + public boolean isCountQuery() { + return tree.isCountProjection(); + } + @Override public Object execute(Object[] parameters) { Class clazz = queryMethod.getResultProcessor().getReturnedType().getDomainType(); 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 044fc6377..997c9292f 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 @@ -20,7 +20,8 @@ import java.lang.reflect.ParameterizedType; import java.util.Collection; import java.util.stream.Stream; -import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.core.annotation.AnnotatedElementUtils; +import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.data.elasticsearch.annotations.Highlight; import org.springframework.data.elasticsearch.annotations.Query; import org.springframework.data.elasticsearch.core.SearchHit; @@ -34,7 +35,9 @@ 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.QueryMethod; +import org.springframework.data.util.ClassTypeInformation; import org.springframework.data.util.Lazy; +import org.springframework.data.util.TypeInformation; import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; @@ -53,9 +56,9 @@ public class ElasticsearchQueryMethod extends QueryMethod { private final MappingContext, ElasticsearchPersistentProperty> mappingContext; private @Nullable ElasticsearchEntityMetadata metadata; - private final Method method; // private in base class, but needed here as well - private final Query queryAnnotation; - private final Highlight highlightAnnotation; + 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 Highlight highlightAnnotation; private final Lazy highlightQueryLazy = Lazy.of(this::createAnnotatedHighlightQuery); public ElasticsearchQueryMethod(Method method, RepositoryMetadata repositoryMetadata, ProjectionFactory factory, @@ -67,16 +70,32 @@ public class ElasticsearchQueryMethod extends QueryMethod { this.method = method; this.mappingContext = mappingContext; - this.queryAnnotation = method.getAnnotation(Query.class); - this.highlightAnnotation = method.getAnnotation(Highlight.class); + this.queryAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, Query.class); + this.highlightAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, Highlight.class); + + verifyCountQueryTypes(); + } + + protected void verifyCountQueryTypes() { + + if (hasCountQueryAnnotation()) { + TypeInformation returnType = ClassTypeInformation.fromReturnTypeOf(method); + + if (returnType.getType() != long.class && !Long.class.isAssignableFrom(returnType.getType())) { + throw new InvalidDataAccessApiUsageException("count query methods must return a Long"); + } + } } public boolean hasAnnotatedQuery() { return this.queryAnnotation != null; } + /** + * @return the query String. Must not be {@literal null} when {@link #hasAnnotatedQuery()} returns true + */ public String getAnnotatedQuery() { - return (String) AnnotationUtils.getValue(queryAnnotation, "value"); + return queryAnnotation.value(); } /** @@ -217,4 +236,14 @@ public class ElasticsearchQueryMethod extends QueryMethod { public boolean isNotSearchPageMethod() { return !isSearchPageMethod(); } + + /** + * @return {@literal true} if the method is annotated with + * {@link org.springframework.data.elasticsearch.annotations.CountQuery} or with {@link Query}(count =true) + * @since 4.2 + */ + public boolean hasCountQueryAnnotation() { + return queryAnnotation != null && queryAnnotation.count(); + } + } 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 a32f7da8d..6ddda3d50 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 @@ -69,8 +69,14 @@ public class ElasticsearchStringQuery extends AbstractElasticsearchRepositoryQue this.query = query; } + @Override + public boolean isCountQuery() { + return queryMethod.hasCountQueryAnnotation(); + } + @Override public Object execute(Object[] parameters) { + Class clazz = queryMethod.getResultProcessor().getReturnedType().getDomainType(); ParametersParameterAccessor accessor = new ParametersParameterAccessor(queryMethod.getParameters(), parameters); @@ -86,7 +92,9 @@ public class ElasticsearchStringQuery extends AbstractElasticsearchRepositoryQue Object result = null; - if (queryMethod.isPageQuery()) { + if (isCountQuery()) { + result = elasticsearchOperations.count(stringQuery, clazz, index); + } else if (queryMethod.isPageQuery()) { stringQuery.setPageable(accessor.getPageable()); SearchHits searchHits = elasticsearchOperations.search(stringQuery, clazz, index); result = SearchHitSupport.searchPageFor(searchHits, stringQuery.getPageable()); diff --git a/src/main/java/org/springframework/data/elasticsearch/repository/query/ReactiveElasticsearchQueryMethod.java b/src/main/java/org/springframework/data/elasticsearch/repository/query/ReactiveElasticsearchQueryMethod.java index 7bfa3d04d..9c545be40 100644 --- a/src/main/java/org/springframework/data/elasticsearch/repository/query/ReactiveElasticsearchQueryMethod.java +++ b/src/main/java/org/springframework/data/elasticsearch/repository/query/ReactiveElasticsearchQueryMethod.java @@ -18,9 +18,11 @@ package org.springframework.data.elasticsearch.repository.query; import static org.springframework.data.repository.util.ClassUtils.*; import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; +import java.util.List; import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.data.domain.Page; @@ -59,7 +61,6 @@ public class ReactiveElasticsearchQueryMethod extends ElasticsearchQueryMethod { if (hasParameterOfType(method, Pageable.class)) { TypeInformation returnType = ClassTypeInformation.fromReturnTypeOf(method); - boolean multiWrapper = ReactiveWrappers.isMultiValueType(returnType.getType()); boolean singleWrapperWithWrappedPageableResult = ReactiveWrappers.isSingleValueType(returnType.getType()) && (PAGE_TYPE.isAssignableFrom(returnType.getRequiredComponentType()) @@ -87,6 +88,20 @@ public class ReactiveElasticsearchQueryMethod extends ElasticsearchQueryMethod { && ReactiveWrappers.isMultiValueType(metadata.getReturnType(method).getType()) || super.isCollectionQuery())); } + @Override + protected void verifyCountQueryTypes() { + if (hasCountQueryAnnotation()) { + TypeInformation returnType = ClassTypeInformation.fromReturnTypeOf(method); + List> typeArguments = returnType.getTypeArguments(); + + if (!Mono.class.isAssignableFrom(returnType.getType()) || typeArguments.size() != 1 + || (typeArguments.get(0).getType() != long.class + && !Long.class.isAssignableFrom(typeArguments.get(0).getType()))) { + throw new InvalidDataAccessApiUsageException("count query methods must return a Mono"); + } + } + } + @Override protected ElasticsearchParameters createParameters(Method method) { return new ElasticsearchParameters(method); 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 8ee9f6e6b..a20625825 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 @@ -75,7 +75,7 @@ public class ReactiveElasticsearchStringQuery extends AbstractReactiveElasticsea @Override boolean isCountQuery() { - return false; + return queryMethod.hasCountQueryAnnotation(); } @Override diff --git a/src/test/java/org/springframework/data/elasticsearch/core/mapping/FieldNamingStrategyIntegrationTemplateTest.java b/src/test/java/org/springframework/data/elasticsearch/core/mapping/FieldNamingStrategyIntegrationTemplateTest.java index 8ae38d4b3..4fcae45f6 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/mapping/FieldNamingStrategyIntegrationTemplateTest.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/mapping/FieldNamingStrategyIntegrationTemplateTest.java @@ -1,5 +1,17 @@ /* - * (c) Copyright 2021 sothawo + * Copyright 2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package org.springframework.data.elasticsearch.core.mapping; @@ -10,7 +22,7 @@ import org.springframework.data.mapping.model.SnakeCaseFieldNamingStrategy; import org.springframework.test.context.ContextConfiguration; /** - * @author P.J. Meisch (pj.meisch@sothawo.com) + * @author Peter-Josef Meisch */ @ContextConfiguration(classes = { FieldNamingStrategyIntegrationTemplateTest.Config.class }) public class FieldNamingStrategyIntegrationTemplateTest extends FieldNamingStrategyIntegrationTest { diff --git a/src/test/java/org/springframework/data/elasticsearch/repositories/custommethod/CustomMethodRepositoryBaseTests.java b/src/test/java/org/springframework/data/elasticsearch/repositories/custommethod/CustomMethodRepositoryBaseTests.java index 2d16a74ad..3eecaf2b5 100644 --- a/src/test/java/org/springframework/data/elasticsearch/repositories/custommethod/CustomMethodRepositoryBaseTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/repositories/custommethod/CustomMethodRepositoryBaseTests.java @@ -35,6 +35,7 @@ import java.util.stream.Stream; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.annotation.Id; @@ -44,6 +45,7 @@ import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort.Order; +import org.springframework.data.elasticsearch.annotations.CountQuery; import org.springframework.data.elasticsearch.annotations.Document; import org.springframework.data.elasticsearch.annotations.Field; import org.springframework.data.elasticsearch.annotations.Highlight; @@ -911,6 +913,31 @@ public abstract class CustomMethodRepositoryBaseTests { assertThat(count).isEqualTo(1L); } + @Test // #1156 + @DisplayName("should count with query by type") + void shouldCountWithQueryByType() { + + String documentId = nextIdAsString(); + SampleEntity sampleEntity = new SampleEntity(); + sampleEntity.setId(documentId); + sampleEntity.setType("test"); + sampleEntity.setMessage("some message"); + + repository.save(sampleEntity); + + documentId = nextIdAsString(); + SampleEntity sampleEntity2 = new SampleEntity(); + sampleEntity2.setId(documentId); + sampleEntity2.setType("test2"); + sampleEntity2.setMessage("some message"); + + repository.save(sampleEntity2); + + long count = repository.countWithQueryByType("test"); + + assertThat(count).isEqualTo(1L); + } + @Test // DATAES-106 public void shouldCountCustomMethodForNot() { @@ -1746,6 +1773,9 @@ public abstract class CustomMethodRepositoryBaseTests { SearchHits searchBy(Sort sort); SearchPage searchByMessage(String message, Pageable pageable); + + @CountQuery("{\"bool\" : {\"must\" : {\"term\" : {\"type\" : \"?0\"}}}}") + long countWithQueryByType(String type); } /** diff --git a/src/test/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchQueryMethodUnitTests.java b/src/test/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchQueryMethodUnitTests.java new file mode 100644 index 000000000..3325cd54c --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchQueryMethodUnitTests.java @@ -0,0 +1,92 @@ +/* + * Copyright 2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.repository.query; + +import static org.assertj.core.api.Assertions.*; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.lang.reflect.Method; +import java.util.List; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.dao.InvalidDataAccessApiUsageException; +import org.springframework.data.annotation.Id; +import org.springframework.data.elasticsearch.annotations.CountQuery; +import org.springframework.data.elasticsearch.annotations.Document; +import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext; +import org.springframework.data.projection.ProjectionFactory; +import org.springframework.data.projection.SpelAwareProxyProjectionFactory; +import org.springframework.data.repository.Repository; +import org.springframework.data.repository.core.support.DefaultRepositoryMetadata; +import org.springframework.lang.Nullable; + +/** + * @author Peter-Josef Meisch + */ +public class ElasticsearchQueryMethodUnitTests { + + private SimpleElasticsearchMappingContext mappingContext; + + @BeforeEach + public void setUp() { + mappingContext = new SimpleElasticsearchMappingContext(); + } + + @Test // #1156 + @DisplayName("should reject count query method not returning a Long") + void shouldRejectCountQueryMethodNotReturningLong() { + + assertThatThrownBy(() -> queryMethod(PersonRepository.class, "invalidCountQueryResult", String.class)) + .isInstanceOf(InvalidDataAccessApiUsageException.class); + } + + @Test // #1156 + @DisplayName("should accept count query method returning a Long") + void shouldAcceptCountQueryMethodReturningALong() throws Exception { + queryMethod(PersonRepository.class, "validCountQueryResult", String.class); + } + + private ElasticsearchQueryMethod queryMethod(Class repository, String name, Class... parameters) + throws Exception { + + Method method = repository.getMethod(name, parameters); + ProjectionFactory factory = new SpelAwareProxyProjectionFactory(); + return new ElasticsearchQueryMethod(method, new DefaultRepositoryMetadata(repository), factory, mappingContext); + } + + interface PersonRepository extends Repository { + @CountQuery("{}") + List invalidCountQueryResult(String name); // invalid return type here + + @CountQuery("{}") + Long validCountQueryResult(String name); + } + + @Data + @AllArgsConstructor + @NoArgsConstructor + @Document(indexName = "query-method-unit-tests") + private static class Person { + @Id private String id; + @Nullable private String name; + @Nullable private String firstName; + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/repository/query/ReactiveElasticsearchQueryMethodUnitTests.java b/src/test/java/org/springframework/data/elasticsearch/repository/query/ReactiveElasticsearchQueryMethodUnitTests.java index fd9f95699..b7c3ab193 100644 --- a/src/test/java/org/springframework/data/elasticsearch/repository/query/ReactiveElasticsearchQueryMethodUnitTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/repository/query/ReactiveElasticsearchQueryMethodUnitTests.java @@ -32,12 +32,14 @@ import java.util.List; import java.util.Map; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.data.annotation.Id; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; +import org.springframework.data.elasticsearch.annotations.CountQuery; import org.springframework.data.elasticsearch.annotations.Document; import org.springframework.data.elasticsearch.annotations.Field; import org.springframework.data.elasticsearch.annotations.FieldType; @@ -52,7 +54,6 @@ import org.springframework.lang.Nullable; /** * @author Christoph Strobl - * @currentRead Fool's Fate - Robin Hobb */ public class ReactiveElasticsearchQueryMethodUnitTests { @@ -115,6 +116,20 @@ public class ReactiveElasticsearchQueryMethodUnitTests { assertThat(method.getEntityInformation().getJavaType()).isAssignableFrom(Person.class); } + @Test // #1156 + @DisplayName("should reject count query method not returning a Mono of Long") + void shouldRejectCountQueryMethodNotReturningAMonoOfLong() { + + assertThatThrownBy(() -> queryMethod(PersonRepository.class, "invalidCountQueryResult", String.class)) + .isInstanceOf(InvalidDataAccessApiUsageException.class); + } + + @Test // #1156 + @DisplayName("should accept count query method returning a Mono of Long") + void shouldAcceptCountQueryMethodReturningAMonoOfLong() throws Exception { + queryMethod(PersonRepository.class, "validCountQueryResult", String.class); + } + private ReactiveElasticsearchQueryMethod queryMethod(Class repository, String name, Class... parameters) throws Exception { @@ -137,6 +152,12 @@ public class ReactiveElasticsearchQueryMethodUnitTests { Flux findByName(String name, Pageable pageRequest); void deleteByName(String name); + + @CountQuery("{}") + Flux invalidCountQueryResult(String name); // invalid return type here + + @CountQuery("{}") + Mono validCountQueryResult(String name); } interface NonReactiveRepository extends Repository { @@ -156,6 +177,7 @@ public class ReactiveElasticsearchQueryMethodUnitTests { @Nullable @Id private String id; @Nullable private String name; + @Nullable private String firstName; @Nullable @Field(type = FieldType.Nested) private List car; diff --git a/src/test/java/org/springframework/data/elasticsearch/repository/support/simple/SimpleElasticsearchRepositoryIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/repository/support/SimpleElasticsearchRepositoryIntegrationTests.java similarity index 99% rename from src/test/java/org/springframework/data/elasticsearch/repository/support/simple/SimpleElasticsearchRepositoryIntegrationTests.java rename to src/test/java/org/springframework/data/elasticsearch/repository/support/SimpleElasticsearchRepositoryIntegrationTests.java index 8c9665425..103b590fb 100644 --- a/src/test/java/org/springframework/data/elasticsearch/repository/support/simple/SimpleElasticsearchRepositoryIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/repository/support/SimpleElasticsearchRepositoryIntegrationTests.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.data.elasticsearch.repository.support.simple; +package org.springframework.data.elasticsearch.repository.support; import static org.assertj.core.api.Assertions.*; import static org.elasticsearch.index.query.QueryBuilders.*; @@ -77,7 +77,7 @@ class SimpleElasticsearchRepositoryIntegrationTests { @Configuration @Import({ ElasticsearchRestTemplateConfiguration.class }) @EnableElasticsearchRepositories( - basePackages = { "org.springframework.data.elasticsearch.repository.support.simple" }, + basePackages = { "org.springframework.data.elasticsearch.repository.support" }, considerNestedRepositories = true) static class Config {} diff --git a/src/test/java/org/springframework/data/elasticsearch/repository/support/SimpleReactiveElasticsearchRepositoryTests.java b/src/test/java/org/springframework/data/elasticsearch/repository/support/SimpleReactiveElasticsearchRepositoryTests.java index d97d1314b..146633f6f 100644 --- a/src/test/java/org/springframework/data/elasticsearch/repository/support/SimpleReactiveElasticsearchRepositoryTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/repository/support/SimpleReactiveElasticsearchRepositoryTests.java @@ -37,6 +37,7 @@ import java.util.stream.IntStream; import org.elasticsearch.ElasticsearchStatusException; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.reactivestreams.Publisher; import org.springframework.beans.factory.annotation.Autowired; @@ -48,6 +49,7 @@ import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort.Order; +import org.springframework.data.elasticsearch.annotations.CountQuery; import org.springframework.data.elasticsearch.annotations.Document; import org.springframework.data.elasticsearch.annotations.Field; import org.springframework.data.elasticsearch.annotations.Highlight; @@ -316,8 +318,7 @@ class SimpleReactiveElasticsearchRepositoryTests { bulkIndex(SampleEntity.builder().id("id-one").message("message").build(), // SampleEntity.builder().id("id-two").message("test message").build(), // - SampleEntity.builder().id("id-three").message("test test").build()) // - .block(); + SampleEntity.builder().id("id-three").message("test test").build()).block(); repository.countAllByMessage("test") // .as(StepVerifier::create) // @@ -325,6 +326,20 @@ class SimpleReactiveElasticsearchRepositoryTests { .verifyComplete(); } + @Test // #1156 + @DisplayName("should count with string query") + void shouldCountWithStringQuery() { + + bulkIndex(SampleEntity.builder().id("id-one").message("message").build(), // + SampleEntity.builder().id("id-two").message("test message").build(), // + SampleEntity.builder().id("id-three").message("test test").build()).block(); + + repository.retrieveCountByText("test") // + .as(StepVerifier::create) // + .expectNext(2L) // + .verifyComplete(); + } + @Test // DATAES-519 void existsShouldReturnTrueIfExists() { @@ -593,6 +608,9 @@ class SimpleReactiveElasticsearchRepositoryTests { Mono existsAllByMessage(String message); Mono deleteAllByMessage(String message); + + @CountQuery(value = "{\"bool\": {\"must\": [{\"term\": {\"message\": \"?0\"}}]}}") + Mono retrieveCountByText(String message); } /**