DATAES-714 - Sort results should be returned in the SearchHits.

Original PR: #361
This commit is contained in:
Peter-Josef Meisch 2019-12-22 14:25:43 +01:00 committed by GitHub
parent e55bae725e
commit d19e699b32
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 116 additions and 10 deletions

View File

@ -15,6 +15,11 @@
*/
package org.springframework.data.elasticsearch.core;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.springframework.lang.Nullable;
/**
@ -28,11 +33,13 @@ public class SearchHit<T> {
private final String id;
private final float score;
private final List<Object> sortValues;
private final T content;
public SearchHit(@Nullable String id, float score, T content) {
public SearchHit(@Nullable String id, float score, Object[] sortValues, T content) {
this.id = id;
this.score = score;
this.sortValues = (sortValues != null) ? Arrays.asList(sortValues) : new ArrayList<>();
this.content = content;
}
@ -55,12 +62,16 @@ public class SearchHit<T> {
return content;
}
/**
* @return the sort values if the query had a sort criterion.
*/
public List<Object> getSortValues() {
return Collections.unmodifiableList(sortValues);
}
@Override
public String toString() {
return "SearchHit{" +
"id='" + id + '\'' +
", score=" + score +
", content=" + content +
'}';
return "SearchHit{" + "id='" + id + '\'' + ", score=" + score + ", sortValues=" + sortValues + ", content="
+ content + '}';
}
}

View File

@ -601,9 +601,10 @@ public class MappingElasticsearchConverter
public <T> SearchHit<T> read(Class<T> type, SearchDocument searchDocument) {
String id = searchDocument.hasId() ? searchDocument.getId() : null;
float score = searchDocument.getScore();
Object[] sortValues = searchDocument.getSortValues();
T content = mapDocument(searchDocument, type);
return new SearchHit<T>(id, score, content);
return new SearchHit<T>(id, score, sortValues, content);
}
@Override

View File

