diff --git a/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchRestTemplate.java b/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchRestTemplate.java index 9de8e6902..1d4798f52 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchRestTemplate.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchRestTemplate.java @@ -137,6 +137,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; * @author Lorenzo Spinelli * @author Dmitriy Yakovlev * @author Roman Puchkovskiy + * @author Martin Choraine * @author Farid Azaza */ public class ElasticsearchRestTemplate @@ -1101,6 +1102,10 @@ public class ElasticsearchRestTemplate } } + if (searchQuery.getCollapseBuilder() != null) { + searchRequest.source().collapse(searchQuery.getCollapseBuilder()); + } + if (searchQuery.getHighlightFields() != null || searchQuery.getHighlightBuilder() != null) { HighlightBuilder highlightBuilder = searchQuery.getHighlightBuilder(); if (highlightBuilder == null) { diff --git a/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplate.java b/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplate.java index 2f8d048a9..e471faac5 100755 --- a/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplate.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplate.java @@ -120,6 +120,7 @@ import org.springframework.util.StringUtils; * @author Christoph Strobl * @author Dmitriy Yakovlev * @author Peter-Josef Meisch + * @author Martin Choraine * @author Farid Azaza */ public class ElasticsearchTemplate implements ElasticsearchOperations, EsClient, ApplicationContextAware { @@ -922,6 +923,10 @@ public class ElasticsearchTemplate implements ElasticsearchOperations, EsClient< } } + if (searchQuery.getCollapseBuilder() != null) { + searchRequest.setCollapse(searchQuery.getCollapseBuilder()); + } + if (searchQuery.getHighlightFields() != null || searchQuery.getHighlightBuilder() != null) { HighlightBuilder highlightBuilder = searchQuery.getHighlightBuilder(); if (highlightBuilder == null) { @@ -952,6 +957,7 @@ public class ElasticsearchTemplate implements ElasticsearchOperations, EsClient< searchRequest.addAggregation(aggregatedFacet.getFacet()); } } + return searchRequest.setQuery(searchQuery.getQuery()); } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplate.java b/src/main/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplate.java index 22d741129..0f42188c2 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplate.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplate.java @@ -76,6 +76,7 @@ import org.springframework.util.Assert; * @author Christoph Strobl * @author Mark Paluch * @author Farid Azaza + * @author Martin Choraine * @since 3.2 */ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOperations { @@ -263,6 +264,10 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera searchSourceBuilder.fetchSource(query.getSourceFilter().getIncludes(), query.getSourceFilter().getExcludes()); } + if (query instanceof NativeSearchQuery && ((NativeSearchQuery) query).getCollapseBuilder() != null) { + searchSourceBuilder.collapse(((NativeSearchQuery) query).getCollapseBuilder()); + } + sort(query, entity).forEach(searchSourceBuilder::sort); if (query.getMinScore() > 0) { diff --git a/src/main/java/org/springframework/data/elasticsearch/core/query/NativeSearchQuery.java b/src/main/java/org/springframework/data/elasticsearch/core/query/NativeSearchQuery.java index 627ff5d12..3f0286122 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/query/NativeSearchQuery.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/query/NativeSearchQuery.java @@ -20,6 +20,7 @@ import java.util.List; import org.elasticsearch.index.query.QueryBuilder; import org.elasticsearch.search.aggregations.AbstractAggregationBuilder; +import org.elasticsearch.search.collapse.CollapseBuilder; import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder; import org.elasticsearch.search.sort.SortBuilder; import org.springframework.data.elasticsearch.core.facet.FacetRequest; @@ -33,20 +34,21 @@ import java.util.Arrays; * @author Mohsin Husen * @author Artur Konczak * @author Jean-Baptiste Nizet + * @author Martin Choraine */ public class NativeSearchQuery extends AbstractQuery implements SearchQuery { private QueryBuilder query; private QueryBuilder filter; private List sorts; - private final List scriptFields = new ArrayList<>(); + private final List scriptFields = new ArrayList<>(); + private CollapseBuilder collapseBuilder; private List facets; private List aggregations; private HighlightBuilder highlightBuilder; private HighlightBuilder.Field[] highlightFields; private List indicesBoost; - public NativeSearchQuery(QueryBuilder query) { this.query = query; } @@ -111,6 +113,15 @@ public class NativeSearchQuery extends AbstractQuery implements SearchQuery { scriptFields.addAll(Arrays.asList(scriptField)); } + @Override + public CollapseBuilder getCollapseBuilder() { + return collapseBuilder; + } + + public void setCollapseBuilder(CollapseBuilder collapseBuilder) { + this.collapseBuilder = collapseBuilder; + } + public void addFacet(FacetRequest facetRequest) { if (facets == null) { facets = new ArrayList<>(); @@ -132,7 +143,6 @@ public class NativeSearchQuery extends AbstractQuery implements SearchQuery { return aggregations; } - public void addAggregation(AbstractAggregationBuilder aggregationBuilder) { if (aggregations == null) { aggregations = new ArrayList<>(); diff --git a/src/main/java/org/springframework/data/elasticsearch/core/query/NativeSearchQueryBuilder.java b/src/main/java/org/springframework/data/elasticsearch/core/query/NativeSearchQueryBuilder.java index e37271b87..bef951601 100755 --- a/src/main/java/org/springframework/data/elasticsearch/core/query/NativeSearchQueryBuilder.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/query/NativeSearchQueryBuilder.java @@ -25,6 +25,7 @@ import org.elasticsearch.action.search.SearchType; import org.elasticsearch.action.support.IndicesOptions; import org.elasticsearch.index.query.QueryBuilder; import org.elasticsearch.search.aggregations.AbstractAggregationBuilder; +import org.elasticsearch.search.collapse.CollapseBuilder; import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder; import org.elasticsearch.search.sort.SortBuilder; import org.springframework.data.domain.Pageable; @@ -40,6 +41,7 @@ import org.springframework.data.elasticsearch.core.facet.FacetRequest; * @author Alen Turkovic * @author Sascha Woo * @author Jean-Baptiste Nizet + * @author Martin Choraine * @author Farid Azaza */ public class NativeSearchQueryBuilder { @@ -57,6 +59,7 @@ public class NativeSearchQueryBuilder { private String[] types; private String[] fields; private SourceFilter sourceFilter; + private CollapseBuilder collapseBuilder; private List indicesBoost; private float minScore; private boolean trackScores; @@ -86,6 +89,11 @@ public class NativeSearchQueryBuilder { return this; } + public NativeSearchQueryBuilder withCollapse(String collapseField) { + this.collapseBuilder = new CollapseBuilder(collapseField); + return this; + } + public NativeSearchQueryBuilder addAggregation(AbstractAggregationBuilder aggregationBuilder) { this.aggregationBuilders.add(aggregationBuilder); return this; @@ -207,6 +215,10 @@ public class NativeSearchQueryBuilder { nativeSearchQuery.setScriptFields(scriptFields); } + if (collapseBuilder != null) { + nativeSearchQuery.setCollapseBuilder(collapseBuilder); + } + if (!isEmpty(facetRequests)) { nativeSearchQuery.setFacets(facetRequests); } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/query/SearchQuery.java b/src/main/java/org/springframework/data/elasticsearch/core/query/SearchQuery.java index 1dd88901b..3570d0879 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/query/SearchQuery.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/query/SearchQuery.java @@ -19,6 +19,7 @@ import java.util.List; import org.elasticsearch.index.query.QueryBuilder; import org.elasticsearch.search.aggregations.AbstractAggregationBuilder; +import org.elasticsearch.search.collapse.CollapseBuilder; import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder; import org.elasticsearch.search.sort.SortBuilder; import org.springframework.data.elasticsearch.core.facet.FacetRequest; @@ -30,6 +31,7 @@ import org.springframework.data.elasticsearch.core.facet.FacetRequest; * @author Mohsin Husen * @author Artur Konczak * @author Jean-Baptiste Nizet + * @author Martin Choraine */ public interface SearchQuery extends Query { @@ -50,6 +52,8 @@ public interface SearchQuery extends Query { List getIndicesBoost(); - List getScriptFields(); + List getScriptFields(); + + CollapseBuilder getCollapseBuilder(); } diff --git a/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java b/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java index 099e733e6..87dbb88a5 100755 --- a/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java @@ -105,6 +105,7 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; * @author Ivan Greene * @author Dmitriy Yakovlev * @author Peter-Josef Meisch + * @author Martin Choraine * @author Farid Azaza */ @RunWith(SpringJUnit4ClassRunner.class) @@ -2825,6 +2826,36 @@ public class ElasticsearchTemplateTests { assertThat(sampleEntities.get(2).getMessage()).isEqualTo(sampleEntity1.getMessage()); } + @Test // DATAES-593 + public void shouldReturnDocumentWithCollapsedField() { + + // given + SampleEntity sampleEntity = SampleEntity.builder().id(randomNumeric(5)).message("message 1").rate(1) + .version(System.currentTimeMillis()).build(); + SampleEntity sampleEntity2 = SampleEntity.builder().id(randomNumeric(5)).message("message 2").rate(2) + .version(System.currentTimeMillis()).build(); + SampleEntity sampleEntit3 = SampleEntity.builder().id(randomNumeric(5)).message("message 1").rate(1) + .version(System.currentTimeMillis()).build(); + + List indexQueries = getIndexQueries(Arrays.asList(sampleEntity, sampleEntity2, sampleEntit3)); + + elasticsearchTemplate.bulkIndex(indexQueries); + elasticsearchTemplate.refresh(SampleEntity.class); + + SearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()) + .withIndices(INDEX_NAME_SAMPLE_ENTITY).withTypes(TYPE_NAME).withCollapse("rate").build(); + + // when + Page page = elasticsearchTemplate.queryForPage(searchQuery, SampleEntity.class); + + // then + assertThat(page).isNotNull(); + assertThat(page.getTotalElements()).isEqualTo(3); + assertThat(page.getContent()).hasSize(2); + assertThat(page.getContent().get(0).getMessage()).isEqualTo("message 1"); + assertThat(page.getContent().get(1).getMessage()).isEqualTo("message 2"); + } + private IndexQuery getIndexQuery(SampleEntity sampleEntity) { return new IndexQueryBuilder().withId(sampleEntity.getId()).withObject(sampleEntity) .withVersion(sampleEntity.getVersion()).build(); diff --git a/src/test/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplateTests.java b/src/test/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplateTests.java index 42c05c8eb..a40d3747b 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplateTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplateTests.java @@ -24,6 +24,8 @@ import lombok.Builder; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.elasticsearch.core.query.NativeSearchQuery; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; @@ -76,6 +78,7 @@ import org.springframework.util.StringUtils; * @author Mark Paluch * @author Peter-Josef Meisch * @author Farid Azaza + * @author Martin Choraine * @currentRead Golden Fool - Robin Hobb */ @RunWith(SpringRunner.class) @@ -666,6 +669,30 @@ public class ReactiveElasticsearchTemplateTests { .verifyComplete(); } + @Test // DATAES-593 + public void shouldReturnDocumentWithCollapsedField() { + + SampleEntity entity1 = randomEntity("test message"); + entity1.setRate(1); + SampleEntity entity2 = randomEntity("test another message"); + entity2.setRate(2); + SampleEntity entity3 = randomEntity("test message again"); + entity3.setRate(1); + index(entity1, entity2, entity3); + + SearchQuery query = new NativeSearchQueryBuilder() + .withIndices(DEFAULT_INDEX) + .withQuery(matchAllQuery()) + .withCollapse("rate") + .withPageable(PageRequest.of(0, 25)) + .build(); + + template.find(query, SampleEntity.class) + .as(StepVerifier::create) + .expectNextCount(2) + .verifyComplete(); + } + @Data @Document(indexName = "marvel", type = "characters") static class Person { @@ -733,6 +760,7 @@ public class ReactiveElasticsearchTemplateTests { @Id private String id; @Field(type = Text, store = true, fielddata = true) private String message; + private int rate; @Version private Long version; @Score private float score; }