Rescore collapsed documents (#28521)
This change adds the ability to rescore collapsed documents.
This commit is contained in:
parent
c26bd6046b
commit
f057fc294a
|
@ -237,28 +237,6 @@ setup:
|
||||||
search_after: [6]
|
search_after: [6]
|
||||||
sort: [{ sort: desc }]
|
sort: [{ sort: desc }]
|
||||||
|
|
||||||
---
|
|
||||||
"field collapsing and rescore":
|
|
||||||
|
|
||||||
- skip:
|
|
||||||
version: " - 5.2.99"
|
|
||||||
reason: this uses a new API that has been added in 5.3
|
|
||||||
|
|
||||||
- do:
|
|
||||||
catch: /cannot use \`collapse\` in conjunction with \`rescore\`/
|
|
||||||
search:
|
|
||||||
index: test
|
|
||||||
type: test
|
|
||||||
body:
|
|
||||||
collapse: { field: numeric_group }
|
|
||||||
rescore:
|
|
||||||
window_size: 20
|
|
||||||
query:
|
|
||||||
rescore_query:
|
|
||||||
match_all: {}
|
|
||||||
query_weight: 1
|
|
||||||
rescore_query_weight: 2
|
|
||||||
|
|
||||||
---
|
---
|
||||||
"no hits and inner_hits":
|
"no hits and inner_hits":
|
||||||
|
|
||||||
|
|
|
@ -225,9 +225,6 @@ public class CollapseBuilder implements Writeable, ToXContentObject {
|
||||||
if (context.searchAfter() != null) {
|
if (context.searchAfter() != null) {
|
||||||
throw new SearchContextException(context, "cannot use `collapse` in conjunction with `search_after`");
|
throw new SearchContextException(context, "cannot use `collapse` in conjunction with `search_after`");
|
||||||
}
|
}
|
||||||
if (context.rescore() != null && context.rescore().isEmpty() == false) {
|
|
||||||
throw new SearchContextException(context, "cannot use `collapse` in conjunction with `rescore`");
|
|
||||||
}
|
|
||||||
|
|
||||||
MappedFieldType fieldType = context.getQueryShardContext().fieldMapper(field);
|
MappedFieldType fieldType = context.getQueryShardContext().fieldMapper(field);
|
||||||
if (fieldType == null) {
|
if (fieldType == null) {
|
||||||
|
|
|
@ -128,6 +128,7 @@ abstract class TopDocsCollectorContext extends QueryCollectorContext {
|
||||||
static class CollapsingTopDocsCollectorContext extends TopDocsCollectorContext {
|
static class CollapsingTopDocsCollectorContext extends TopDocsCollectorContext {
|
||||||
private final DocValueFormat[] sortFmt;
|
private final DocValueFormat[] sortFmt;
|
||||||
private final CollapsingTopDocsCollector<?> topDocsCollector;
|
private final CollapsingTopDocsCollector<?> topDocsCollector;
|
||||||
|
private final boolean rescore;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ctr
|
* Ctr
|
||||||
|
@ -139,13 +140,14 @@ abstract class TopDocsCollectorContext extends QueryCollectorContext {
|
||||||
private CollapsingTopDocsCollectorContext(CollapseContext collapseContext,
|
private CollapsingTopDocsCollectorContext(CollapseContext collapseContext,
|
||||||
@Nullable SortAndFormats sortAndFormats,
|
@Nullable SortAndFormats sortAndFormats,
|
||||||
int numHits,
|
int numHits,
|
||||||
boolean trackMaxScore) {
|
boolean trackMaxScore, boolean rescore) {
|
||||||
super(REASON_SEARCH_TOP_HITS, numHits);
|
super(REASON_SEARCH_TOP_HITS, numHits);
|
||||||
assert numHits > 0;
|
assert numHits > 0;
|
||||||
assert collapseContext != null;
|
assert collapseContext != null;
|
||||||
Sort sort = sortAndFormats == null ? Sort.RELEVANCE : sortAndFormats.sort;
|
Sort sort = sortAndFormats == null ? Sort.RELEVANCE : sortAndFormats.sort;
|
||||||
this.sortFmt = sortAndFormats == null ? new DocValueFormat[] { DocValueFormat.RAW } : sortAndFormats.formats;
|
this.sortFmt = sortAndFormats == null ? new DocValueFormat[] { DocValueFormat.RAW } : sortAndFormats.formats;
|
||||||
this.topDocsCollector = collapseContext.createTopDocs(sort, numHits, trackMaxScore);
|
this.topDocsCollector = collapseContext.createTopDocs(sort, numHits, trackMaxScore);
|
||||||
|
this.rescore = rescore;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -158,6 +160,11 @@ abstract class TopDocsCollectorContext extends QueryCollectorContext {
|
||||||
void postProcess(QuerySearchResult result) throws IOException {
|
void postProcess(QuerySearchResult result) throws IOException {
|
||||||
result.topDocs(topDocsCollector.getTopDocs(), sortFmt);
|
result.topDocs(topDocsCollector.getTopDocs(), sortFmt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
boolean shouldRescore() {
|
||||||
|
return rescore;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract static class SimpleTopDocsCollectorContext extends TopDocsCollectorContext {
|
abstract static class SimpleTopDocsCollectorContext extends TopDocsCollectorContext {
|
||||||
|
@ -332,11 +339,6 @@ abstract class TopDocsCollectorContext extends QueryCollectorContext {
|
||||||
return new ScrollingTopDocsCollectorContext(reader, query, searchContext.scrollContext(),
|
return new ScrollingTopDocsCollectorContext(reader, query, searchContext.scrollContext(),
|
||||||
searchContext.sort(), numDocs, searchContext.trackScores(), searchContext.numberOfShards(),
|
searchContext.sort(), numDocs, searchContext.trackScores(), searchContext.numberOfShards(),
|
||||||
searchContext.trackTotalHits(), hasFilterCollector);
|
searchContext.trackTotalHits(), hasFilterCollector);
|
||||||
} else if (searchContext.collapse() != null) {
|
|
||||||
boolean trackScores = searchContext.sort() == null ? true : searchContext.trackScores();
|
|
||||||
int numDocs = Math.min(searchContext.from() + searchContext.size(), totalNumDocs);
|
|
||||||
return new CollapsingTopDocsCollectorContext(searchContext.collapse(),
|
|
||||||
searchContext.sort(), numDocs, trackScores);
|
|
||||||
} else {
|
} else {
|
||||||
int numDocs = Math.min(searchContext.from() + searchContext.size(), totalNumDocs);
|
int numDocs = Math.min(searchContext.from() + searchContext.size(), totalNumDocs);
|
||||||
final boolean rescore = searchContext.rescore().isEmpty() == false;
|
final boolean rescore = searchContext.rescore().isEmpty() == false;
|
||||||
|
@ -346,6 +348,11 @@ abstract class TopDocsCollectorContext extends QueryCollectorContext {
|
||||||
numDocs = Math.max(numDocs, rescoreContext.getWindowSize());
|
numDocs = Math.max(numDocs, rescoreContext.getWindowSize());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (searchContext.collapse() != null) {
|
||||||
|
boolean trackScores = searchContext.sort() == null ? true : searchContext.trackScores();
|
||||||
|
return new CollapsingTopDocsCollectorContext(searchContext.collapse(),
|
||||||
|
searchContext.sort(), numDocs, trackScores, rescore);
|
||||||
|
}
|
||||||
return new SimpleTopDocsCollectorContext(reader, query, searchContext.sort(), searchContext.searchAfter(), numDocs,
|
return new SimpleTopDocsCollectorContext(reader, query, searchContext.sort(), searchContext.searchAfter(), numDocs,
|
||||||
searchContext.trackScores(), searchContext.trackTotalHits(), hasFilterCollector) {
|
searchContext.trackScores(), searchContext.trackTotalHits(), hasFilterCollector) {
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -36,13 +36,17 @@ import org.elasticsearch.index.query.QueryBuilders;
|
||||||
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;
|
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;
|
||||||
import org.elasticsearch.search.SearchHit;
|
import org.elasticsearch.search.SearchHit;
|
||||||
import org.elasticsearch.search.SearchHits;
|
import org.elasticsearch.search.SearchHits;
|
||||||
|
import org.elasticsearch.search.collapse.CollapseBuilder;
|
||||||
import org.elasticsearch.search.rescore.QueryRescoreMode;
|
import org.elasticsearch.search.rescore.QueryRescoreMode;
|
||||||
import org.elasticsearch.search.rescore.QueryRescorerBuilder;
|
import org.elasticsearch.search.rescore.QueryRescorerBuilder;
|
||||||
import org.elasticsearch.search.sort.SortBuilders;
|
import org.elasticsearch.search.sort.SortBuilders;
|
||||||
import org.elasticsearch.test.ESIntegTestCase;
|
import org.elasticsearch.test.ESIntegTestCase;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static org.elasticsearch.cluster.metadata.IndexMetaData.SETTING_NUMBER_OF_REPLICAS;
|
import static org.elasticsearch.cluster.metadata.IndexMetaData.SETTING_NUMBER_OF_REPLICAS;
|
||||||
import static org.elasticsearch.cluster.metadata.IndexMetaData.SETTING_NUMBER_OF_SHARDS;
|
import static org.elasticsearch.cluster.metadata.IndexMetaData.SETTING_NUMBER_OF_SHARDS;
|
||||||
|
@ -67,6 +71,7 @@ import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertSeco
|
||||||
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertThirdHit;
|
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertThirdHit;
|
||||||
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.hasId;
|
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.hasId;
|
||||||
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.hasScore;
|
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.hasScore;
|
||||||
|
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||||
import static org.hamcrest.Matchers.containsString;
|
import static org.hamcrest.Matchers.containsString;
|
||||||
import static org.hamcrest.Matchers.equalTo;
|
import static org.hamcrest.Matchers.equalTo;
|
||||||
import static org.hamcrest.Matchers.greaterThan;
|
import static org.hamcrest.Matchers.greaterThan;
|
||||||
|
@ -748,4 +753,64 @@ public class QueryRescorerIT extends ESIntegTestCase {
|
||||||
assertThat(hit.getScore(), equalTo(101f));
|
assertThat(hit.getScore(), equalTo(101f));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testRescoreAfterCollapse() throws Exception {
|
||||||
|
assertAcked(prepareCreate("test")
|
||||||
|
.addMapping(
|
||||||
|
"type1",
|
||||||
|
jsonBuilder()
|
||||||
|
.startObject()
|
||||||
|
.startObject("properties")
|
||||||
|
.startObject("group")
|
||||||
|
.field("type", "keyword")
|
||||||
|
.endObject()
|
||||||
|
.endObject()
|
||||||
|
.endObject())
|
||||||
|
);
|
||||||
|
|
||||||
|
ensureGreen("test");
|
||||||
|
|
||||||
|
indexDocument(1, "miss", "a", 1, 10);
|
||||||
|
indexDocument(2, "name", "a", 2, 20);
|
||||||
|
indexDocument(3, "name", "b", 2, 30);
|
||||||
|
// should be highest on rescore, but filtered out during collapse
|
||||||
|
indexDocument(4, "name", "b", 1, 40);
|
||||||
|
|
||||||
|
refresh("test");
|
||||||
|
|
||||||
|
SearchResponse searchResponse = client().prepareSearch("test")
|
||||||
|
.setTypes("type1")
|
||||||
|
.setQuery(staticScoreQuery("static_score"))
|
||||||
|
.addRescorer(new QueryRescorerBuilder(staticScoreQuery("static_rescore")))
|
||||||
|
.setCollapse(new CollapseBuilder("group"))
|
||||||
|
.get();
|
||||||
|
|
||||||
|
assertThat(searchResponse.getHits().totalHits, equalTo(3L));
|
||||||
|
assertThat(searchResponse.getHits().getHits().length, equalTo(2));
|
||||||
|
|
||||||
|
Map<String, Float> collapsedHits = Arrays
|
||||||
|
.stream(searchResponse.getHits().getHits())
|
||||||
|
.collect(Collectors.toMap(SearchHit::getId, SearchHit::getScore));
|
||||||
|
|
||||||
|
assertThat(collapsedHits.keySet(), containsInAnyOrder("2", "3"));
|
||||||
|
assertThat(collapsedHits.get("2"), equalTo(22F));
|
||||||
|
assertThat(collapsedHits.get("3"), equalTo(32F));
|
||||||
|
}
|
||||||
|
|
||||||
|
private QueryBuilder staticScoreQuery(String scoreField) {
|
||||||
|
return functionScoreQuery(termQuery("name", "name"), ScoreFunctionBuilders.fieldValueFactorFunction(scoreField))
|
||||||
|
.boostMode(CombineFunction.REPLACE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void indexDocument(int id, String name, String group, int score, int rescore) throws IOException {
|
||||||
|
XContentBuilder docBuilder =jsonBuilder()
|
||||||
|
.startObject()
|
||||||
|
.field("name", name)
|
||||||
|
.field("group", group)
|
||||||
|
.field("static_score", score)
|
||||||
|
.field("static_rescore", rescore)
|
||||||
|
.endObject();
|
||||||
|
|
||||||
|
client().prepareIndex("test", "type1", Integer.toString(id)).setSource(docBuilder).get();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue