Skip to content

Commit 7d9e6a5

Browse files
committed
Fix search_after field values (#2679)
Closes #2678 (cherry picked from commit 9adc4d2) (cherry picked from commit 4614c62)
1 parent b34ed6f commit 7d9e6a5

File tree

3 files changed

+118
-20
lines changed

3 files changed

+118
-20
lines changed

src/main/java/org/springframework/data/elasticsearch/client/elc/RequestConverter.java

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
import static org.springframework.util.CollectionUtils.isEmpty;
2424

2525
import co.elastic.clients.elasticsearch._types.Conflicts;
26-
import co.elastic.clients.elasticsearch._types.FieldValue;
2726
import co.elastic.clients.elasticsearch._types.InlineScript;
2827
import co.elastic.clients.elasticsearch._types.OpType;
2928
import co.elastic.clients.elasticsearch._types.SortOptions;
@@ -85,7 +84,6 @@
8584
import java.util.Arrays;
8685
import java.util.Collections;
8786
import java.util.HashMap;
88-
import java.util.LinkedHashMap;
8987
import java.util.List;
9088
import java.util.Map;
9189
import java.util.function.Function;
@@ -1121,8 +1119,7 @@ public MsearchRequest searchMsearchRequest(
11211119
}
11221120

11231121
if (!isEmpty(query.getSearchAfter())) {
1124-
bb.searchAfter(query.getSearchAfter().stream().map(it -> FieldValue.of(it.toString()))
1125-
.collect(Collectors.toList()));
1122+
bb.searchAfter(query.getSearchAfter().stream().map(TypeUtils::toFieldValue).toList());
11261123
}
11271124

11281125
query.getRescorerQueries().forEach(rescorerQuery -> bb.rescore(getRescore(rescorerQuery)));
@@ -1261,8 +1258,7 @@ private <T> void prepareSearchRequest(Query query, @Nullable Class<T> clazz, Ind
12611258
}
12621259

12631260
if (!isEmpty(query.getSearchAfter())) {
1264-
builder.searchAfter(
1265-
query.getSearchAfter().stream().map(it -> FieldValue.of(it.toString())).collect(Collectors.toList()));
1261+
builder.searchAfter(query.getSearchAfter().stream().map(TypeUtils::toFieldValue).toList());
12661262
}
12671263

12681264
query.getRescorerQueries().forEach(rescorerQuery -> builder.rescore(getRescore(rescorerQuery)));

src/main/java/org/springframework/data/elasticsearch/client/elc/TypeUtils.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import co.elastic.clients.elasticsearch.core.search.HighlighterTagsSchema;
2525
import co.elastic.clients.elasticsearch.core.search.HighlighterType;
2626
import co.elastic.clients.elasticsearch.core.search.ScoreMode;
27+
import co.elastic.clients.json.JsonData;
2728

2829
import java.time.Duration;
2930

@@ -127,6 +128,40 @@ static String toString(@Nullable FieldValue fieldValue) {
127128
}
128129
}
129130

