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 1d40d4ace..863c91667 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 @@ -90,7 +90,8 @@ public abstract class AbstractElasticsearchRepositoryQuery implements Repository Query query = createQuery(parameters); - IndexCoordinates index = elasticsearchOperations.getIndexCoordinatesFor(clazz); + IndexCoordinates index = parameterAccessor + .getIndexCoordinates(elasticsearchOperations.getIndexCoordinatesFor(clazz)); Object result = null; 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 6e6111d81..830de5315 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 @@ -110,7 +110,7 @@ abstract class AbstractReactiveElasticsearchRepositoryQuery implements Repositor evaluationContextProvider); String indexName = queryMethod.getEntityInformation().getIndexName(); - IndexCoordinates index = IndexCoordinates.of(indexName); + IndexCoordinates index = parameterAccessor.getIndexCoordinates(IndexCoordinates.of(indexName)); ReactiveElasticsearchQueryExecution execution = getExecution(parameterAccessor, new ResultProcessingConverter(processor)); diff --git a/src/main/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchParameter.java b/src/main/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchParameter.java index 697549137..67b836172 100644 --- a/src/main/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchParameter.java +++ b/src/main/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchParameter.java @@ -17,6 +17,7 @@ package org.springframework.data.elasticsearch.repository.query; import org.springframework.core.MethodParameter; import org.springframework.data.core.TypeInformation; +import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; import org.springframework.data.elasticsearch.core.query.RuntimeField; import org.springframework.data.elasticsearch.core.query.ScriptedField; import org.springframework.data.repository.query.Parameter; @@ -42,7 +43,8 @@ public class ElasticsearchParameter extends Parameter { @Override public boolean isSpecialParameter() { - return super.isSpecialParameter() || isScriptedFieldParameter() || isRuntimeFieldParameter(); + return super.isSpecialParameter() || isScriptedFieldParameter() || isRuntimeFieldParameter() + || isIndexCoordinatesParameter(); } public Boolean isScriptedFieldParameter() { @@ -52,4 +54,8 @@ public class ElasticsearchParameter extends Parameter { public Boolean isRuntimeFieldParameter() { return RuntimeField.class.isAssignableFrom(getType()); } + + public Boolean isIndexCoordinatesParameter() { + return IndexCoordinates.class.isAssignableFrom(getType()); + } } diff --git a/src/main/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchParameterAccessor.java b/src/main/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchParameterAccessor.java index 9b8e280a7..0f3ebcea6 100644 --- a/src/main/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchParameterAccessor.java +++ b/src/main/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchParameterAccessor.java @@ -15,6 +15,7 @@ */ package org.springframework.data.elasticsearch.repository.query; +import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; import org.springframework.data.repository.query.ParameterAccessor; /** @@ -29,4 +30,11 @@ public interface ElasticsearchParameterAccessor extends ParameterAccessor { * @return */ Object[] getValues(); + + /** + * If there is a parameter of type IndexCoordinates, this parameter value is returned, otherwise the defaults value + * + * @param defaults default value + */ + IndexCoordinates getIndexCoordinates(IndexCoordinates defaults); } diff --git a/src/main/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchParameters.java b/src/main/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchParameters.java index 45b1928e6..0dcf36bd1 100644 --- a/src/main/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchParameters.java +++ b/src/main/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchParameters.java @@ -29,10 +29,11 @@ import org.springframework.data.repository.query.ParametersSource; * @since 3.2 */ public class ElasticsearchParameters extends Parameters { - private final List scriptedFields = new ArrayList<>(); private final List runtimeFields = new ArrayList<>(); + private final int indexCoordinatesIndex; + public ElasticsearchParameters(ParametersSource parametersSource) { super(parametersSource, @@ -53,6 +54,23 @@ public class ElasticsearchParameters extends Parameters domainType) { @@ -61,6 +79,7 @@ public class ElasticsearchParameters extends Parameters parameters) { super(parameters); + this.indexCoordinatesIndex = initIndexCoordinatesIndex(); } @Override @@ -75,4 +94,12 @@ public class ElasticsearchParameters extends Parameters getRuntimeFields() { return runtimeFields; } + + public boolean hasIndexCoordinatesParameter() { + return this.indexCoordinatesIndex != -1; + } + + public int getIndexCoordinatesIndex() { + return indexCoordinatesIndex; + } } diff --git a/src/main/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchParametersParameterAccessor.java b/src/main/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchParametersParameterAccessor.java index cd17b5e56..03d35fa59 100644 --- a/src/main/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchParametersParameterAccessor.java +++ b/src/main/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchParametersParameterAccessor.java @@ -15,6 +15,7 @@ */ package org.springframework.data.elasticsearch.repository.query; +import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; import org.springframework.data.repository.query.ParametersParameterAccessor; /** @@ -25,6 +26,7 @@ public class ElasticsearchParametersParameterAccessor extends ParametersParamete implements ElasticsearchParameterAccessor { private final Object[] values; + private final ElasticsearchParameters eleasticSearchParameters; /** * Creates a new {@link ElasticsearchParametersParameterAccessor}. @@ -36,10 +38,19 @@ public class ElasticsearchParametersParameterAccessor extends ParametersParamete super(method.getParameters(), values); this.values = values; + this.eleasticSearchParameters = method.getParameters(); } @Override public Object[] getValues() { return values; } + + @Override + public IndexCoordinates getIndexCoordinates(IndexCoordinates defaults) { + if (!eleasticSearchParameters.hasIndexCoordinatesParameter()) { + return defaults; + } + return (IndexCoordinates) getValues()[eleasticSearchParameters.getIndexCoordinatesIndex()]; + } } 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 77e3afded..56a3c7006 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 @@ -373,6 +373,11 @@ public class ElasticsearchQueryMethod extends QueryMethod { return fieldNames.toArray(new String[0]); } + @Override + public ElasticsearchParameters getParameters() { + return (ElasticsearchParameters) super.getParameters(); + } + // region Copied from QueryMethod base class /* * Copied from the QueryMethod class adding support for collections of SearchHit instances. No static method here. 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 1d81aeedf..639dda8ff 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 @@ -136,11 +136,6 @@ public class ReactiveElasticsearchQueryMethod extends ElasticsearchQueryMethod { return true; } - @Override - public ElasticsearchParameters getParameters() { - return (ElasticsearchParameters) super.getParameters(); - } - @Override protected boolean isAllowedGenericType(ParameterizedType methodGenericReturnType) { return super.isAllowedGenericType(methodGenericReturnType) diff --git a/src/test/java/org/springframework/data/elasticsearch/repository/query/indexcoordinates/IndexCoordinatesParameterELCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/repository/query/indexcoordinates/IndexCoordinatesParameterELCIntegrationTests.java new file mode 100644 index 000000000..9e4fb821a --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/repository/query/indexcoordinates/IndexCoordinatesParameterELCIntegrationTests.java @@ -0,0 +1,23 @@ +package org.springframework.data.elasticsearch.repository.query.indexcoordinates; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchTemplateConfiguration; +import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; +import org.springframework.test.context.ContextConfiguration; + +@ContextConfiguration(classes = { IndexCoordinatesParameterELCIntegrationTests.Config.class }) +public class IndexCoordinatesParameterELCIntegrationTests extends IndexCoordinatesParameterIntegrationTests { + + @Configuration + @Import({ ElasticsearchTemplateConfiguration.class }) + @EnableElasticsearchRepositories(considerNestedRepositories = true) + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("query-index-coordinates"); + } + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/repository/query/indexcoordinates/IndexCoordinatesParameterIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/repository/query/indexcoordinates/IndexCoordinatesParameterIntegrationTests.java new file mode 100644 index 000000000..62c000129 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/repository/query/indexcoordinates/IndexCoordinatesParameterIntegrationTests.java @@ -0,0 +1,98 @@ +package org.springframework.data.elasticsearch.repository.query.indexcoordinates; + +import org.jspecify.annotations.Nullable; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Order; +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.ElasticsearchOperations; +import org.springframework.data.elasticsearch.core.SearchHits; +import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; +import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest; +import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; + +@SpringIntegrationTest +abstract class IndexCoordinatesParameterIntegrationTests { + @Autowired ElasticsearchOperations operations; + @Autowired IndexNameProvider indexNameProvider; + @Autowired RecordRepository recordRepository; + + @BeforeEach + public void before() { + indexNameProvider.increment(); + } + + @Test + @Order(Integer.MAX_VALUE) + void cleanup() { + operations.indexOps(IndexCoordinates.of(indexNameProvider.getPrefix() + "*")).delete(); + } + + @Test // #2506 + @DisplayName("should use indexcoordinates passes as repository query argument") + void shouldUseIndexCoordinatesPassesAsRepositoryQueryArgument() { + + var record1 = new Record("1", "one"); + var indexName1 = indexNameProvider.indexName(); + var indexCoordinates1 = IndexCoordinates.of(indexName1); + operations.save(record1, indexCoordinates1); + + var record2 = new Record("2", "two"); + var indexName2 = indexName1 + "second"; + var indexCoordinates2 = IndexCoordinates.of(indexName2); + operations.save(record2, indexCoordinates2); + + // search for record1 + var searchHits = recordRepository.findByText("one"); + assert searchHits.getTotalHits() == 1; + searchHits = recordRepository.findByText("one", indexCoordinates2); + assert searchHits.getTotalHits() == 0; + + // search for record2 + searchHits = recordRepository.findByText("two"); + assert searchHits.getTotalHits() == 0; + searchHits = recordRepository.findByText("two", indexCoordinates2); + assert searchHits.getTotalHits() == 1; + } + + @Document(indexName = "#{@indexNameProvider.indexName()}") + static class Record { + @Nullable + @Id private String id; + @Nullable + @Field(type = FieldType.Keyword) private String text; + + public Record(@Nullable String id, @Nullable String text) { + this.id = id; + this.text = text; + } + + public @Nullable String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + public @Nullable String getText() { + return text; + } + + public void setText(@Nullable String text) { + this.text = text; + } + } + + interface RecordRepository extends ElasticsearchRepository { + SearchHits findByText(String text); + + SearchHits findByText(String text, IndexCoordinates index); + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/repository/query/indexcoordinates/ReactiveIndexCoordinatesParameterELCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/repository/query/indexcoordinates/ReactiveIndexCoordinatesParameterELCIntegrationTests.java new file mode 100644 index 000000000..a9a2cc5e7 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/repository/query/indexcoordinates/ReactiveIndexCoordinatesParameterELCIntegrationTests.java @@ -0,0 +1,24 @@ +package org.springframework.data.elasticsearch.repository.query.indexcoordinates; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.elasticsearch.junit.jupiter.ReactiveElasticsearchTemplateConfiguration; +import org.springframework.data.elasticsearch.repository.config.EnableReactiveElasticsearchRepositories; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; +import org.springframework.test.context.ContextConfiguration; + +@ContextConfiguration(classes = { ReactiveIndexCoordinatesParameterELCIntegrationTests.Config.class }) +public class ReactiveIndexCoordinatesParameterELCIntegrationTests + extends ReactiveIndexCoordinatesParameterIntegrationTests { + + @Configuration + @Import({ ReactiveElasticsearchTemplateConfiguration.class }) + @EnableReactiveElasticsearchRepositories(considerNestedRepositories = true) + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("reactive-query-index-coordinates"); + } + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/repository/query/indexcoordinates/ReactiveIndexCoordinatesParameterIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/repository/query/indexcoordinates/ReactiveIndexCoordinatesParameterIntegrationTests.java new file mode 100644 index 000000000..cdc49f1ed --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/repository/query/indexcoordinates/ReactiveIndexCoordinatesParameterIntegrationTests.java @@ -0,0 +1,109 @@ +package org.springframework.data.elasticsearch.repository.query.indexcoordinates; + +import reactor.core.publisher.Flux; +import reactor.test.StepVerifier; + +import org.jspecify.annotations.Nullable; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Order; +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.ReactiveElasticsearchOperations; +import org.springframework.data.elasticsearch.core.SearchHit; +import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; +import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest; +import org.springframework.data.elasticsearch.repository.ReactiveElasticsearchRepository; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; + +@SpringIntegrationTest +abstract class ReactiveIndexCoordinatesParameterIntegrationTests { + @Autowired ReactiveElasticsearchOperations operations; + @Autowired IndexNameProvider indexNameProvider; + @Autowired RecordRepository recordRepository; + + @BeforeEach + public void before() { + indexNameProvider.increment(); + } + + @Test + @Order(Integer.MAX_VALUE) + void cleanup() { + operations.indexOps(IndexCoordinates.of(indexNameProvider.getPrefix() + "*")).delete().block(); + } + + @Test // #2506 + @DisplayName("should use indexcoordinates passes as repository query argument") + void shouldUseIndexCoordinatesPassesAsRepositoryQueryArgument() { + + var record1 = new Record("1", "one"); + var indexName1 = indexNameProvider.indexName(); + var indexCoordinates1 = IndexCoordinates.of(indexName1); + operations.save(record1, indexCoordinates1).block(); + + var record2 = new Record("2", "two"); + var indexName2 = indexName1 + "second"; + var indexCoordinates2 = IndexCoordinates.of(indexName2); + operations.save(record2, indexCoordinates2).block(); + + // search for record1 + recordRepository.findByText("one") + .as(StepVerifier::create) + .expectNextCount(1) + .verifyComplete(); + recordRepository.findByText("one", indexCoordinates2) + .as(StepVerifier::create) + .expectNextCount(0) + .verifyComplete(); + + // search for record2 + recordRepository.findByText("two") + .as(StepVerifier::create) + .expectNextCount(0) + .verifyComplete(); + recordRepository.findByText("two", indexCoordinates2) + .as(StepVerifier::create) + .expectNextCount(1) + .verifyComplete(); + } + + @Document(indexName = "#{@indexNameProvider.indexName()}") + static class Record { + @Nullable + @Id private String id; + @Nullable + @Field(type = FieldType.Keyword) private String text; + + public Record(@Nullable String id, @Nullable String text) { + this.id = id; + this.text = text; + } + + public @Nullable String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + public @Nullable String getText() { + return text; + } + + public void setText(@Nullable String text) { + this.text = text; + } + } + + interface RecordRepository extends ReactiveElasticsearchRepository { + Flux> findByText(String text); + + Flux> findByText(String text, IndexCoordinates index); + } +}