diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/search/SearchHit.java b/modules/elasticsearch/src/main/java/org/elasticsearch/search/SearchHit.java index 1987626b93f..0fb4933f76d 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/search/SearchHit.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/search/SearchHit.java @@ -134,6 +134,16 @@ public interface SearchHit extends Streamable, ToXContent, Iterable getHighlightFields(); + /** + * An array of the sort values used. + */ + Object[] sortValues(); + + /** + * An array of the sort values used. + */ + Object[] getSortValues(); + /** * The shard of the search hit. */ diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/search/controller/SearchPhaseController.java b/modules/elasticsearch/src/main/java/org/elasticsearch/search/controller/SearchPhaseController.java index db95db2bba0..18dc5c928e6 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/search/controller/SearchPhaseController.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/search/controller/SearchPhaseController.java @@ -171,6 +171,21 @@ public class SearchPhaseController { } public InternalSearchResponse merge(ShardDoc[] sortedDocs, Map queryResults, Map fetchResults) { + + boolean sorted = false; + int sortScoreIndex = -1; + QuerySearchResult querySearchResult = Iterables.get(queryResults.values(), 0).queryResult(); + + if (querySearchResult.topDocs() instanceof TopFieldDocs) { + sorted = true; + TopFieldDocs fieldDocs = (TopFieldDocs) querySearchResult.queryResult().topDocs(); + for (int i = 0; i < fieldDocs.fields.length; i++) { + if (fieldDocs.fields[i].getType() == SortField.SCORE) { + sortScoreIndex = i; + } + } + } + // merge facets InternalFacets facets = null; if (!queryResults.isEmpty()) { @@ -223,6 +238,15 @@ public class SearchPhaseController { InternalSearchHit searchHit = fetchResult.hits().internalHits()[index]; searchHit.score(shardDoc.score()); searchHit.shard(fetchResult.shardTarget()); + + if (sorted) { + FieldDoc fieldDoc = (FieldDoc) shardDoc; + searchHit.sortValues(fieldDoc.fields); + if (sortScoreIndex != -1) { + searchHit.score(((Number) fieldDoc.fields[sortScoreIndex]).floatValue()); + } + } + hits.add(searchHit); } } diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/search/internal/InternalSearchHit.java b/modules/elasticsearch/src/main/java/org/elasticsearch/search/internal/InternalSearchHit.java index 2fa6b506c93..d3837b4e0d2 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/search/internal/InternalSearchHit.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/search/internal/InternalSearchHit.java @@ -49,6 +49,8 @@ import static org.elasticsearch.search.internal.InternalSearchHitField.*; */ public class InternalSearchHit implements SearchHit { + private static final Object[] EMPTY_SORT_VALUES = new Object[0]; + private transient int docId; private float score = Float.NEGATIVE_INFINITY; @@ -63,6 +65,8 @@ public class InternalSearchHit implements SearchHit { private Map highlightFields = ImmutableMap.of(); + private Object[] sortValues = EMPTY_SORT_VALUES; + private Explanation explanation; @Nullable private SearchShardTarget shard; @@ -195,6 +199,18 @@ public class InternalSearchHit implements SearchHit { this.highlightFields = highlightFields; } + public void sortValues(Object[] sortValues) { + this.sortValues = sortValues; + } + + @Override public Object[] sortValues() { + return sortValues; + } + + @Override public Object[] getSortValues() { + return sortValues(); + } + @Override public Explanation explanation() { return explanation; } @@ -274,6 +290,13 @@ public class InternalSearchHit implements SearchHit { } builder.endObject(); } + if (sortValues != null && sortValues.length > 0) { + builder.startArray("sort_values"); + for (Object sortValue : sortValues) { + builder.value(sortValue); + } + builder.endArray(); + } if (explanation() != null) { builder.field("_explanation"); buildExplanation(builder, explanation()); @@ -391,6 +414,31 @@ public class InternalSearchHit implements SearchHit { highlightFields = builder.build(); } + size = in.readVInt(); + if (size > 0) { + sortValues = new Object[size]; + for (int i = 0; i < sortValues.length; i++) { + byte type = in.readByte(); + if (type == 0) { + sortValues[i] = null; + } else if (type == 1) { + sortValues[i] = in.readUTF(); + } else if (type == 2) { + sortValues[i] = in.readInt(); + } else if (type == 3) { + sortValues[i] = in.readLong(); + } else if (type == 4) { + sortValues[i] = in.readFloat(); + } else if (type == 5) { + sortValues[i] = in.readDouble(); + } else if (type == 6) { + sortValues[i] = in.readByte(); + } else { + throw new IOException("Can't match type [" + type + "]"); + } + } + } + if (shardLookupMap != null) { int lookupId = in.readVInt(); if (lookupId > 0) { @@ -439,6 +487,41 @@ public class InternalSearchHit implements SearchHit { highlightField.writeTo(out); } } + + if (sortValues.length == 0) { + out.writeVInt(0); + } else { + out.writeVInt(sortValues.length); + for (Object sortValue : sortValues) { + if (sortValue == null) { + out.writeByte((byte) 0); + } else { + Class type = sortValue.getClass(); + if (type == String.class) { + out.writeByte((byte) 1); + out.writeUTF((String) sortValue); + } else if (type == Integer.class) { + out.writeByte((byte) 2); + out.writeInt((Integer) sortValue); + } else if (type == Long.class) { + out.writeByte((byte) 3); + out.writeLong((Long) sortValue); + } else if (type == Float.class) { + out.writeByte((byte) 4); + out.writeFloat((Float) sortValue); + } else if (type == Double.class) { + out.writeByte((byte) 5); + out.writeDouble((Double) sortValue); + } else if (type == Byte.class) { + out.writeByte((byte) 6); + out.writeByte((Byte) sortValue); + } else { + throw new IOException("Can't handle sort field value of type [" + type + "]"); + } + } + } + } + if (shardLookupMap == null) { if (shard == null) { out.writeBoolean(false); diff --git a/modules/test/integration/src/test/java/org/elasticsearch/test/integration/search/sort/SimpleSortTests.java b/modules/test/integration/src/test/java/org/elasticsearch/test/integration/search/sort/SimpleSortTests.java index e59f9326861..0c1b1ff924d 100644 --- a/modules/test/integration/src/test/java/org/elasticsearch/test/integration/search/sort/SimpleSortTests.java +++ b/modules/test/integration/src/test/java/org/elasticsearch/test/integration/search/sort/SimpleSortTests.java @@ -88,7 +88,9 @@ public class SimpleSortTests extends AbstractNodesTests { assertThat(searchResponse.hits().getTotalHits(), equalTo(2l)); assertThat((String) searchResponse.hits().getAt(0).field("id").value(), equalTo("1")); + assertThat(searchResponse.hits().getAt(0).sortValues()[0].toString(), equalTo("aaa")); assertThat((String) searchResponse.hits().getAt(1).field("id").value(), equalTo("2")); + assertThat(searchResponse.hits().getAt(1).sortValues()[0].toString(), equalTo("bbb")); searchResponse = client.prepareSearch() .setQuery(matchAllQuery()) @@ -128,7 +130,9 @@ public class SimpleSortTests extends AbstractNodesTests { assertThat(searchResponse.hits().getTotalHits(), equalTo(2l)); assertThat((String) searchResponse.hits().getAt(0).field("id").value(), equalTo("1")); + assertThat(((Number) searchResponse.hits().getAt(0).sortValues()[0]).longValue(), equalTo(100l)); assertThat((String) searchResponse.hits().getAt(1).field("id").value(), equalTo("2")); + assertThat(((Number) searchResponse.hits().getAt(1).sortValues()[0]).longValue(), equalTo(200l)); searchResponse = client.prepareSearch() .setQuery(matchAllQuery()) @@ -148,7 +152,9 @@ public class SimpleSortTests extends AbstractNodesTests { assertThat(searchResponse.hits().getTotalHits(), equalTo(2l)); assertThat((String) searchResponse.hits().getAt(0).field("id").value(), equalTo("2")); + assertThat(((Number) searchResponse.hits().getAt(0).sortValues()[0]).longValue(), equalTo(200l)); assertThat((String) searchResponse.hits().getAt(1).field("id").value(), equalTo("1")); + assertThat(((Number) searchResponse.hits().getAt(1).sortValues()[0]).longValue(), equalTo(100l)); searchResponse = client.prepareSearch() .setQuery(matchAllQuery())