Fix search_after field values (#2679)

Closes #2678

(cherry picked from commit 9adc4d2b368fc600e58ac5d15c02cdc78c0dd743)
This commit is contained in:
Peter-Josef Meisch 2023-08-28 19:51:41 +02:00
parent 063020f8b3
commit 4614c62bb5
No known key found for this signature in database
GPG Key ID: DE108246970C7708
3 changed files with 131 additions and 22 deletions

View File

@ -19,7 +19,6 @@ import static org.springframework.data.elasticsearch.client.elc.TypeUtils.*;
import static org.springframework.util.CollectionUtils.*;
import co.elastic.clients.elasticsearch._types.Conflicts;
import co.elastic.clients.elasticsearch._types.FieldValue;
import co.elastic.clients.elasticsearch._types.InlineScript;
import co.elastic.clients.elasticsearch._types.OpType;
import co.elastic.clients.elasticsearch._types.SortOptions;
@ -63,7 +62,6 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
@ -1221,8 +1219,7 @@ class RequestConverter {
}
if (!isEmpty(query.getSearchAfter())) {
bb.searchAfter(query.getSearchAfter().stream().map(it -> FieldValue.of(it.toString()))
.collect(Collectors.toList()));
bb.searchAfter(query.getSearchAfter().stream().map(TypeUtils::toFieldValue).toList());
}
query.getRescorerQueries().forEach(rescorerQuery -> bb.rescore(getRescore(rescorerQuery)));
@ -1376,8 +1373,7 @@ class RequestConverter {
}
if (!isEmpty(query.getSearchAfter())) {
builder.searchAfter(
query.getSearchAfter().stream().map(it -> FieldValue.of(it.toString())).collect(Collectors.toList()));
builder.searchAfter(query.getSearchAfter().stream().map(TypeUtils::toFieldValue).toList());
}
query.getRescorerQueries().forEach(rescorerQuery -> builder.rescore(getRescore(rescorerQuery)));

View File

@ -18,8 +18,15 @@ package org.springframework.data.elasticsearch.client.elc;
import co.elastic.clients.elasticsearch._types.*;
import co.elastic.clients.elasticsearch._types.mapping.FieldType;
import co.elastic.clients.elasticsearch._types.mapping.TypeMapping;
import co.elastic.clients.elasticsearch.core.search.*;
import co.elastic.clients.elasticsearch.core.search.BoundaryScanner;
import co.elastic.clients.elasticsearch.core.search.HighlighterEncoder;
import co.elastic.clients.elasticsearch.core.search.HighlighterFragmenter;
import co.elastic.clients.elasticsearch.core.search.HighlighterOrder;
import co.elastic.clients.elasticsearch.core.search.HighlighterTagsSchema;
import co.elastic.clients.elasticsearch.core.search.HighlighterType;
import co.elastic.clients.elasticsearch.core.search.ScoreMode;
import co.elastic.clients.elasticsearch.indices.IndexSettings;
import co.elastic.clients.json.JsonData;
import java.io.StringReader;
import java.time.Duration;
@ -31,8 +38,13 @@ import java.util.stream.Collectors;
import org.springframework.data.domain.Sort;
import org.springframework.data.elasticsearch.core.RefreshPolicy;
import org.springframework.data.elasticsearch.core.document.Document;
import org.springframework.data.elasticsearch.core.query.*;
import org.springframework.data.elasticsearch.core.query.GeoDistanceOrder;
import org.springframework.data.elasticsearch.core.query.IndexQuery;
import org.springframework.data.elasticsearch.core.query.IndicesOptions;
import org.springframework.data.elasticsearch.core.query.Order;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.data.elasticsearch.core.query.RescorerQuery;
import org.springframework.data.elasticsearch.core.query.UpdateResponse;
import org.springframework.data.elasticsearch.core.reindex.ReindexRequest;
import org.springframework.lang.Nullable;
@ -156,6 +168,40 @@ final class TypeUtils {
}
}
@Nullable
static FieldValue toFieldValue(@Nullable Object fieldValue) {
if (fieldValue == null) {
return FieldValue.NULL;
}
if (fieldValue instanceof Boolean b) {
return b ? FieldValue.TRUE : FieldValue.FALSE;
}
if (fieldValue instanceof String s) {
return FieldValue.of(s);
}
if (fieldValue instanceof Long l) {
return FieldValue.of(l);
}
if (fieldValue instanceof Integer i) {
return FieldValue.of((long) i);
}
if (fieldValue instanceof Double d) {
return FieldValue.of(d);
}
if (fieldValue instanceof Float f) {
return FieldValue.of((double) f);
}
return FieldValue.of(JsonData.of(fieldValue));
}
@Nullable
static GeoDistanceType geoDistanceType(GeoDistanceOrder.DistanceType distanceType) {

View File

@ -62,7 +62,7 @@ public abstract class SearchAfterIntegrationTests {
@Test
@Order(java.lang.Integer.MAX_VALUE)
void cleanup() {
operations.indexOps(IndexCoordinates.of(indexNameProvider.getPrefix() + "*")).delete();
operations.indexOps(IndexCoordinates.of(indexNameProvider.getPrefix() + '*')).delete();
}
@Test // #1143
@ -85,11 +85,11 @@ public abstract class SearchAfterIntegrationTests {
query.setSearchAfter(searchAfter);
SearchHits<Entity> searchHits = operations.search(query, Entity.class);
if (searchHits.getSearchHits().size() == 0) {
if (searchHits.getSearchHits().isEmpty()) {
break;
}
foundEntities.addAll(searchHits.stream().map(SearchHit::getContent).collect(Collectors.toList()));
searchAfter = searchHits.getSearchHit((int) (searchHits.getSearchHits().size() - 1)).getSortValues();
foundEntities.addAll(searchHits.stream().map(SearchHit::getContent).toList());
searchAfter = searchHits.getSearchHit(searchHits.getSearchHits().size() - 1).getSortValues();
if (++loop > 10) {
fail("loop not terminating");
@ -99,16 +99,69 @@ public abstract class SearchAfterIntegrationTests {
assertThat(foundEntities).containsExactlyElementsOf(entities);
}
@Test // #2678
@DisplayName("should be able to handle different search after type values including null")
void shouldBeAbleToHandleDifferentSearchAfterTypeValuesIncludingNull() {
List<Entity> entities = IntStream.rangeClosed(1, 10)
.mapToObj(i -> {
var message = (i % 2 == 0) ? null : "message " + i;
var value = (i % 3 == 0) ? null : (long) i;
return new Entity((long) i, message, value);
})
.collect(Collectors.toList());
operations.save(entities);
Query query = Query.findAll();
query.setPageable(PageRequest.of(0, 3));
query.addSort(Sort.by(Sort.Direction.ASC, "id"));
query.addSort(Sort.by(Sort.Direction.ASC, "keyword"));
query.addSort(Sort.by(Sort.Direction.ASC, "value"));
List<Object> searchAfter = null;
List<Entity> foundEntities = new ArrayList<>();
int loop = 0;
do {
query.setSearchAfter(searchAfter);
SearchHits<Entity> searchHits = operations.search(query, Entity.class);
if (searchHits.getSearchHits().isEmpty()) {
break;
}
foundEntities.addAll(searchHits.stream().map(SearchHit::getContent).toList());
searchAfter = searchHits.getSearchHit(searchHits.getSearchHits().size() - 1).getSortValues();
if (++loop > 10) {
fail("loop not terminating");
}
} while (true);
assertThat(foundEntities).containsExactlyElementsOf(entities);
}
@SuppressWarnings("unused")
@Document(indexName = "#{@indexNameProvider.indexName()}")
private static class Entity {
@Nullable
@Id private Long id;
@Nullable
@Field(type = FieldType.Text) private String message;
@Field(type = FieldType.Keyword) private String keyword;
public Entity(@Nullable Long id, @Nullable String message) {
@Nullable
@Field(type = FieldType.Long) private Long value;
public Entity() {}
public Entity(@Nullable Long id, @Nullable String keyword) {
this.id = id;
this.message = message;
this.keyword = keyword;
}
public Entity(@Nullable Long id, @Nullable String keyword, @Nullable Long value) {
this.id = id;
this.keyword = keyword;
this.value = value;
}
@Nullable
@ -121,30 +174,44 @@ public abstract class SearchAfterIntegrationTests {
}
@Nullable
public String getMessage() {
return message;
public String getKeyword() {
return keyword;
}
public void setMessage(@Nullable String message) {
this.message = message;
public void setKeyword(@Nullable String keyword) {
this.keyword = keyword;
}
@Nullable
public Long getValue() {
return value;
}
public void setValue(@Nullable Long value) {
this.value = value;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (!(o instanceof Entity entity))
if (o == null || getClass() != o.getClass())
return false;
Entity entity = (Entity) o;
if (!Objects.equals(id, entity.id))
return false;
return Objects.equals(message, entity.message);
if (!Objects.equals(keyword, entity.keyword))
return false;
return Objects.equals(value, entity.value);
}
@Override
public int hashCode() {
int result = id != null ? id.hashCode() : 0;
result = 31 * result + (message != null ? message.hashCode() : 0);
result = 31 * result + (keyword != null ? keyword.hashCode() : 0);
result = 31 * result + (value != null ? value.hashCode() : 0);
return result;
}
}