Skip to content

Commit 9adc4d2

Browse files
authored
Fix search_after field values (#2679)
Closes #2678
1 parent 922c7dd commit 9adc4d2

File tree

3 files changed

+126
-20
lines changed

3 files changed

+126
-20
lines changed

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

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,15 @@
1818
import static org.springframework.data.elasticsearch.client.elc.TypeUtils.*;
1919
import static org.springframework.util.CollectionUtils.*;
2020

21-
import co.elastic.clients.elasticsearch._types.*;
21+
import co.elastic.clients.elasticsearch._types.Conflicts;
22+
import co.elastic.clients.elasticsearch._types.ExpandWildcard;
23+
import co.elastic.clients.elasticsearch._types.InlineScript;
24+
import co.elastic.clients.elasticsearch._types.NestedSortValue;
25+
import co.elastic.clients.elasticsearch._types.OpType;
26+
import co.elastic.clients.elasticsearch._types.SortOptions;
27+
import co.elastic.clients.elasticsearch._types.SortOrder;
28+
import co.elastic.clients.elasticsearch._types.VersionType;
29+
import co.elastic.clients.elasticsearch._types.WaitForActiveShardOptions;
2230
import co.elastic.clients.elasticsearch._types.mapping.FieldType;
2331
import co.elastic.clients.elasticsearch._types.mapping.RuntimeField;
2432
import co.elastic.clients.elasticsearch._types.mapping.RuntimeFieldType;
@@ -81,7 +89,6 @@
8189
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
8290
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
8391
import org.springframework.data.elasticsearch.core.query.*;
84-
import org.springframework.data.elasticsearch.core.query.IndicesOptions;
8592
import org.springframework.data.elasticsearch.core.reindex.ReindexRequest;
8693
import org.springframework.data.elasticsearch.core.reindex.Remote;
8794
import org.springframework.data.elasticsearch.core.script.Script;
@@ -1226,8 +1233,7 @@ public MsearchRequest searchMsearchRequest(
12261233
}
12271234

12281235
if (!isEmpty(query.getSearchAfter())) {
1229-
bb.searchAfter(query.getSearchAfter().stream().map(it -> FieldValue.of(it.toString()))
1230-
.collect(Collectors.toList()));
1236+
bb.searchAfter(query.getSearchAfter().stream().map(TypeUtils::toFieldValue).toList());
12311237
}
12321238

12331239
query.getRescorerQueries().forEach(rescorerQuery -> bb.rescore(getRescore(rescorerQuery)));
@@ -1391,8 +1397,7 @@ private <T> void prepareSearchRequest(Query query, @Nullable String routing, @Nu
13911397
}
13921398

13931399
if (!isEmpty(query.getSearchAfter())) {
1394-
builder.searchAfter(
1395-
query.getSearchAfter().stream().map(it -> FieldValue.of(it.toString())).collect(Collectors.toList()));
1400+
builder.searchAfter(query.getSearchAfter().stream().map(TypeUtils::toFieldValue).toList());
13961401
}
13971402

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

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

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,40 @@ static Object toObject(@Nullable FieldValue fieldValue) {
170170
}
171171
}
172172

173+
@Nullable
174+
static FieldValue toFieldValue(@Nullable Object fieldValue) {
175+
176+
if (fieldValue == null) {
177+
return FieldValue.NULL;
178+
}
179+
180+
if (fieldValue instanceof Boolean b) {
181+
return b ? FieldValue.TRUE : FieldValue.FALSE;
182+
}
183+
184+
if (fieldValue instanceof String s) {
185+
return FieldValue.of(s);
186+
}
187+
188+
if (fieldValue instanceof Long l) {
189+
return FieldValue.of(l);
190+
}
191+
192+
if (fieldValue instanceof Integer i) {
193+
return FieldValue.of((long) i);
194+
}
195+
196+
if (fieldValue instanceof Double d) {
197+
return FieldValue.of(d);
198+
}
199+
200+
if (fieldValue instanceof Float f) {
201+
return FieldValue.of((double) f);
202+
}
203+
204+
return FieldValue.of(JsonData.of(fieldValue));
205+
}
206+
173207
@Nullable
174208
static GeoDistanceType geoDistanceType(GeoDistanceOrder.DistanceType distanceType) {
175209

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)