diff --git a/pom.xml b/pom.xml
index 8de17af65..2879d3633 100644
--- a/pom.xml
+++ b/pom.xml
@@ -202,6 +202,31 @@
test
+
+
+ org.jetbrains.kotlin
+ kotlin-stdlib
+ true
+
+
+
+ org.jetbrains.kotlin
+ kotlin-reflect
+ true
+
+
+
+ org.jetbrains.kotlinx
+ kotlinx-coroutines-core
+ true
+
+
+
+ org.jetbrains.kotlinx
+ kotlinx-coroutines-reactor
+ true
+
+
org.springframework
@@ -215,6 +240,13 @@
+
+ org.jetbrains.kotlinx
+ kotlinx-coroutines-test
+ test
+ true
+
+
org.slf4j
log4j-over-slf4j
diff --git a/src/main/java/org/springframework/data/elasticsearch/repository/ReactiveElasticsearchRepository.java b/src/main/java/org/springframework/data/elasticsearch/repository/ReactiveElasticsearchRepository.java
index d152c3d34..a94d626dc 100644
--- a/src/main/java/org/springframework/data/elasticsearch/repository/ReactiveElasticsearchRepository.java
+++ b/src/main/java/org/springframework/data/elasticsearch/repository/ReactiveElasticsearchRepository.java
@@ -27,6 +27,4 @@ import org.springframework.data.repository.reactive.ReactiveSortingRepository;
*/
@NoRepositoryBean
public interface ReactiveElasticsearchRepository
- extends ReactiveSortingRepository, ReactiveCrudRepository {
-
-}
+ extends ReactiveSortingRepository, ReactiveCrudRepository {}
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 2c417e9ef..9df5c7062 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
@@ -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);
}
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 1bd7f652c..2a079ae39 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
@@ -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,
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 bad23c8a7..4ab188946 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
@@ -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 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
+
}
diff --git a/src/main/kotlin/org/springframework/data/elasticsearch/core/DocumentOperationsExtensions.kt b/src/main/kotlin/org/springframework/data/elasticsearch/core/DocumentOperationsExtensions.kt
new file mode 100644
index 000000000..c3c1a63b0
--- /dev/null
+++ b/src/main/kotlin/org/springframework/data/elasticsearch/core/DocumentOperationsExtensions.kt
@@ -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 DocumentOperations.get(id: String): T? =
+ get(id, T::class.java)
+
+inline fun DocumentOperations.get(id: String, index: IndexCoordinates): T? =
+ get(id, T::class.java, index)
+
+inline fun DocumentOperations.multiGet(query: Query): List> =
+ multiGet(query, T::class.java)
+
+inline fun DocumentOperations.multiGet(query: Query, index: IndexCoordinates): List> =
+ multiGet(query, T::class.java, index)
+
+inline fun DocumentOperations.exists(id: String): Boolean = exists(id, T::class.java)
+
+inline fun DocumentOperations.bulkIndex(queries: List): List =
+ bulkIndex(queries, T::class.java)
+
+inline fun DocumentOperations.bulkIndex(
+ queries: List,
+ bulkOptions: BulkOptions
+): List =
+ bulkIndex(queries, bulkOptions, T::class.java)
+
+inline fun DocumentOperations.bulkUpdate(queries: List) =
+ bulkUpdate(queries, T::class.java)
+
+inline fun DocumentOperations.delete(id: String): String =
+ delete(id, T::class.java)
+
+inline fun DocumentOperations.delete(query: Query): ByQueryResponse =
+ delete(query, T::class.java)
diff --git a/src/main/kotlin/org/springframework/data/elasticsearch/core/ElasticsearchOperationsExtensions.kt b/src/main/kotlin/org/springframework/data/elasticsearch/core/ElasticsearchOperationsExtensions.kt
new file mode 100644
index 000000000..20ad9eb35
--- /dev/null
+++ b/src/main/kotlin/org/springframework/data/elasticsearch/core/ElasticsearchOperationsExtensions.kt
@@ -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 ElasticsearchOperations.indexOps(): IndexOperations =
+ indexOps(T::class.java)
+
+inline fun ElasticsearchOperations.getIndexCoordinatesFor(): IndexCoordinates =
+ getIndexCoordinatesFor(T::class.java)
diff --git a/src/main/kotlin/org/springframework/data/elasticsearch/core/ReactiveDocumentOperationsExtensions.kt b/src/main/kotlin/org/springframework/data/elasticsearch/core/ReactiveDocumentOperationsExtensions.kt
new file mode 100644
index 000000000..0900e1a4e
--- /dev/null
+++ b/src/main/kotlin/org/springframework/data/elasticsearch/core/ReactiveDocumentOperationsExtensions.kt
@@ -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 ReactiveDocumentOperations.multiGet(query: Query): Flux> = multiGet(query, T::class.java)
+inline fun ReactiveDocumentOperations.multiGet(query: Query, index: IndexCoordinates): Flux> = multiGet(query, T::class.java, index)
+
+inline fun ReactiveDocumentOperations.get(id: String): Mono = get(id, T::class.java)
+inline fun ReactiveDocumentOperations.get(id: String, index: IndexCoordinates): Mono = get(id, T::class.java, index)
+inline fun ReactiveDocumentOperations.exists(id: String): Mono = exists(id, T::class.java)
diff --git a/src/main/kotlin/org/springframework/data/elasticsearch/core/ReactiveElasticsearchOperationsExtensions.kt b/src/main/kotlin/org/springframework/data/elasticsearch/core/ReactiveElasticsearchOperationsExtensions.kt
new file mode 100644
index 000000000..ea75056f1
--- /dev/null
+++ b/src/main/kotlin/org/springframework/data/elasticsearch/core/ReactiveElasticsearchOperationsExtensions.kt
@@ -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 ReactiveElasticsearchOperations.indexOps(): ReactiveIndexOperations =
+ indexOps(T::class.java)
+
+inline fun ReactiveElasticsearchOperations.getIndexCoordinatesFor(): IndexCoordinates =
+ getIndexCoordinatesFor(T::class.java)
diff --git a/src/main/kotlin/org/springframework/data/elasticsearch/core/ReactiveSearchOperationsExtensions.kt b/src/main/kotlin/org/springframework/data/elasticsearch/core/ReactiveSearchOperationsExtensions.kt
new file mode 100644
index 000000000..e4983c1d3
--- /dev/null
+++ b/src/main/kotlin/org/springframework/data/elasticsearch/core/ReactiveSearchOperationsExtensions.kt
@@ -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 ReactiveSearchOperations.count(): Mono = count(T::class.java)
+inline fun ReactiveSearchOperations.count(query: Query): Mono = count(query, T::class.java)
+inline fun ReactiveSearchOperations.count(query: Query, index: IndexCoordinates): Mono = count(query, T::class.java, index)
+inline fun ReactiveSearchOperations.search(query: Query): Flux> = search(query, T::class.java)
+inline fun ReactiveSearchOperations.search(query: Query, index: IndexCoordinates): Flux> = search(query, T::class.java, index)
+inline fun ReactiveSearchOperations.searchForPage(query: Query): Mono> = searchForPage(query, T::class.java)
+inline fun ReactiveSearchOperations.searchForPage(query: Query, index: IndexCoordinates): Mono> = searchForPage(query, T::class.java, index)
+inline fun ReactiveSearchOperations.searchForHits(query: Query): Mono> = searchForHits(query, T::class.java)
+inline fun ReactiveSearchOperations.aggregate(query: Query): Flux> = aggregate(query, T::class.java)
+inline fun ReactiveSearchOperations.aggregate(query: Query, index: IndexCoordinates): Flux> = aggregate(query, T::class.java, index)
+inline fun ReactiveSearchOperations.suggest(query: Query): Mono = suggest(query, T::class.java)
+inline fun ReactiveSearchOperations.suggest(query: Query, index: IndexCoordinates): Mono = suggest(query, T::class.java, index)
diff --git a/src/main/kotlin/org/springframework/data/elasticsearch/core/SearchOperationsExtensions.kt b/src/main/kotlin/org/springframework/data/elasticsearch/core/SearchOperationsExtensions.kt
new file mode 100644
index 000000000..dd797e4cf
--- /dev/null
+++ b/src/main/kotlin/org/springframework/data/elasticsearch/core/SearchOperationsExtensions.kt
@@ -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 SearchOperations.count(query: Query): Long = count(query, T::class.java)
+
+inline fun SearchOperations.searchOne(query: Query): SearchHit? = searchOne(query, T::class.java)
+inline fun SearchOperations.searchOne(query: Query, index: IndexCoordinates): SearchHit? = searchOne(query, T::class.java, index)
+inline fun SearchOperations.multiSearch(queries: List): List> =
+ multiSearch(queries, T::class.java)
+
+inline fun SearchOperations.multiSearch(queries: List, index: IndexCoordinates): List> =
+ multiSearch(queries, T::class.java, index)
+
+inline fun SearchOperations.search(query: Query): SearchHits =
+ search(query, T::class.java)
+
+inline fun SearchOperations.search(query: Query, index: IndexCoordinates): SearchHits =
+ search(query, T::class.java, index)
+
+inline fun SearchOperations.search(query: MoreLikeThisQuery): SearchHits =
+ search(query, T::class.java)
+
+inline fun SearchOperations.search(query: MoreLikeThisQuery, index: IndexCoordinates): SearchHits =
+ search(query, T::class.java, index)
+
+inline fun SearchOperations.searchForStream(query: Query): SearchHitsIterator =
+ searchForStream(query, T::class.java)
+
+inline fun SearchOperations.searchForStream(query: Query, index: IndexCoordinates): SearchHitsIterator =
+ searchForStream(query, T::class.java, index)
diff --git a/src/main/kotlin/org/springframework/data/elasticsearch/repository/CoroutineElasticsearchRepository.kt b/src/main/kotlin/org/springframework/data/elasticsearch/repository/CoroutineElasticsearchRepository.kt
new file mode 100644
index 000000000..994eb49d5
--- /dev/null
+++ b/src/main/kotlin/org/springframework/data/elasticsearch/repository/CoroutineElasticsearchRepository.kt
@@ -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 : CoroutineCrudRepository, CoroutineSortingRepository
diff --git a/src/test/kotlin/org/springframework/data/elasticsearch/repository/query/CoroutineRepositoryELCIntegrationTests.kt b/src/test/kotlin/org/springframework/data/elasticsearch/repository/query/CoroutineRepositoryELCIntegrationTests.kt
new file mode 100644
index 000000000..57c5f6356
--- /dev/null
+++ b/src/test/kotlin/org/springframework/data/elasticsearch/repository/query/CoroutineRepositoryELCIntegrationTests.kt
@@ -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")
+ }
+ }
+}
diff --git a/src/test/kotlin/org/springframework/data/elasticsearch/repository/query/CoroutineRepositoryIntegrationTests.kt b/src/test/kotlin/org/springframework/data/elasticsearch/repository/query/CoroutineRepositoryIntegrationTests.kt
new file mode 100644
index 000000000..99ebe2a51
--- /dev/null
+++ b/src/test/kotlin/org/springframework/data/elasticsearch/repository/query/CoroutineRepositoryIntegrationTests.kt
@@ -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 {
+
+ suspend fun searchByText(text: String): List
+ suspend fun findByText(text: String): Flow
+ suspend fun queryByText(text: String): Flow>
+ }
+}
diff --git a/src/test/kotlin/org/springframework/data/elasticsearch/repository/query/ReactiveElasticsearchQueryMethodCoroutineUnitTests.kt b/src/test/kotlin/org/springframework/data/elasticsearch/repository/query/ReactiveElasticsearchQueryMethodCoroutineUnitTests.kt
new file mode 100644
index 000000000..1b89d7c43
--- /dev/null
+++ b/src/test/kotlin/org/springframework/data/elasticsearch/repository/query/ReactiveElasticsearchQueryMethodCoroutineUnitTests.kt
@@ -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 {
+
+ suspend fun findSuspendAllByName(): Flow
+
+ fun findAllByName(): Flow
+
+ suspend fun findSuspendByName(): List
+ }
+
+ @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?
+ )
+}