131+
@Nullable
132+
static FieldValue toFieldValue(@Nullable Object fieldValue) {
133+
134+
if (fieldValue == null) {
135+
return FieldValue.NULL;
136+
}
137+
138+
if (fieldValue instanceof Boolean b) {
139+
return b ? FieldValue.TRUE : FieldValue.FALSE;
140+
}
141+
142+
if (fieldValue instanceof String s) {
143+
return FieldValue.of(s);
144+
}
145+
146+
if (fieldValue instanceof Long l) {
147+
return FieldValue.of(l);
148+
}
149+
150+
if (fieldValue instanceof Integer i) {
151+
return FieldValue.of((long) i);
152+
}
153+
154+
if (fieldValue instanceof Double d) {
155+
return FieldValue.of(d);
156+
}
157+
158+
if (fieldValue instanceof Float f) {
159+
return FieldValue.of((double) f);
160+
}
161+
162+
return FieldValue.of(JsonData.of(fieldValue));
163+
}
164+
130165
@Nullable
131166
static GeoDistanceType geoDistanceType(GeoDistanceOrder.DistanceType distanceType) {
132167

src/test/java/org/springframework/data/elasticsearch/core/paginating/SearchAfterIntegrationTests.java

Lines changed: 81 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ public void before() {
6262
@Test
6363
@Order(java.lang.Integer.MAX_VALUE)
6464
void cleanup() {
65-
operations.indexOps(IndexCoordinates.of(indexNameProvider.getPrefix() + "*")).delete();
65+
operations.indexOps(IndexCoordinates.of(indexNameProvider.getPrefix() + '*')).delete();
6666
}
6767

6868
@Test // #1143
@@ -85,11 +85,11 @@ void shouldReadPagesWithSearchAfter() {
8585
query.setSearchAfter(searchAfter);
8686
SearchHits<Entity> searchHits = operations.search(query, Entity.class);
8787

88-
if (searchHits.getSearchHits().size() == 0) {
88+
if (searchHits.getSearchHits().isEmpty()) {
8989
break;
9090
}
91-
foundEntities.addAll(searchHits.stream().map(SearchHit::getContent).collect(Collectors.toList()));
92-
searchAfter = searchHits.getSearchHit((int) (searchHits.getSearchHits().size() - 1)).getSortValues();
91+
foundEntities.addAll(searchHits.stream().map(SearchHit::getContent).toList());
92+
searchAfter = searchHits.getSearchHit(searchHits.getSearchHits().size() - 1).getSortValues();
9393

9494
if (++loop > 10) {
9595
fail("loop not terminating");
@@ -99,16 +99,69 @@ void shouldReadPagesWithSearchAfter() {
9999
assertThat(foundEntities).containsExactlyElementsOf(entities);
100100
}
101101

102+
@Test // #2678
103+
@DisplayName("should be able to handle different search after type values including null")
104+
void shouldBeAbleToHandleDifferentSearchAfterTypeValuesIncludingNull() {
105+
106+
List<Entity> entities = IntStream.rangeClosed(1, 10)
107+
.mapToObj(i -> {
108+
var message = (i % 2 == 0) ? null : "message " + i;
109+
var value = (i % 3 == 0) ? null : (long) i;
110+
return new Entity((long) i, message, value);
111+
})
112+
.collect(Collectors.toList());
113+
operations.save(entities);
114+
115+
Query query = Query.findAll();
116+
query.setPageable(PageRequest.of(0, 3));
117+
query.addSort(Sort.by(Sort.Direction.ASC, "id"));
118+
query.addSort(Sort.by(Sort.Direction.ASC, "keyword"));
119+
query.addSort(Sort.by(Sort.Direction.ASC, "value"));
120+
121+
List<Object> searchAfter = null;
122+
List<Entity> foundEntities = new ArrayList<>();
123+
124+
int loop = 0;
125+
do {
126+
query.setSearchAfter(searchAfter);
127+
SearchHits<Entity> searchHits = operations.search(query, Entity.class);
128+
129+
if (searchHits.getSearchHits().isEmpty()) {
130+
break;
131+
}
132+
foundEntities.addAll(searchHits.stream().map(SearchHit::getContent).toList());
133+
searchAfter = searchHits.getSearchHit(searchHits.getSearchHits().size() - 1).getSortValues();
134+
135+
if (++loop > 10) {
136+
fail("loop not terminating");
137+
}
138+
} while (true);
139+
140+
assertThat(foundEntities).containsExactlyElementsOf(entities);
141+
}
142+
143+
@SuppressWarnings("unused")
102144
@Document(indexName = "#{@indexNameProvider.indexName()}")
103145
private static class Entity {
104146
@Nullable
105147
@Id private Long id;
106148
@Nullable
107-
@Field(type = FieldType.Text) private String message;
149+
@Field(type = FieldType.Keyword) private String keyword;
150+
151+
@Nullable
152+
@Field(type = FieldType.Long) private Long value;
153+
154+
public Entity() {}
108155

109-
public Entity(@Nullable Long id, @Nullable String message) {
156+
public Entity(@Nullable Long id, @Nullable String keyword) {
110157
this.id = id;
111-
this.message = message;
158+
this.keyword = keyword;
159+
}
160+
161+
public Entity(@Nullable Long id, @Nullable String keyword, @Nullable Long value) {
162+
this.id = id;
163+
this.keyword = keyword;
164+
this.value = value;
112165
}
113166

114167
@Nullable
@@ -121,30 +174,44 @@ public void setId(@Nullable Long id) {
121174
}
122175

123176
@Nullable
124-
public String getMessage() {
125-
return message;
177+
public String getKeyword() {
178+
return keyword;
179+
}
180+
181+
public void setKeyword(@Nullable String keyword) {
182+
this.keyword = keyword;
183+
}
184+
185+
@Nullable
186+
public Long getValue() {
187+
return value;
126188
}
127189

128-
public void setMessage(@Nullable String message) {
129-
this.message = message;
190+
public void setValue(@Nullable Long value) {
191+
this.value = value;
130192
}
131193

132194
@Override
133195
public boolean equals(Object o) {
134196
if (this == o)
135197
return true;
136-
if (!(o instanceof Entity entity))
198+
if (o == null || getClass() != o.getClass())
137199
return false;
138200

201+
Entity entity = (Entity) o;
202+
139203
if (!Objects.equals(id, entity.id))
140204
return false;
141-
return Objects.equals(message, entity.message);
205+
if (!Objects.equals(keyword, entity.keyword))
206+
return false;
207+
return Objects.equals(value, entity.value);
142208
}
143209

144210
@Override
145211
public int hashCode() {
146212
int result = id != null ? id.hashCode() : 0;
147-
result = 31 * result + (message != null ? message.hashCode() : 0);
213+
result = 31 * result + (keyword != null ? keyword.hashCode() : 0);
214+
result = 31 * result + (value != null ? value.hashCode() : 0);
148215
return result;
149216
}
150217
}

0 commit comments

Comments
 (0)