Add @CountQuery annotation.

Original Pull Request #1682 
Closes #1156
This commit is contained in:
Peter-Josef Meisch 2021-02-07 20:21:02 +01:00 committed by GitHub
parent 910ca7b665
commit fe8c4f12ed
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 308 additions and 23 deletions

View File

@ -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 "";
}

View File

@ -22,24 +22,32 @@ import java.lang.annotation.*;
* *
* @author Rizwan Idrees * @author Rizwan Idrees
* @author Mohsin Husen * @author Mohsin Husen
* @author Peter-Josef Meisch
*/ */
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD) @Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Documented @Documented
public @interface Query { public @interface Query {
/** /**
* 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
*
* @return
*/ */
String value() default ""; String value() default "";
/** /**
* Named Query Named looked up by repository. * Named Query Named looked up by repository.
* *
* @return * @deprecated since 4.2, not implemented and used anywhere
*/ */
String name() default ""; 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;
} }

View File

@ -24,6 +24,7 @@ import org.springframework.data.repository.query.RepositoryQuery;
* *
* @author Rizwan Idrees * @author Rizwan Idrees
* @author Mohsin Husen * @author Mohsin Husen
* @author Peter-Josef Meisch
*/ */
public abstract class AbstractElasticsearchRepositoryQuery implements RepositoryQuery { public abstract class AbstractElasticsearchRepositoryQuery implements RepositoryQuery {
@ -42,4 +43,10 @@ public abstract class AbstractElasticsearchRepositoryQuery implements Repository
public QueryMethod getQueryMethod() { public QueryMethod getQueryMethod() {
return queryMethod; return queryMethod;
} }
/**
* @return {@literal true} if this is a count query
* @since 4.2
*/
public abstract boolean isCountQuery();
} }

View File