@ -142,7 +142,7 @@ public class DocumentAdapters {
BytesReference sourceRef = source.getSourceRef();
if (sourceRef == null || sourceRef.length() == 0) {
return new SearchDocumentAdapter(source.getScore(), source.getFields(),
return new SearchDocumentAdapter(source.getScore(), source.getSortValues(), source.getFields(),
fromDocumentFields(source, source.getId(), source.getVersion()));
}
@ -153,7 +153,7 @@ public class DocumentAdapters {
document.setVersion(source.getVersion());
}
return new SearchDocumentAdapter(source.getScore(), source.getFields(), document);
return new SearchDocumentAdapter(source.getScore(), source.getSortValues(), source.getFields(), document);
}
/**
@ -438,11 +438,13 @@ public class DocumentAdapters {
static class SearchDocumentAdapter implements SearchDocument {
private final float score;
private final Object[] sortValues;
private final Map<String, List<Object>> fields = new HashMap<>();
private final Document delegate;
SearchDocumentAdapter(float score, Map<String, DocumentField> fields, Document delegate) {
SearchDocumentAdapter(float score, Object[] sortValues, Map<String, DocumentField> fields, Document delegate) {
this.score = score;
this.sortValues = sortValues;
this.delegate = delegate;
fields.forEach((name, documentField) -> this.fields.put(name, documentField.getValues()));
}
@ -476,6 +478,15 @@ public class DocumentAdapters {
return fields;
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.core.document.SearchDocument#getSortValues()
*/
@Override
public Object[] getSortValues() {
return sortValues;
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.core.document.Document#hasId()

View File

@ -18,6 +18,8 @@ package org.springframework.data.elasticsearch.core.document;
import java.util.List;
import java.util.Map;
import org.springframework.lang.Nullable;
/**
* Extension to {@link Document} exposing a search response related data.
*
@ -45,6 +47,7 @@ public interface SearchDocument extends Document {
*
* @param name the field name
*/
@Nullable
default <V> V getFieldValue(final String name) {
List<Object> values = getFields().get(name);
if (values == null || values.isEmpty()) {
@ -52,4 +55,12 @@ public interface SearchDocument extends Document {
}
return (V) values.get(0);
}
/**
* @return the sort values for the search hit
*/
@Nullable
default Object[] getSortValues() {
return null;
}
}

View File

@ -128,6 +128,9 @@ public abstract class ElasticsearchTemplateTests {
indexOperations.createIndex(SampleEntityUUIDKeyed.class);
indexOperations.putMapping(SampleEntityUUIDKeyed.class);
indexOperations.createIndex(SearchHitsEntity.class);
indexOperations.putMapping(SearchHitsEntity.class);
}
@AfterEach
@ -146,6 +149,7 @@ public abstract class ElasticsearchTemplateTests {
indexOperations.deleteIndex(INDEX_1_NAME);
indexOperations.deleteIndex(INDEX_2_NAME);
indexOperations.deleteIndex(INDEX_3_NAME);
indexOperations.deleteIndex(SearchHitsEntity.class);
}
@Test // DATAES-106
@ -2856,6 +2860,41 @@ public abstract class ElasticsearchTemplateTests {
assertThat(map).containsKey("index.max_result_window");
}
@Test // DATAES-714
void shouldReturnSortFieldsInSearchHits() {
IndexCoordinates index = IndexCoordinates.of("test-index-searchhits-entity-template");
SearchHitsEntity entity = SearchHitsEntity.builder().id("1").number(1000L).keyword("thousands").build();
IndexQuery indexQuery = new IndexQueryBuilder().withId(entity.getId()).withObject(entity).build();
operations.index(indexQuery, index);
indexOperations.refresh(index);
NativeSearchQuery query = new NativeSearchQueryBuilder() //
.withQuery(matchAllQuery()) //
.withSort(new FieldSortBuilder("keyword").order(SortOrder.ASC))
.withSort(new FieldSortBuilder("number").order(SortOrder.DESC)).build();
SearchHits<SearchHitsEntity> searchHits = operations.search(query, SearchHitsEntity.class, index);
assertThat(searchHits).isNotNull();
assertThat(searchHits.getSearchHits()).hasSize(1);
SearchHit<SearchHitsEntity> searchHit = searchHits.getSearchHit(0);
List<Object> sortValues = searchHit.getSortValues();
assertThat(sortValues).hasSize(2);
assertThat(sortValues.get(0)).isInstanceOf(String.class).isEqualTo("thousands");
// transport client returns Long, rest client Integer
java.lang.Object o = sortValues.get(1);
if (o instanceof Integer) {
Integer i = (Integer) o;
assertThat(o).isInstanceOf(Integer.class).isEqualTo(1000);
} else if (o instanceof Long) {
Long l = (Long) o;
assertThat(o).isInstanceOf(Long.class).isEqualTo(1000L);
} else {
fail("unexpected object type " + o);
}
}
protected RequestFactory getRequestFactory() {
return ((AbstractElasticsearchTemplate) operations).getRequestFactory();
}
@ -3007,4 +3046,14 @@ public abstract class ElasticsearchTemplateTests {
}
}
@Data
@AllArgsConstructor
@Builder
@Document(indexName = "test-index-searchhits-entity-template")
static class SearchHitsEntity {
@Id private String id;
@Field(type = FieldType.Long) Long number;
@Field(type = FieldType.Keyword) String keyword;
}
}

View File

@ -40,6 +40,8 @@ import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.elasticsearch.ElasticsearchStatusException;
import org.elasticsearch.search.sort.FieldSortBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@ -693,6 +695,27 @@ public class ReactiveElasticsearchTemplateTests {
.verifyComplete();
}
@Test
void shouldReturnSortFields() {
SampleEntity entity = randomEntity("test message");
entity.rate = 42;
index(entity);
NativeSearchQuery query = new NativeSearchQueryBuilder() //
.withQuery(matchAllQuery()) //
.withSort(new FieldSortBuilder("rate").order(SortOrder.DESC)) //
.build();
template.search(query, SampleEntity.class) //
.as(StepVerifier::create) //
.consumeNextWith(it -> {
List<Object> sortValues = it.getSortValues();
assertThat(sortValues).hasSize(1);
assertThat(sortValues.get(0)).isEqualTo(42);
}) //
.verifyComplete();
}
@Data
@Document(indexName = "marvel", type = "characters")
static class Person {