Skip to content

Commit f057fc2

Browse files
fred84jimczi
authored andcommitted
Rescore collapsed documents (#28521)
This change adds the ability to rescore collapsed documents.
1 parent c26bd60 commit f057fc2

File tree

4 files changed

+78
-31
lines changed

4 files changed

+78
-31
lines changed

rest-api-spec/src/main/resources/rest-api-spec/test/search/110_field_collapsing.yml

-22
Original file line numberDiff line numberDiff line change
@@ -237,28 +237,6 @@ setup:
237237
search_after: [6]
238238
sort: [{ sort: desc }]
239239

240-
---
241-
"field collapsing and rescore":
242-
243-
- skip:
244-
version: " - 5.2.99"
245-
reason: this uses a new API that has been added in 5.3
246-
247-
- do:
248-
catch: /cannot use \`collapse\` in conjunction with \`rescore\`/
249-
search:
250-
index: test
251-
type: test
252-
body:
253-
collapse: { field: numeric_group }
254-
rescore:
255-
window_size: 20
256-
query:
257-
rescore_query:
258-
match_all: {}
259-
query_weight: 1
260-
rescore_query_weight: 2
261-
262240
---
263241
"no hits and inner_hits":
264242

server/src/main/java/org/elasticsearch/search/collapse/CollapseBuilder.java

-3
Original file line numberDiff line numberDiff line change
@@ -225,9 +225,6 @@ public CollapseContext build(SearchContext context) {
225225
if (context.searchAfter() != null) {
226226
throw new SearchContextException(context, "cannot use `collapse` in conjunction with `search_after`");
227227
}
228-
if (context.rescore() != null && context.rescore().isEmpty() == false) {
229-
throw new SearchContextException(context, "cannot use `collapse` in conjunction with `rescore`");
230-
}
231228

232229
MappedFieldType fieldType = context.getQueryShardContext().fieldMapper(field);
233230
if (fieldType == null) {

server/src/main/java/org/elasticsearch/search/query/TopDocsCollectorContext.java

+13-6
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ void postProcess(QuerySearchResult result) {
128128
static class CollapsingTopDocsCollectorContext extends TopDocsCollectorContext {
129129
private final DocValueFormat[] sortFmt;
130130
private final CollapsingTopDocsCollector<?> topDocsCollector;
131+
private final boolean rescore;
131132

132133
/**
133134
* Ctr
@@ -139,13 +140,14 @@ static class CollapsingTopDocsCollectorContext extends TopDocsCollectorContext {
139140
private CollapsingTopDocsCollectorContext(CollapseContext collapseContext,
140141
@Nullable SortAndFormats sortAndFormats,
141142
int numHits,
142-
boolean trackMaxScore) {
143+
boolean trackMaxScore, boolean rescore) {
143144
super(REASON_SEARCH_TOP_HITS, numHits);
144145
assert numHits > 0;
145146
assert collapseContext != null;
146147
Sort sort = sortAndFormats == null ? Sort.RELEVANCE : sortAndFormats.sort;
147148
this.sortFmt = sortAndFormats == null ? new DocValueFormat[] { DocValueFormat.RAW } : sortAndFormats.formats;
148149
this.topDocsCollector = collapseContext.createTopDocs(sort, numHits, trackMaxScore);
150+
this.rescore = rescore;
149151
}
150152

151153
@Override
@@ -158,6 +160,11 @@ Collector create(Collector in) throws IOException {
158160
void postProcess(QuerySearchResult result) throws IOException {
159161
result.topDocs(topDocsCollector.getTopDocs(), sortFmt);
160162
}
163+
164+
@Override
165+
boolean shouldRescore() {
166+
return rescore;
167+
}
161168
}
162169

163170
abstract static class SimpleTopDocsCollectorContext extends TopDocsCollectorContext {
@@ -332,11 +339,6 @@ static TopDocsCollectorContext createTopDocsCollectorContext(SearchContext searc
332339
return new ScrollingTopDocsCollectorContext(reader, query, searchContext.scrollContext(),
333340
searchContext.sort(), numDocs, searchContext.trackScores(), searchContext.numberOfShards(),
334341
searchContext.trackTotalHits(), hasFilterCollector);
335-
} else if (searchContext.collapse() != null) {
336-
boolean trackScores = searchContext.sort() == null ? true : searchContext.trackScores();
337-
int numDocs = Math.min(searchContext.from() + searchContext.size(), totalNumDocs);
338-
return new CollapsingTopDocsCollectorContext(searchContext.collapse(),
339-
searchContext.sort(), numDocs, trackScores);
340342
} else {
341343
int numDocs = Math.min(searchContext.from() + searchContext.size(), totalNumDocs);
342344
final boolean rescore = searchContext.rescore().isEmpty() == false;
@@ -346,6 +348,11 @@ static TopDocsCollectorContext createTopDocsCollectorContext(SearchContext searc
346348
numDocs = Math.max(numDocs, rescoreContext.getWindowSize());
347349
}
348350
}
351+
if (searchContext.collapse() != null) {
352+
boolean trackScores = searchContext.sort() == null ? true : searchContext.trackScores();
353+
return new CollapsingTopDocsCollectorContext(searchContext.collapse(),
354+
searchContext.sort(), numDocs, trackScores, rescore);
355+
}
349356
return new SimpleTopDocsCollectorContext(reader, query, searchContext.sort(), searchContext.searchAfter(), numDocs,
350357
searchContext.trackScores(), searchContext.trackTotalHits(), hasFilterCollector) {
351358
@Override

server/src/test/java/org/elasticsearch/search/functionscore/QueryRescorerIT.java

+65
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,17 @@
3636
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;
3737
import org.elasticsearch.search.SearchHit;
3838
import org.elasticsearch.search.SearchHits;
39+
import org.elasticsearch.search.collapse.CollapseBuilder;
3940
import org.elasticsearch.search.rescore.QueryRescoreMode;
4041
import org.elasticsearch.search.rescore.QueryRescorerBuilder;
4142
import org.elasticsearch.search.sort.SortBuilders;
4243
import org.elasticsearch.test.ESIntegTestCase;
4344

45+
import java.io.IOException;
4446
import java.util.Arrays;
4547
import java.util.Comparator;
48+
import java.util.Map;
49+
import java.util.stream.Collectors;
4650

4751
import static org.elasticsearch.cluster.metadata.IndexMetaData.SETTING_NUMBER_OF_REPLICAS;
4852
import static org.elasticsearch.cluster.metadata.IndexMetaData.SETTING_NUMBER_OF_SHARDS;
@@ -67,6 +71,7 @@
6771
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertThirdHit;
6872
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.hasId;
6973
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.hasScore;
74+
import static org.hamcrest.Matchers.containsInAnyOrder;
7075
import static org.hamcrest.Matchers.containsString;
7176
import static org.hamcrest.Matchers.equalTo;
7277
import static org.hamcrest.Matchers.greaterThan;
@@ -748,4 +753,64 @@ public void testRescorePhaseWithInvalidSort() throws Exception {
748753
assertThat(hit.getScore(), equalTo(101f));
749754
}
750755
}
756+
757+
public void testRescoreAfterCollapse() throws Exception {
758+
assertAcked(prepareCreate("test")
759+
.addMapping(
760+
"type1",
761+
jsonBuilder()
762+
.startObject()
763+
.startObject("properties")
764+
.startObject("group")
765+
.field("type", "keyword")
766+
.endObject()
767+
.endObject()
768+
.endObject())
769+
);
770+
771+
ensureGreen("test");
772+
773+
indexDocument(1, "miss", "a", 1, 10);
774+
indexDocument(2, "name", "a", 2, 20);
775+
indexDocument(3, "name", "b", 2, 30);
776+
// should be highest on rescore, but filtered out during collapse
777+
indexDocument(4, "name", "b", 1, 40);
778+
779+
refresh("test");
780+
781+
SearchResponse searchResponse = client().prepareSearch("test")
782+
.setTypes("type1")
783+
.setQuery(staticScoreQuery("static_score"))
784+
.addRescorer(new QueryRescorerBuilder(staticScoreQuery("static_rescore")))
785+
.setCollapse(new CollapseBuilder("group"))
786+
.get();
787+
788+
assertThat(searchResponse.getHits().totalHits, equalTo(3L));
789+
assertThat(searchResponse.getHits().getHits().length, equalTo(2));
790+
791+
Map<String, Float> collapsedHits = Arrays
792+
.stream(searchResponse.getHits().getHits())
793+
.collect(Collectors.toMap(SearchHit::getId, SearchHit::getScore));
794+
795+
assertThat(collapsedHits.keySet(), containsInAnyOrder("2", "3"));
796+
assertThat(collapsedHits.get("2"), equalTo(22F));
797+
assertThat(collapsedHits.get("3"), equalTo(32F));
798+
}
799+
800+
private QueryBuilder staticScoreQuery(String scoreField) {
801+
return functionScoreQuery(termQuery("name", "name"), ScoreFunctionBuilders.fieldValueFactorFunction(scoreField))
802+
.boostMode(CombineFunction.REPLACE);
803+
}
804+
805+
private void indexDocument(int id, String name, String group, int score, int rescore) throws IOException {
806+
XContentBuilder docBuilder =jsonBuilder()
807+
.startObject()
808+
.field("name", name)
809+
.field("group", group)
810+
.field("static_score", score)
811+
.field("static_rescore", rescore)
812+
.endObject();
813+
814+
client().prepareIndex("test", "type1", Integer.toString(id)).setSource(docBuilder).get();
815+
}
751816
}

0 commit comments

Comments
 (0)