From 5a66569f6efbf93525653f6c79952003fb2a6940 Mon Sep 17 00:00:00 2001 From: "owen.qq" Date: Fri, 4 Mar 2022 20:48:15 +0900 Subject: [PATCH 1/2] Add searchAfter interfaces to NativeSearchQueryBuilder - support searchAfter for nativeSearchQuery --- .../data/elasticsearch/core/SearchHits.java | 1 - .../core/query/NativeSearchQueryBuilder.java | 12 ++++++ .../SearchAfterIntegrationTests.java | 41 ++++++++++++++++++- 3 files changed, 52 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/springframework/data/elasticsearch/core/SearchHits.java b/src/main/java/org/springframework/data/elasticsearch/core/SearchHits.java index 2d62fe295..af01eacca 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/SearchHits.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/SearchHits.java @@ -99,5 +99,4 @@ default boolean hasSuggest() { default Iterator> iterator() { return getSearchHits().iterator(); } - } 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 19e62236f..91e784e13 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 @@ -50,6 +50,7 @@ * @author Peter-Josef Meisch * @author Peer Mueller * @author vdisk + * @author owen.qq */ public class NativeSearchQueryBuilder { @@ -80,6 +81,7 @@ public class NativeSearchQueryBuilder { @Nullable private Duration timeout; private final List rescorerQueries = new ArrayList<>(); @Nullable private SuggestBuilder suggestBuilder; + @Nullable private List searchAfter; public NativeSearchQueryBuilder withQuery(QueryBuilder queryBuilder) { this.queryBuilder = queryBuilder; @@ -342,6 +344,11 @@ public NativeSearchQueryBuilder withSuggestBuilder(SuggestBuilder suggestBuilder return this; } + public NativeSearchQueryBuilder withSearchAfter(List searchAfter) { + this.searchAfter = searchAfter; + return this; + } + public NativeSearchQuery build() { NativeSearchQuery nativeSearchQuery = new NativeSearchQuery( // @@ -431,6 +438,11 @@ public NativeSearchQuery build() { if (suggestBuilder != null) { nativeSearchQuery.setSuggestBuilder(suggestBuilder); } + + if (searchAfter != null) { + nativeSearchQuery.setSearchAfter(searchAfter); + } + return nativeSearchQuery; } } diff --git a/src/test/java/org/springframework/data/elasticsearch/core/paginating/SearchAfterIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/paginating/SearchAfterIntegrationTests.java index 60d1fa63d..07869624f 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/paginating/SearchAfterIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/paginating/SearchAfterIntegrationTests.java @@ -15,13 +15,16 @@ */ package org.springframework.data.elasticsearch.core.paginating; -import static org.assertj.core.api.Assertions.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; import java.util.stream.IntStream; +import org.elasticsearch.search.sort.SortBuilders; +import org.elasticsearch.search.sort.SortOrder; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -33,6 +36,7 @@ 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.query.NativeSearchQueryBuilder; import org.springframework.data.elasticsearch.core.query.Query; import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest; import org.springframework.lang.Nullable; @@ -79,6 +83,41 @@ void shouldReadPagesWithSearchAfter() { assertThat(foundEntities).containsExactlyElementsOf(entities); } + @Test // #2105 + @DisplayName("should read pages with search_after using native search query") + void shouldReadPagesWithSearchAfterUsingNativeSearchQuery() { + + List entities = IntStream.rangeClosed(1, 10).mapToObj(i -> new Entity((long) i, "message " + i)) + .collect(Collectors.toList()); + operations.save(entities); + + NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder(); + + nativeSearchQueryBuilder.withPageable(PageRequest.of(0, 3)); + nativeSearchQueryBuilder.withSorts(SortBuilders.fieldSort("id").order(SortOrder.ASC)); + + List searchAfter = null; + List foundEntities = new ArrayList<>(); + + int loop = 0; + do { + nativeSearchQueryBuilder.withSearchAfter(searchAfter); + SearchHits searchHits = operations.search(nativeSearchQueryBuilder.build(), Entity.class); + + if (searchHits.getSearchHits().size() == 0) { + break; + } + foundEntities.addAll(searchHits.stream().map(searchHit -> searchHit.getContent()).collect(Collectors.toList())); + searchAfter = searchHits.getSearchHit((int) (searchHits.getSearchHits().size() - 1)).getSortValues(); + + if (++loop > 10) { + fail("loop not terminating"); + } + } while (true); + + assertThat(foundEntities).containsExactlyElementsOf(entities); + } + @Document(indexName = "test-search-after") private static class Entity { @Nullable From a7129e91eff9259c28d15738df1fa5c7dfaa386f Mon Sep 17 00:00:00 2001 From: "owen.qq" Date: Sat, 12 Mar 2022 09:09:39 +0900 Subject: [PATCH 2/2] Create unit test for NativeSearchQueryBuilder - add validate conditions in searchAfter() - cause SearchAfterBuilder.setSortValues() assert not allow empty values - delete not appropriate tests for searchAfter in SearchAfterIntegrationTests --- .../data/elasticsearch/core/SearchHits.java | 1 + .../core/query/NativeSearchQueryBuilder.java | 4 ++ .../SearchAfterIntegrationTests.java | 41 +--------------- .../query/NativeSearchQueryBuilderTests.java | 47 +++++++++++++++++++ 4 files changed, 53 insertions(+), 40 deletions(-) create mode 100644 src/test/java/org/springframework/data/elasticsearch/core/query/NativeSearchQueryBuilderTests.java diff --git a/src/main/java/org/springframework/data/elasticsearch/core/SearchHits.java b/src/main/java/org/springframework/data/elasticsearch/core/SearchHits.java index af01eacca..2d62fe295 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/SearchHits.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/SearchHits.java @@ -99,4 +99,5 @@ default boolean hasSuggest() { default Iterator> iterator() { return getSearchHits().iterator(); } + } 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 91e784e13..9dc7a5ddc 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 @@ -345,6 +345,10 @@ public NativeSearchQueryBuilder withSuggestBuilder(SuggestBuilder suggestBuilder } public NativeSearchQueryBuilder withSearchAfter(List searchAfter) { + if (searchAfter != null && searchAfter.isEmpty()) { + return this; + } + this.searchAfter = searchAfter; return this; } diff --git a/src/test/java/org/springframework/data/elasticsearch/core/paginating/SearchAfterIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/paginating/SearchAfterIntegrationTests.java index 07869624f..60d1fa63d 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/paginating/SearchAfterIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/paginating/SearchAfterIntegrationTests.java @@ -15,16 +15,13 @@ */ package org.springframework.data.elasticsearch.core.paginating; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; +import static org.assertj.core.api.Assertions.*; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; import java.util.stream.IntStream; -import org.elasticsearch.search.sort.SortBuilders; -import org.elasticsearch.search.sort.SortOrder; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -36,7 +33,6 @@ 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.query.NativeSearchQueryBuilder; import org.springframework.data.elasticsearch.core.query.Query; import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest; import org.springframework.lang.Nullable; @@ -83,41 +79,6 @@ void shouldReadPagesWithSearchAfter() { assertThat(foundEntities).containsExactlyElementsOf(entities); } - @Test // #2105 - @DisplayName("should read pages with search_after using native search query") - void shouldReadPagesWithSearchAfterUsingNativeSearchQuery() { - - List entities = IntStream.rangeClosed(1, 10).mapToObj(i -> new Entity((long) i, "message " + i)) - .collect(Collectors.toList()); - operations.save(entities); - - NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder(); - - nativeSearchQueryBuilder.withPageable(PageRequest.of(0, 3)); - nativeSearchQueryBuilder.withSorts(SortBuilders.fieldSort("id").order(SortOrder.ASC)); - - List searchAfter = null; - List foundEntities = new ArrayList<>(); - - int loop = 0; - do { - nativeSearchQueryBuilder.withSearchAfter(searchAfter); - SearchHits searchHits = operations.search(nativeSearchQueryBuilder.build(), Entity.class); - - if (searchHits.getSearchHits().size() == 0) { - break; - } - foundEntities.addAll(searchHits.stream().map(searchHit -> searchHit.getContent()).collect(Collectors.toList())); - searchAfter = searchHits.getSearchHit((int) (searchHits.getSearchHits().size() - 1)).getSortValues(); - - if (++loop > 10) { - fail("loop not terminating"); - } - } while (true); - - assertThat(foundEntities).containsExactlyElementsOf(entities); - } - @Document(indexName = "test-search-after") private static class Entity { @Nullable diff --git a/src/test/java/org/springframework/data/elasticsearch/core/query/NativeSearchQueryBuilderTests.java b/src/test/java/org/springframework/data/elasticsearch/core/query/NativeSearchQueryBuilderTests.java new file mode 100644 index 000000000..724f5e7b8 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/core/query/NativeSearchQueryBuilderTests.java @@ -0,0 +1,47 @@ +package org.springframework.data.elasticsearch.core.query; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; + +import org.junit.jupiter.api.Test; + +import com.google.common.collect.Lists; + +/** + * @author owen.qq + */ +public class NativeSearchQueryBuilderTests { + + @Test // #2105 + void shouldContainEffectiveSearchAfterValue() { + Long lastSortValue = 1L; + List searchAfter = Lists.newArrayList(lastSortValue); + + NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder(); + nativeSearchQueryBuilder.withSearchAfter(searchAfter); + NativeSearchQuery nativeSearchQuery = nativeSearchQueryBuilder.build(); + + assertThat(nativeSearchQuery.getSearchAfter()).isNotNull(); + } + + @Test // #2105 + void shouldIgnoreNullableSearchAfterValue() { + List emptySearchValueByFirstSearch = null; + NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder(); + nativeSearchQueryBuilder.withSearchAfter(emptySearchValueByFirstSearch); + NativeSearchQuery nativeSearchQuery = nativeSearchQueryBuilder.build(); + + assertThat(nativeSearchQuery.getSearchAfter()).isNull(); + } + + @Test // #2105 + void shouldIgnoreEmptySearchAfterValue() { + List emptySearchValueByFirstSearch = Lists.newArrayList(); + NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder(); + nativeSearchQueryBuilder.withSearchAfter(emptySearchValueByFirstSearch); + NativeSearchQuery nativeSearchQuery = nativeSearchQueryBuilder.build(); + + assertThat(nativeSearchQuery.getSearchAfter()).isNull(); + } +}