@ -43,7 +43,7 @@ import org.springframework.data.repository.query.ResultProcessor;
*/ */
abstract class AbstractReactiveElasticsearchRepositoryQuery implements RepositoryQuery { abstract class AbstractReactiveElasticsearchRepositoryQuery implements RepositoryQuery {
private final ReactiveElasticsearchQueryMethod queryMethod; protected final ReactiveElasticsearchQueryMethod queryMethod;
private final ReactiveElasticsearchOperations elasticsearchOperations; private final ReactiveElasticsearchOperations elasticsearchOperations;
AbstractReactiveElasticsearchRepositoryQuery(ReactiveElasticsearchQueryMethod queryMethod, AbstractReactiveElasticsearchRepositoryQuery(ReactiveElasticsearchQueryMethod queryMethod,

View File

@ -59,6 +59,11 @@ public class ElasticsearchPartQuery extends AbstractElasticsearchRepositoryQuery
this.mappingContext = elasticsearchConverter.getMappingContext(); this.mappingContext = elasticsearchConverter.getMappingContext();
} }
@Override
public boolean isCountQuery() {
return tree.isCountProjection();
}
@Override @Override
public Object execute(Object[] parameters) { public Object execute(Object[] parameters) {
Class<?> clazz = queryMethod.getResultProcessor().getReturnedType().getDomainType(); Class<?> clazz = queryMethod.getResultProcessor().getReturnedType().getDomainType();

View File

@ -20,7 +20,8 @@ import java.lang.reflect.ParameterizedType;
import java.util.Collection; import java.util.Collection;
import java.util.stream.Stream; 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.Highlight;
import org.springframework.data.elasticsearch.annotations.Query; import org.springframework.data.elasticsearch.annotations.Query;
import org.springframework.data.elasticsearch.core.SearchHit; 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.projection.ProjectionFactory;
import org.springframework.data.repository.core.RepositoryMetadata; import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.query.QueryMethod; import org.springframework.data.repository.query.QueryMethod;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.data.util.Lazy; import org.springframework.data.util.Lazy;
import org.springframework.data.util.TypeInformation;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
@ -53,9 +56,9 @@ public class ElasticsearchQueryMethod extends QueryMethod {
private final MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext; private final MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext;
private @Nullable ElasticsearchEntityMetadata<?> metadata; private @Nullable ElasticsearchEntityMetadata<?> metadata;
private final Method method; // private in base class, but needed here as well protected final Method method; // private in base class, but needed here and in derived classes as well
private final Query queryAnnotation; @Nullable private final Query queryAnnotation;
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);
public ElasticsearchQueryMethod(Method method, RepositoryMetadata repositoryMetadata, ProjectionFactory factory, public ElasticsearchQueryMethod(Method method, RepositoryMetadata repositoryMetadata, ProjectionFactory factory,
@ -67,16 +70,32 @@ public class ElasticsearchQueryMethod extends QueryMethod {
this.method = method; this.method = method;
this.mappingContext = mappingContext; this.mappingContext = mappingContext;
this.queryAnnotation = method.getAnnotation(Query.class); this.queryAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, Query.class);
this.highlightAnnotation = method.getAnnotation(Highlight.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() { public boolean hasAnnotatedQuery() {
return this.queryAnnotation != null; return this.queryAnnotation != null;
} }
/**
* @return the query String. Must not be {@literal null} when {@link #hasAnnotatedQuery()} returns true
*/
public String getAnnotatedQuery() { public String getAnnotatedQuery() {
return (String) AnnotationUtils.getValue(queryAnnotation, "value"); return queryAnnotation.value();
} }
/** /**
@ -217,4 +236,14 @@ public class ElasticsearchQueryMethod extends QueryMethod {
public boolean isNotSearchPageMethod() { public boolean isNotSearchPageMethod() {
return !isSearchPageMethod(); 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();
}
} }

View File

@ -69,8 +69,14 @@ public class ElasticsearchStringQuery extends AbstractElasticsearchRepositoryQue
this.query = query; this.query = query;
} }
@Override
public boolean isCountQuery() {
return queryMethod.hasCountQueryAnnotation();
}
@Override @Override
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 accessor = new ParametersParameterAccessor(queryMethod.getParameters(), parameters);
@ -86,7 +92,9 @@ public class ElasticsearchStringQuery extends AbstractElasticsearchRepositoryQue
Object result = null; Object result = null;
if (queryMethod.isPageQuery()) { if (isCountQuery()) {
result = elasticsearchOperations.count(stringQuery, clazz, index);
} else if (queryMethod.isPageQuery()) {
stringQuery.setPageable(accessor.getPageable()); stringQuery.setPageable(accessor.getPageable());
SearchHits<?> searchHits = elasticsearchOperations.search(stringQuery, clazz, index); SearchHits<?> searchHits = elasticsearchOperations.search(stringQuery, clazz, index);
result = SearchHitSupport.searchPageFor(searchHits, stringQuery.getPageable()); result = SearchHitSupport.searchPageFor(searchHits, stringQuery.getPageable());

View File

@ -18,9 +18,11 @@ package org.springframework.data.elasticsearch.repository.query;
import static org.springframework.data.repository.util.ClassUtils.*; import static org.springframework.data.repository.util.ClassUtils.*;
import reactor.core.publisher.Flux; import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType; import java.lang.reflect.ParameterizedType;
import java.util.List;
import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
@ -59,7 +61,6 @@ public class ReactiveElasticsearchQueryMethod extends ElasticsearchQueryMethod {
if (hasParameterOfType(method, Pageable.class)) { if (hasParameterOfType(method, Pageable.class)) {
TypeInformation<?> returnType = ClassTypeInformation.fromReturnTypeOf(method); TypeInformation<?> returnType = ClassTypeInformation.fromReturnTypeOf(method);
boolean multiWrapper = ReactiveWrappers.isMultiValueType(returnType.getType()); boolean multiWrapper = ReactiveWrappers.isMultiValueType(returnType.getType());
boolean singleWrapperWithWrappedPageableResult = ReactiveWrappers.isSingleValueType(returnType.getType()) boolean singleWrapperWithWrappedPageableResult = ReactiveWrappers.isSingleValueType(returnType.getType())
&& (PAGE_TYPE.isAssignableFrom(returnType.getRequiredComponentType()) && (PAGE_TYPE.isAssignableFrom(returnType.getRequiredComponentType())
@ -87,6 +88,20 @@ public class ReactiveElasticsearchQueryMethod extends ElasticsearchQueryMethod {
&& ReactiveWrappers.isMultiValueType(metadata.getReturnType(method).getType()) || super.isCollectionQuery())); && ReactiveWrappers.isMultiValueType(metadata.getReturnType(method).getType()) || super.isCollectionQuery()));
} }
@Override
protected void verifyCountQueryTypes() {
if (hasCountQueryAnnotation()) {
TypeInformation<?> returnType = ClassTypeInformation.fromReturnTypeOf(method);
List<TypeInformation<?>> 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<Long>");
}
}
}
@Override @Override
protected ElasticsearchParameters createParameters(Method method) { protected ElasticsearchParameters createParameters(Method method) {
return new ElasticsearchParameters(method); return new ElasticsearchParameters(method);

View File

@ -75,7 +75,7 @@ public class ReactiveElasticsearchStringQuery extends AbstractReactiveElasticsea
@Override @Override
boolean isCountQuery() { boolean isCountQuery() {
return false; return queryMethod.hasCountQueryAnnotation();
} }
@Override @Override

View File

@ -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; package org.springframework.data.elasticsearch.core.mapping;
@ -10,7 +22,7 @@ import org.springframework.data.mapping.model.SnakeCaseFieldNamingStrategy;
import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.ContextConfiguration;
/** /**
* @author P.J. Meisch (pj.meisch@sothawo.com) * @author Peter-Josef Meisch
*/ */
@ContextConfiguration(classes = { FieldNamingStrategyIntegrationTemplateTest.Config.class }) @ContextConfiguration(classes = { FieldNamingStrategyIntegrationTemplateTest.Config.class })
public class FieldNamingStrategyIntegrationTemplateTest extends FieldNamingStrategyIntegrationTest { public class FieldNamingStrategyIntegrationTemplateTest extends FieldNamingStrategyIntegrationTest {

View File

@ -35,6 +35,7 @@ import java.util.stream.Stream;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.annotation.Id; 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.Pageable;
import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Order; 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.Document;
import org.springframework.data.elasticsearch.annotations.Field; import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.Highlight; import org.springframework.data.elasticsearch.annotations.Highlight;
@ -911,6 +913,31 @@ public abstract class CustomMethodRepositoryBaseTests {
assertThat(count).isEqualTo(1L); 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 @Test // DATAES-106
public void shouldCountCustomMethodForNot() { public void shouldCountCustomMethodForNot() {
@ -1746,6 +1773,9 @@ public abstract class CustomMethodRepositoryBaseTests {
SearchHits<SampleEntity> searchBy(Sort sort); SearchHits<SampleEntity> searchBy(Sort sort);
SearchPage<SampleEntity> searchByMessage(String message, Pageable pageable); SearchPage<SampleEntity> searchByMessage(String message, Pageable pageable);
@CountQuery("{\"bool\" : {\"must\" : {\"term\" : {\"type\" : \"?0\"}}}}")
long countWithQueryByType(String type);
} }
/** /**

View File

@ -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<ReactiveElasticsearchQueryMethodUnitTests.Person, String> {
@CountQuery("{}")
List<Person> 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;
}
}

View File

@ -32,12 +32,14 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.annotation.Id; import org.springframework.data.annotation.Id;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice; 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.Document;
import org.springframework.data.elasticsearch.annotations.Field; import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType; import org.springframework.data.elasticsearch.annotations.FieldType;
@ -52,7 +54,6 @@ import org.springframework.lang.Nullable;
/** /**
* @author Christoph Strobl * @author Christoph Strobl
* @currentRead Fool's Fate - Robin Hobb
*/ */
public class ReactiveElasticsearchQueryMethodUnitTests { public class ReactiveElasticsearchQueryMethodUnitTests {
@ -115,6 +116,20 @@ public class ReactiveElasticsearchQueryMethodUnitTests {
assertThat(method.getEntityInformation().getJavaType()).isAssignableFrom(Person.class); 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) private ReactiveElasticsearchQueryMethod queryMethod(Class<?> repository, String name, Class<?>... parameters)
throws Exception { throws Exception {
@ -137,6 +152,12 @@ public class ReactiveElasticsearchQueryMethodUnitTests {
Flux<Person> findByName(String name, Pageable pageRequest); Flux<Person> findByName(String name, Pageable pageRequest);
void deleteByName(String name); void deleteByName(String name);
@CountQuery("{}")
Flux<Person> invalidCountQueryResult(String name); // invalid return type here
@CountQuery("{}")
Mono<Long> validCountQueryResult(String name);
} }
interface NonReactiveRepository extends Repository<Person, Long> { interface NonReactiveRepository extends Repository<Person, Long> {
@ -156,6 +177,7 @@ public class ReactiveElasticsearchQueryMethodUnitTests {
@Nullable @Id private String id; @Nullable @Id private String id;
@Nullable private String name; @Nullable private String name;
@Nullable private String firstName;
@Nullable @Field(type = FieldType.Nested) private List<Car> car; @Nullable @Field(type = FieldType.Nested) private List<Car> car;

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * 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.assertj.core.api.Assertions.*;
import static org.elasticsearch.index.query.QueryBuilders.*; import static org.elasticsearch.index.query.QueryBuilders.*;
@ -77,7 +77,7 @@ class SimpleElasticsearchRepositoryIntegrationTests {
@Configuration @Configuration
@Import({ ElasticsearchRestTemplateConfiguration.class }) @Import({ ElasticsearchRestTemplateConfiguration.class })
@EnableElasticsearchRepositories( @EnableElasticsearchRepositories(
basePackages = { "org.springframework.data.elasticsearch.repository.support.simple" }, basePackages = { "org.springframework.data.elasticsearch.repository.support" },
considerNestedRepositories = true) considerNestedRepositories = true)
static class Config {} static class Config {}

View File

@ -37,6 +37,7 @@ import java.util.stream.IntStream;
import org.elasticsearch.ElasticsearchStatusException; import org.elasticsearch.ElasticsearchStatusException;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.reactivestreams.Publisher; import org.reactivestreams.Publisher;
import org.springframework.beans.factory.annotation.Autowired; 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.Pageable;
import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Order; 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.Document;
import org.springframework.data.elasticsearch.annotations.Field; import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.Highlight; import org.springframework.data.elasticsearch.annotations.Highlight;
@ -316,8 +318,7 @@ class SimpleReactiveElasticsearchRepositoryTests {
bulkIndex(SampleEntity.builder().id("id-one").message("message").build(), // bulkIndex(SampleEntity.builder().id("id-one").message("message").build(), //
SampleEntity.builder().id("id-two").message("test message").build(), // SampleEntity.builder().id("id-two").message("test message").build(), //
SampleEntity.builder().id("id-three").message("test test").build()) // SampleEntity.builder().id("id-three").message("test test").build()).block();
.block();
repository.countAllByMessage("test") // repository.countAllByMessage("test") //
.as(StepVerifier::create) // .as(StepVerifier::create) //
@ -325,6 +326,20 @@ class SimpleReactiveElasticsearchRepositoryTests {
.verifyComplete(); .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 @Test // DATAES-519
void existsShouldReturnTrueIfExists() { void existsShouldReturnTrueIfExists() {
@ -593,6 +608,9 @@ class SimpleReactiveElasticsearchRepositoryTests {
Mono<Boolean> existsAllByMessage(String message); Mono<Boolean> existsAllByMessage(String message);
Mono<Long> deleteAllByMessage(String message); Mono<Long> deleteAllByMessage(String message);
@CountQuery(value = "{\"bool\": {\"must\": [{\"term\": {\"message\": \"?0\"}}]}}")
Mono<Long> retrieveCountByText(String message);
} }
/** /**