Add Kotlin extensions for several interfaces and CoroutineElasticsearchRepository.

Original Pull Request #2573
Closes #2545
This commit is contained in:
Peter-Josef Meisch 2023-05-21 21:56:19 +02:00 committed by GitHub
parent f973236588
commit cf09e57736
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 505 additions and 12 deletions

32
pom.xml
View File

@ -202,6 +202,31 @@
<scope>test</scope>
</dependency>
<!-- Kotlin extension -->
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-reflect</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-coroutines-core</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-coroutines-reactor</artifactId>
<optional>true</optional>
</dependency>
<!-- Test -->
<dependency>
<groupId>org.springframework</groupId>
@ -215,6 +240,13 @@
</exclusions>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-coroutines-test</artifactId>
<scope>test</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>

View File

@ -27,6 +27,4 @@ import org.springframework.data.repository.reactive.ReactiveSortingRepository;
*/
@NoRepositoryBean
public interface ReactiveElasticsearchRepository<T, ID>
extends ReactiveSortingRepository<T, ID>, ReactiveCrudRepository<T, ID> {
}
extends ReactiveSortingRepository<T, ID>, ReactiveCrudRepository<T, ID> {}

View File

@ -29,6 +29,7 @@ import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.data.repository.query.ParametersParameterAccessor;
import org.springframework.data.repository.query.QueryMethod;
import org.springframework.data.repository.query.RepositoryQuery;
import org.springframework.data.repository.query.ResultProcessor;
import org.springframework.data.util.StreamUtils;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@ -75,7 +76,9 @@ public abstract class AbstractElasticsearchRepositoryQuery implements Repository
public Object execute(Object[] parameters) {
ParametersParameterAccessor parameterAccessor = getParameterAccessor(parameters);
Class<?> clazz = getResultClass();
ResultProcessor resultProcessor = queryMethod.getResultProcessor().withDynamicProjection(parameterAccessor);
Class<?> clazz = resultProcessor.getReturnedType().getDomainType();
Query query = createQuery(parameters);
IndexCoordinates index = elasticsearchOperations.getIndexCoordinatesFor(clazz);
@ -132,8 +135,10 @@ public abstract class AbstractElasticsearchRepositoryQuery implements Repository
public Query createQuery(Object[] parameters) {
Class<?> clazz = getResultClass();
ParametersParameterAccessor parameterAccessor = getParameterAccessor(parameters);
ResultProcessor resultProcessor = queryMethod.getResultProcessor().withDynamicProjection(parameterAccessor);
Class<?> returnedType = resultProcessor.getReturnedType().getDomainType();
Query query = createQuery(parameterAccessor);
Assert.notNull(query, "unsupported query");
@ -151,10 +156,6 @@ public abstract class AbstractElasticsearchRepositoryQuery implements Repository
return query;
}
private Class<?> getResultClass() {
return queryMethod.getResultProcessor().getReturnedType().getDomainType();
}
private ParametersParameterAccessor getParameterAccessor(Object[] parameters) {
return new ParametersParameterAccessor(queryMethod.getParameters(), parameters);
}

View File

@ -21,6 +21,7 @@ import reactor.core.publisher.Mono;
import org.reactivestreams.Publisher;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.core.SearchHitSupport;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
@ -81,6 +82,13 @@ abstract class AbstractReactiveElasticsearchRepositoryQuery implements Repositor
private Object execute(ElasticsearchParameterAccessor parameterAccessor) {
ResultProcessor processor = queryMethod.getResultProcessor().withDynamicProjection(parameterAccessor);
var returnedType = processor.getReturnedType();
Class<?> domainType = returnedType.getDomainType();
Class<?> typeToRead = returnedType.getTypeToRead();
if (SearchHit.class.isAssignableFrom(typeToRead)) {
typeToRead = queryMethod.unwrappedReturnType;
}
Query query = createQuery(parameterAccessor);
@ -100,8 +108,7 @@ abstract class AbstractReactiveElasticsearchRepositoryQuery implements Repositor
ReactiveElasticsearchQueryExecution execution = getExecution(parameterAccessor,
new ResultProcessingConverter(processor));
var returnedType = processor.getReturnedType();
return execution.execute(query, returnedType.getDomainType(), returnedType.getTypeToRead(), index);
return execution.execute(query, domainType, typeToRead, index);
}
private ReactiveElasticsearchQueryExecution getExecution(ElasticsearchParameterAccessor accessor,

View File

@ -43,6 +43,8 @@ import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.query.ParameterAccessor;
import org.springframework.data.repository.query.QueryMethod;
import org.springframework.data.repository.util.QueryExecutionConverters;
import org.springframework.data.repository.util.ReactiveWrapperConverters;
import org.springframework.data.util.Lazy;
import org.springframework.data.util.TypeInformation;
import org.springframework.lang.Nullable;
@ -62,9 +64,16 @@ import org.springframework.util.ClassUtils;
*/
public class ElasticsearchQueryMethod extends QueryMethod {
// the following 2 variables exits in the base class, but are private. We need them for
// correct handling of return types (SearchHits), so we have our own values here.
// Alas this means that we have to copy code that initializes these variables and in the
// base class uses them in order to use our variables
protected final Method method;
protected final Class<?> unwrappedReturnType;
private Boolean unwrappedReturnTypeFromSearchHit = null;
private final MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext;
@Nullable private ElasticsearchEntityMetadata<?> metadata;
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<HighlightQuery> highlightQueryLazy = Lazy.of(this::createAnnotatedHighlightQuery);
@ -83,6 +92,7 @@ public class ElasticsearchQueryMethod extends QueryMethod {
this.queryAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, Query.class);
this.highlightAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, Highlight.class);
this.sourceFilters = AnnotatedElementUtils.findMergedAnnotation(method, SourceFilters.class);
this.unwrappedReturnType = potentiallyUnwrapReturnTypeFor(repositoryMetadata, method);
verifyCountQueryTypes();
}
@ -188,6 +198,11 @@ public class ElasticsearchQueryMethod extends QueryMethod {
* @since 4.0
*/
public boolean isSearchHitMethod() {
if (unwrappedReturnTypeFromSearchHit != null && unwrappedReturnTypeFromSearchHit) {
return true;
}
Class<?> methodReturnType = method.getReturnType();
if (SearchHits.class.isAssignableFrom(methodReturnType)) {
@ -322,4 +337,32 @@ public class ElasticsearchQueryMethod extends QueryMethod {
return fieldNames.toArray(new String[0]);
}
// region Copied from QueryMethod base class
/*
* Copied from the QueryMethod class adding support for collections of SearchHit instances. No static method here.
*/
private Class<? extends Object> potentiallyUnwrapReturnTypeFor(RepositoryMetadata metadata, Method method) {
TypeInformation<?> returnType = metadata.getReturnType(method);
if (!QueryExecutionConverters.supports(returnType.getType())
&& !ReactiveWrapperConverters.supports(returnType.getType())) {
return returnType.getType();
} else {
TypeInformation<?> componentType = returnType.getComponentType();
if (componentType == null) {
throw new IllegalStateException(
String.format("Couldn't find component type for return value of method %s", method));
} else {
if (SearchHit.class.isAssignableFrom(componentType.getType())) {
unwrappedReturnTypeFromSearchHit = true;
return componentType.getComponentType().getType();
} else {
return componentType.getType();
}
}
}
}
// endregion
}

View File

@ -0,0 +1,46 @@
package org.springframework.data.elasticsearch.core
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates
import org.springframework.data.elasticsearch.core.query.BulkOptions
import org.springframework.data.elasticsearch.core.query.ByQueryResponse
import org.springframework.data.elasticsearch.core.query.IndexQuery
import org.springframework.data.elasticsearch.core.query.Query
import org.springframework.data.elasticsearch.core.query.UpdateQuery
/**
* Extension functions for [DocumentOperations] methods that take a Class parameter leveraging reified type parameters.
* @author Peter-Josef Meisch
* @since 5.2
*/
inline fun <reified T : Any> DocumentOperations.get(id: String): T? =
get(id, T::class.java)
inline fun <reified T : Any> DocumentOperations.get(id: String, index: IndexCoordinates): T? =
get(id, T::class.java, index)
inline fun <reified T : Any> DocumentOperations.multiGet(query: Query): List<MultiGetItem<T>> =
multiGet(query, T::class.java)
inline fun <reified T : Any> DocumentOperations.multiGet(query: Query, index: IndexCoordinates): List<MultiGetItem<T>> =
multiGet(query, T::class.java, index)
inline fun <reified T : Any> DocumentOperations.exists(id: String): Boolean = exists(id, T::class.java)
inline fun <reified T : Any> DocumentOperations.bulkIndex(queries: List<IndexQuery>): List<IndexedObjectInformation> =
bulkIndex(queries, T::class.java)
inline fun <reified T : Any> DocumentOperations.bulkIndex(
queries: List<IndexQuery>,
bulkOptions: BulkOptions
): List<IndexedObjectInformation> =
bulkIndex(queries, bulkOptions, T::class.java)
inline fun <reified T : Any> DocumentOperations.bulkUpdate(queries: List<UpdateQuery>) =
bulkUpdate(queries, T::class.java)
inline fun <reified T : Any> DocumentOperations.delete(id: String): String =
delete(id, T::class.java)
inline fun <reified T : Any> DocumentOperations.delete(query: Query): ByQueryResponse =
delete(query, T::class.java)

View File

@ -0,0 +1,15 @@
package org.springframework.data.elasticsearch.core
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates
/**
* Extension functions for [DocumentOperations] methods that take a Class parameter leveraging reified type parameters.
* @author Peter-Josef Meisch
* @since 5.2
*/
inline fun <reified T : Any> ElasticsearchOperations.indexOps(): IndexOperations =
indexOps(T::class.java)
inline fun <reified T : Any> ElasticsearchOperations.getIndexCoordinatesFor(): IndexCoordinates =
getIndexCoordinatesFor(T::class.java)

View File

@ -0,0 +1,19 @@
package org.springframework.data.elasticsearch.core
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates
import org.springframework.data.elasticsearch.core.query.Query
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
/**
* Extension functions for [ReactiveDocumentOperations] methods that take a Class parameter leveraging reified type parameters.
* @author Peter-Josef Meisch
* @since 5.2
*/
inline fun <reified T : Any> ReactiveDocumentOperations.multiGet(query: Query): Flux<MultiGetItem<T>> = multiGet(query, T::class.java)
inline fun <reified T : Any> ReactiveDocumentOperations.multiGet(query: Query, index: IndexCoordinates): Flux<MultiGetItem<T>> = multiGet(query, T::class.java, index)
inline fun <reified T : Any> ReactiveDocumentOperations.get(id: String): Mono<T> = get(id, T::class.java)
inline fun <reified T : Any> ReactiveDocumentOperations.get(id: String, index: IndexCoordinates): Mono<T> = get(id, T::class.java, index)
inline fun <reified T : Any> ReactiveDocumentOperations.exists(id: String): Mono<Boolean> = exists(id, T::class.java)

View File

@ -0,0 +1,15 @@
package org.springframework.data.elasticsearch.core
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates
/**
* Extension functions for [ReacctiveElasticsearchOperations] methods that take a Class parameter leveraging reified type parameters.
* @author Peter-Josef Meisch
* @since 5.2
*/
inline fun <reified T : Any> ReactiveElasticsearchOperations.indexOps(): ReactiveIndexOperations =
indexOps(T::class.java)
inline fun <reified T : Any> ReactiveElasticsearchOperations.getIndexCoordinatesFor(): IndexCoordinates =
getIndexCoordinatesFor(T::class.java)

View File

@ -0,0 +1,26 @@
package org.springframework.data.elasticsearch.core
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates
import org.springframework.data.elasticsearch.core.query.Query
import org.springframework.data.elasticsearch.core.suggest.response.Suggest
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
/**
* Extension functions for [SearchOperations] methods that take a Class parameter leveraging reified type parameters.
* @author Peter-Josef Meisch
* @since 5.2
*/
inline fun <reified T : Any> ReactiveSearchOperations.count(): Mono<Long> = count(T::class.java)
inline fun <reified T : Any> ReactiveSearchOperations.count(query: Query): Mono<Long> = count(query, T::class.java)
inline fun <reified T : Any> ReactiveSearchOperations.count(query: Query, index: IndexCoordinates): Mono<Long> = count(query, T::class.java, index)
inline fun <reified T : Any> ReactiveSearchOperations.search(query: Query): Flux<SearchHit<T>> = search(query, T::class.java)
inline fun <reified T : Any> ReactiveSearchOperations.search(query: Query, index: IndexCoordinates): Flux<SearchHit<T>> = search(query, T::class.java, index)
inline fun <reified T : Any> ReactiveSearchOperations.searchForPage(query: Query): Mono<SearchPage<T>> = searchForPage(query, T::class.java)
inline fun <reified T : Any> ReactiveSearchOperations.searchForPage(query: Query, index: IndexCoordinates): Mono<SearchPage<T>> = searchForPage(query, T::class.java, index)
inline fun <reified T : Any> ReactiveSearchOperations.searchForHits(query: Query): Mono<ReactiveSearchHits<T>> = searchForHits(query, T::class.java)
inline fun <reified T : Any> ReactiveSearchOperations.aggregate(query: Query): Flux<out AggregationContainer<*>> = aggregate(query, T::class.java)
inline fun <reified T : Any> ReactiveSearchOperations.aggregate(query: Query, index: IndexCoordinates): Flux<out AggregationContainer<*>> = aggregate(query, T::class.java, index)
inline fun <reified T : Any> ReactiveSearchOperations.suggest(query: Query): Mono<Suggest> = suggest(query, T::class.java)
inline fun <reified T : Any> ReactiveSearchOperations.suggest(query: Query, index: IndexCoordinates): Mono<Suggest> = suggest(query, T::class.java, index)

View File

@ -0,0 +1,54 @@
/*
* Copyright 2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.core
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates
import org.springframework.data.elasticsearch.core.query.MoreLikeThisQuery
import org.springframework.data.elasticsearch.core.query.Query
/**
* Extension functions for [SearchOperations] methods that take a Class parameter leveraging reified type parameters.
* @author Peter-Josef Meisch
* @since 5.2
*/
inline fun <reified T : Any> SearchOperations.count(query: Query): Long = count(query, T::class.java)
inline fun <reified T : Any> SearchOperations.searchOne(query: Query): SearchHit<T>? = searchOne(query, T::class.java)
inline fun <reified T : Any> SearchOperations.searchOne(query: Query, index: IndexCoordinates): SearchHit<T>? = searchOne(query, T::class.java, index)
inline fun <reified T : Any> SearchOperations.multiSearch(queries: List<out Query>): List<SearchHits<T>> =
multiSearch(queries, T::class.java)
inline fun <reified T : Any> SearchOperations.multiSearch(queries: List<out Query>, index: IndexCoordinates): List<SearchHits<T>> =
multiSearch(queries, T::class.java, index)
inline fun <reified T : Any> SearchOperations.search(query: Query): SearchHits<T> =
search(query, T::class.java)
inline fun <reified T : Any> SearchOperations.search(query: Query, index: IndexCoordinates): SearchHits<T> =
search(query, T::class.java, index)
inline fun <reified T : Any> SearchOperations.search(query: MoreLikeThisQuery): SearchHits<T> =
search(query, T::class.java)
inline fun <reified T : Any> SearchOperations.search(query: MoreLikeThisQuery, index: IndexCoordinates): SearchHits<T> =
search(query, T::class.java, index)
inline fun <reified T : Any> SearchOperations.searchForStream(query: Query): SearchHitsIterator<T> =
searchForStream(query, T::class.java)
inline fun <reified T : Any> SearchOperations.searchForStream(query: Query, index: IndexCoordinates): SearchHitsIterator<T> =
searchForStream(query, T::class.java, index)

View File

@ -0,0 +1,27 @@
/*
* Copyright 2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.repository
import org.springframework.data.repository.NoRepositoryBean
import org.springframework.data.repository.kotlin.CoroutineCrudRepository
import org.springframework.data.repository.kotlin.CoroutineSortingRepository
/**
* @author Peter-Josef Meisch
* @since 5.2
*/
@NoRepositoryBean
interface CoroutineElasticsearchRepository<T, ID> : CoroutineCrudRepository<T, ID>, CoroutineSortingRepository<T, ID>

View File

@ -0,0 +1,47 @@
/*
* Copyright 2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.repository.query
import org.springframework.context.annotation.*
import org.springframework.data.elasticsearch.junit.jupiter.ReactiveElasticsearchTemplateConfiguration
import org.springframework.data.elasticsearch.repository.CoroutineElasticsearchRepository
import org.springframework.data.elasticsearch.repository.config.EnableReactiveElasticsearchRepositories
import org.springframework.data.elasticsearch.utils.IndexNameProvider
import org.springframework.test.context.ContextConfiguration
/**
* @author Peter-Josef Meisch
* @since 5.2
*/
@ContextConfiguration(classes = [CoroutineRepositoryELCIntegrationTests.Config::class])
class CoroutineRepositoryELCIntegrationTests : CoroutineRepositoryIntegrationTests() {
@Configuration
@Import(ReactiveElasticsearchTemplateConfiguration::class)
@EnableReactiveElasticsearchRepositories(
considerNestedRepositories = true,
includeFilters = [ComponentScan.Filter(
type = FilterType.ASSIGNABLE_TYPE,
classes = [CoroutineElasticsearchRepository::class]
)]
)
open class Config {
@Bean
open fun indexNameProvider(): IndexNameProvider {
return IndexNameProvider("coroutine-repository")
}
}
}

View File

@ -0,0 +1,101 @@
/*
* Copyright 2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.repository.query
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.last
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.test.runTest
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.data.annotation.Id
import org.springframework.data.elasticsearch.annotations.Document
import org.springframework.data.elasticsearch.annotations.Field
import org.springframework.data.elasticsearch.annotations.FieldType
import org.springframework.data.elasticsearch.core.SearchHit
import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest
import org.springframework.data.elasticsearch.repository.CoroutineElasticsearchRepository
import org.springframework.data.elasticsearch.utils.IndexNameProvider
/**
* Integrationtests for the different methods a [org.springframework.data.elasticsearch.repository.CoroutineElasticsearchRepository] can have.
* @author Peter-Josef Meisch
* @since 5.2
*/
@Suppress("SpringJavaInjectionPointsAutowiringInspection")
@SpringIntegrationTest
abstract class CoroutineRepositoryIntegrationTests {
@Autowired
lateinit var indexNameProvider: IndexNameProvider
@Autowired
lateinit var repository: CoroutineEntityRepository
val entities = listOf(
Entity("1", "test"),
Entity("2", "test"),
)
@BeforeEach
fun setUp() = runTest {
repository.saveAll(entities).last()
}
@Test
fun `should instantiate repository`() = runTest {
assertThat(repository).isNotNull()
}
@Test
fun `should run with method returning a list of entities`() = runTest {
val result = repository.searchByText("test")
assertThat(result).containsExactlyInAnyOrderElementsOf(entities)
}
@Test
fun `should run with method returning a flow of entities`() = runTest {
val result = repository.findByText("test").toList(mutableListOf())
assertThat(result).containsExactlyInAnyOrderElementsOf(entities)
}
@Test
fun `should run with method returning a flow of SearchHit`() = runTest {
val result = repository.queryByText("test").toList(mutableListOf())
assertThat(result.map { it.content }).containsExactlyInAnyOrderElementsOf(entities)
}
@Document(indexName = "#{@indexNameProvider.indexName()}")
data class Entity(
@Id val id: String?,
@Field(type = FieldType.Text) val text: String?,
)
interface CoroutineEntityRepository : CoroutineElasticsearchRepository<Entity, String> {
suspend fun searchByText(text: String): List<Entity>
suspend fun findByText(text: String): Flow<Entity>
suspend fun queryByText(text: String): Flow<SearchHit<Entity>>
}
}

View File

@ -0,0 +1,62 @@
package org.springframework.data.elasticsearch.repository.query
import kotlinx.coroutines.flow.Flow
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.springframework.data.annotation.Id
import org.springframework.data.elasticsearch.annotations.Field
import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext
import org.springframework.data.elasticsearch.repository.CoroutineElasticsearchRepository
import org.springframework.data.projection.SpelAwareProxyProjectionFactory
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata
import kotlin.coroutines.Continuation
/**
* a@author Peter-Josef Meisch
* @since 5.2
*/
class ReactiveElasticsearchQueryMethodCoroutineUnitTests {
val projectionFactory = SpelAwareProxyProjectionFactory()
interface PersonRepository : CoroutineElasticsearchRepository<Person, String> {
suspend fun findSuspendAllByName(): Flow<Person>
fun findAllByName(): Flow<Person>
suspend fun findSuspendByName(): List<Person>
}
@Test // #2545
internal fun `should consider methods returning Flow as collection queries`() {
val method = PersonRepository::class.java.getMethod("findAllByName")
val queryMethod = ReactiveElasticsearchQueryMethod(method, DefaultRepositoryMetadata(PersonRepository::class.java), projectionFactory, SimpleElasticsearchMappingContext())
assertThat(queryMethod.isCollectionQuery).isTrue()
}
@Test // #2545
internal fun `should consider suspended methods returning Flow as collection queries`() {
val method = PersonRepository::class.java.getMethod("findSuspendAllByName", Continuation::class.java)
val queryMethod = ReactiveElasticsearchQueryMethod(method, DefaultRepositoryMetadata(PersonRepository::class.java), projectionFactory, SimpleElasticsearchMappingContext())
assertThat(queryMethod.isCollectionQuery).isTrue()
}
@Test // #2545
internal fun `should consider suspended methods returning List as collection queries`() {
val method = PersonRepository::class.java.getMethod("findSuspendByName", Continuation::class.java)
val queryMethod = ReactiveElasticsearchQueryMethod(method, DefaultRepositoryMetadata(PersonRepository::class.java), projectionFactory, SimpleElasticsearchMappingContext())
assertThat(queryMethod.isCollectionQuery).isTrue()
}
data class Person(
@Id val id: String?,
@Field val name: String?
)
}