mirror of
https://github.com/honeymoose/OpenSearch.git
synced 2025-02-22 12:56:53 +00:00
Support multiple rescores
Detects if rescores arrive as an array instead of a plain object. If so then parse each element of the array as a separate rescore to be executed one after another. It looks like this: "rescore" : [ { "window_size" : 100, "query" : { "rescore_query" : { "match" : { "field1" : { "query" : "the quick brown", "type" : "phrase", "slop" : 2 } } }, "query_weight" : 0.7, "rescore_query_weight" : 1.2 } }, { "window_size" : 10, "query" : { "score_mode": "multiply", "rescore_query" : { "function_score" : { "script_score": { "script": "log10(doc['numeric'].value + 2)" } } } } } ] Rescores as a single object are still supported. Closes #4748
This commit is contained in:
parent
37f80c8d80
commit
93a8e80aff
@ -79,3 +79,55 @@ for <<query-dsl-function-score-query,`function query`>> rescores.
|
||||
|`max` |Take the max of original score and the rescore query score.
|
||||
|`min` |Take the min of the original score and the rescore query score.
|
||||
|=======================================================================
|
||||
|
||||
==== Multiple Rescores
|
||||
|
||||
It is also possible to execute multiple rescores in sequence:
|
||||
[source,js]
|
||||
--------------------------------------------------
|
||||
curl -s -XPOST 'localhost:9200/_search' -d '{
|
||||
"query" : {
|
||||
"match" : {
|
||||
"field1" : {
|
||||
"operator" : "or",
|
||||
"query" : "the quick brown",
|
||||
"type" : "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"rescore" : [ {
|
||||
"window_size" : 100,
|
||||
"query" : {
|
||||
"rescore_query" : {
|
||||
"match" : {
|
||||
"field1" : {
|
||||
"query" : "the quick brown",
|
||||
"type" : "phrase",
|
||||
"slop" : 2
|
||||
}
|
||||
}
|
||||
},
|
||||
"query_weight" : 0.7,
|
||||
"rescore_query_weight" : 1.2
|
||||
}
|
||||
}, {
|
||||
"window_size" : 10,
|
||||
"query" : {
|
||||
"score_mode": "multiply",
|
||||
"rescore_query" : {
|
||||
"function_score" : {
|
||||
"script_score": {
|
||||
"script": "log10(doc['numeric'].value + 2)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} ]
|
||||
}
|
||||
'
|
||||
--------------------------------------------------
|
||||
|
||||
The first one gets the results of the query then the second one gets the
|
||||
results of the first, etc. The second rescore will "see" the sorting done
|
||||
by the first rescore so it is possible to use a large window on the first
|
||||
rescore to pull documents into a smaller window for the second rescore.
|
||||
|
@ -126,13 +126,10 @@ public class TransportExplainAction extends TransportShardSingleOperationAction<
|
||||
context.parsedQuery(indexService.queryParserService().parseQuery(request.source()));
|
||||
context.preProcess();
|
||||
int topLevelDocId = result.docIdAndVersion().docId + result.docIdAndVersion().context.docBase;
|
||||
Explanation explanation;
|
||||
if (context.rescore() != null) {
|
||||
RescoreSearchContext ctx = context.rescore();
|
||||
Explanation explanation = context.searcher().explain(context.query(), topLevelDocId);
|
||||
for (RescoreSearchContext ctx : context.rescore()) {
|
||||
Rescorer rescorer = ctx.rescorer();
|
||||
explanation = rescorer.explain(topLevelDocId, context, ctx);
|
||||
} else {
|
||||
explanation = context.searcher().explain(context.query(), topLevelDocId);
|
||||
explanation = rescorer.explain(topLevelDocId, context, ctx, explanation);
|
||||
}
|
||||
if (request.fields() != null || (request.fetchSourceContext() != null && request.fetchSourceContext().fetchSource())) {
|
||||
// Advantage is that we're not opening a second searcher to retrieve the _source. Also
|
||||
|
@ -794,13 +794,66 @@ public class SearchRequestBuilder extends ActionRequestBuilder<SearchRequest, Se
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all rescorers on the builder and sets the first one. To use multiple rescore windows use
|
||||
* {@link #addRescorer(org.elasticsearch.search.rescore.RescoreBuilder.Rescorer, int)}.
|
||||
* @param rescorer rescorer configuration
|
||||
* @return this for chaining
|
||||
*/
|
||||
public SearchRequestBuilder setRescorer(RescoreBuilder.Rescorer rescorer) {
|
||||
rescoreBuilder().rescorer(rescorer);
|
||||
sourceBuilder().clearRescorers();
|
||||
return addRescorer(rescorer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all rescorers on the builder and sets the first one. To use multiple rescore windows use
|
||||
* {@link #addRescorer(org.elasticsearch.search.rescore.RescoreBuilder.Rescorer, int)}.
|
||||
* @param rescorer rescorer configuration
|
||||
* @param window rescore window
|
||||
* @return this for chaining
|
||||
*/
|
||||
public SearchRequestBuilder setRescorer(RescoreBuilder.Rescorer rescorer, int window) {
|
||||
sourceBuilder().clearRescorers();
|
||||
return addRescorer(rescorer, window);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new rescorer.
|
||||
* @param rescorer rescorer configuration
|
||||
* @return this for chaining
|
||||
*/
|
||||
public SearchRequestBuilder addRescorer(RescoreBuilder.Rescorer rescorer) {
|
||||
sourceBuilder().addRescorer(new RescoreBuilder().rescorer(rescorer));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new rescorer.
|
||||
* @param rescorer rescorer configuration
|
||||
* @param window rescore window
|
||||
* @return this for chaining
|
||||
*/
|
||||
public SearchRequestBuilder addRescorer(RescoreBuilder.Rescorer rescorer, int window) {
|
||||
sourceBuilder().addRescorer(new RescoreBuilder().rescorer(rescorer).windowSize(window));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all rescorers from the builder.
|
||||
* @return this for chaining
|
||||
*/
|
||||
public SearchRequestBuilder clearRescorers() {
|
||||
sourceBuilder().clearRescorers();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the rescore window for all rescorers that don't specify a window when added.
|
||||
* @param window rescore window
|
||||
* @return this for chaining
|
||||
*/
|
||||
public SearchRequestBuilder setRescoreWindow(int window) {
|
||||
rescoreBuilder().windowSize(window);
|
||||
sourceBuilder().defaultRescoreWindowSize(window);
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -980,9 +1033,4 @@ public class SearchRequestBuilder extends ActionRequestBuilder<SearchRequest, Se
|
||||
private SuggestBuilder suggestBuilder() {
|
||||
return sourceBuilder().suggest();
|
||||
}
|
||||
|
||||
private RescoreBuilder rescoreBuilder() {
|
||||
return sourceBuilder().rescore();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -399,12 +399,12 @@ public class PercolateContext extends SearchContext {
|
||||
}
|
||||
|
||||
@Override
|
||||
public RescoreSearchContext rescore() {
|
||||
public List<RescoreSearchContext> rescore() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rescore(RescoreSearchContext rescore) {
|
||||
public void addRescore(RescoreSearchContext rescore) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
|
@ -48,6 +48,7 @@ import org.elasticsearch.search.suggest.SuggestBuilder;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@ -114,7 +115,8 @@ public class SearchSourceBuilder implements ToXContent {
|
||||
|
||||
private SuggestBuilder suggestBuilder;
|
||||
|
||||
private RescoreBuilder rescoreBuilder;
|
||||
private List<RescoreBuilder> rescoreBuilders;
|
||||
private Integer defaultRescoreWindowSize;
|
||||
|
||||
private ObjectFloatOpenHashMap<String> indexBoost = null;
|
||||
|
||||
@ -438,6 +440,16 @@ public class SearchSourceBuilder implements ToXContent {
|
||||
return aggregations(facets.bytes());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the rescore window size for rescores that don't specify their window.
|
||||
* @param defaultRescoreWindowSize
|
||||
* @return
|
||||
*/
|
||||
public SearchSourceBuilder defaultRescoreWindowSize(int defaultRescoreWindowSize) {
|
||||
this.defaultRescoreWindowSize = defaultRescoreWindowSize;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a raw (xcontent / json) addAggregation.
|
||||
*/
|
||||
@ -473,11 +485,17 @@ public class SearchSourceBuilder implements ToXContent {
|
||||
return suggestBuilder;
|
||||
}
|
||||
|
||||
public RescoreBuilder rescore() {
|
||||
if (rescoreBuilder == null) {
|
||||
rescoreBuilder = new RescoreBuilder();
|
||||
public SearchSourceBuilder addRescorer(RescoreBuilder rescoreBuilder) {
|
||||
if (rescoreBuilders == null) {
|
||||
rescoreBuilders = new ArrayList<RescoreBuilder>();
|
||||
}
|
||||
return rescoreBuilder;
|
||||
rescoreBuilders.add(rescoreBuilder);
|
||||
return this;
|
||||
}
|
||||
|
||||
public SearchSourceBuilder clearRescorers() {
|
||||
rescoreBuilders = null;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -898,8 +916,36 @@ public class SearchSourceBuilder implements ToXContent {
|
||||
suggestBuilder.toXContent(builder, params);
|
||||
}
|
||||
|
||||
if (rescoreBuilder != null) {
|
||||
rescoreBuilder.toXContent(builder, params);
|
||||
if (rescoreBuilders != null) {
|
||||
// Strip empty rescoreBuilders from the request
|
||||
Iterator<RescoreBuilder> itr = rescoreBuilders.iterator();
|
||||
while (itr.hasNext()) {
|
||||
if (itr.next().isEmpty()) {
|
||||
itr.remove();
|
||||
}
|
||||
}
|
||||
|
||||
// Now build the request taking care to skip empty lists and only send the object form
|
||||
// if there is just one builder.
|
||||
if (rescoreBuilders.size() == 1) {
|
||||
builder.startObject("rescore");
|
||||
rescoreBuilders.get(0).toXContent(builder, params);
|
||||
if (rescoreBuilders.get(0).windowSize() == null && defaultRescoreWindowSize != null) {
|
||||
builder.field("window_size", defaultRescoreWindowSize);
|
||||
}
|
||||
builder.endObject();
|
||||
} else if (!rescoreBuilders.isEmpty()) {
|
||||
builder.startArray("rescore");
|
||||
for (RescoreBuilder rescoreBuilder : rescoreBuilders) {
|
||||
builder.startObject();
|
||||
rescoreBuilder.toXContent(builder, params);
|
||||
if (rescoreBuilder.windowSize() == null && defaultRescoreWindowSize != null) {
|
||||
builder.field("window_size", defaultRescoreWindowSize);
|
||||
}
|
||||
builder.endObject();
|
||||
}
|
||||
builder.endArray();
|
||||
}
|
||||
}
|
||||
|
||||
if (stats != null) {
|
||||
|
@ -32,6 +32,7 @@ import org.elasticsearch.common.collect.HppcMaps;
|
||||
import org.elasticsearch.search.SearchParseElement;
|
||||
import org.elasticsearch.search.SearchPhase;
|
||||
import org.elasticsearch.search.internal.SearchContext;
|
||||
import org.elasticsearch.search.rescore.RescoreSearchContext;
|
||||
|
||||
import java.util.AbstractSet;
|
||||
import java.util.Collection;
|
||||
@ -70,8 +71,8 @@ public class DfsPhase implements SearchPhase {
|
||||
termsSet.clear();
|
||||
}
|
||||
context.query().extractTerms(new DelegateSet(termsSet));
|
||||
if (context.rescore() != null) {
|
||||
context.rescore().rescorer().extractTerms(context, context.rescore(), new DelegateSet(termsSet));
|
||||
for (RescoreSearchContext rescoreContext : context.rescore()) {
|
||||
rescoreContext.rescorer().extractTerms(context, rescoreContext, new DelegateSet(termsSet));
|
||||
}
|
||||
|
||||
Term[] terms = termsSet.toArray(Term.class);
|
||||
|
@ -19,7 +19,6 @@
|
||||
package org.elasticsearch.search.fetch.explain;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
|
||||
import org.apache.lucene.search.Explanation;
|
||||
import org.elasticsearch.ElasticsearchException;
|
||||
import org.elasticsearch.search.SearchParseElement;
|
||||
@ -28,7 +27,6 @@ import org.elasticsearch.search.fetch.FetchSubPhase;
|
||||
import org.elasticsearch.search.internal.InternalSearchHit;
|
||||
import org.elasticsearch.search.internal.SearchContext;
|
||||
import org.elasticsearch.search.rescore.RescoreSearchContext;
|
||||
import org.elasticsearch.search.rescore.Rescorer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
@ -61,14 +59,10 @@ public class ExplainFetchSubPhase implements FetchSubPhase {
|
||||
public void hitExecute(SearchContext context, HitContext hitContext) throws ElasticsearchException {
|
||||
try {
|
||||
final int topLevelDocId = hitContext.hit().docId();
|
||||
Explanation explanation;
|
||||
Explanation explanation = context.searcher().explain(context.query(), topLevelDocId);
|
||||
|
||||
if (context.rescore() != null) {
|
||||
RescoreSearchContext ctx = context.rescore();
|
||||
Rescorer rescorer = ctx.rescorer();
|
||||
explanation = rescorer.explain(topLevelDocId, context, ctx);
|
||||
} else {
|
||||
explanation = context.searcher().explain(context.query(), topLevelDocId);
|
||||
for (RescoreSearchContext rescore : context.rescore()) {
|
||||
explanation = rescore.rescorer().explain(topLevelDocId, context, rescore, explanation);
|
||||
}
|
||||
// we use the top level doc id, since we work with the top level searcher
|
||||
hitContext.hit().explanation(explanation);
|
||||
|
@ -70,6 +70,7 @@ import org.elasticsearch.search.scan.ScanContext;
|
||||
import org.elasticsearch.search.suggest.SuggestionSearchContext;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@ -160,7 +161,7 @@ public class DefaultSearchContext extends SearchContext {
|
||||
|
||||
private SuggestionSearchContext suggest;
|
||||
|
||||
private RescoreSearchContext rescore;
|
||||
private List<RescoreSearchContext> rescore;
|
||||
|
||||
private SearchLookup searchLookup;
|
||||
|
||||
@ -342,12 +343,18 @@ public class DefaultSearchContext extends SearchContext {
|
||||
this.suggest = suggest;
|
||||
}
|
||||
|
||||
public RescoreSearchContext rescore() {
|
||||
return this.rescore;
|
||||
public List<RescoreSearchContext> rescore() {
|
||||
if (rescore == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return rescore;
|
||||
}
|
||||
|
||||
public void rescore(RescoreSearchContext rescore) {
|
||||
this.rescore = rescore;
|
||||
public void addRescore(RescoreSearchContext rescore) {
|
||||
if (this.rescore == null) {
|
||||
this.rescore = new ArrayList<RescoreSearchContext>();
|
||||
}
|
||||
this.rescore.add(rescore);
|
||||
}
|
||||
|
||||
public boolean hasFieldDataFields() {
|
||||
|
@ -134,11 +134,11 @@ public abstract class SearchContext implements Releasable {
|
||||
public abstract void suggest(SuggestionSearchContext suggest);
|
||||
|
||||
/**
|
||||
* @return the rescore context or null if rescoring wasn't specified or isn't supported
|
||||
* @return list of all rescore contexts. empty if there aren't any.
|
||||
*/
|
||||
public abstract RescoreSearchContext rescore();
|
||||
public abstract List<RescoreSearchContext> rescore();
|
||||
|
||||
public abstract void rescore(RescoreSearchContext rescore);
|
||||
public abstract void addRescore(RescoreSearchContext rescore);
|
||||
|
||||
public abstract boolean hasFieldDataFields();
|
||||
|
||||
|
@ -33,6 +33,7 @@ import org.elasticsearch.search.facet.FacetPhase;
|
||||
import org.elasticsearch.search.internal.ContextIndexSearcher;
|
||||
import org.elasticsearch.search.internal.SearchContext;
|
||||
import org.elasticsearch.search.rescore.RescorePhase;
|
||||
import org.elasticsearch.search.rescore.RescoreSearchContext;
|
||||
import org.elasticsearch.search.sort.SortParseElement;
|
||||
import org.elasticsearch.search.sort.TrackScoresParseElement;
|
||||
import org.elasticsearch.search.suggest.SuggestPhase;
|
||||
@ -115,9 +116,9 @@ public class QueryPhase implements SearchPhase {
|
||||
topDocs = searchContext.searcher().search(query, null, numDocs, searchContext.sort(),
|
||||
searchContext.trackScores(), searchContext.trackScores());
|
||||
} else {
|
||||
if (searchContext.rescore() != null) {
|
||||
rescore = true;
|
||||
numDocs = Math.max(searchContext.rescore().window(), numDocs);
|
||||
rescore = !searchContext.rescore().isEmpty();
|
||||
for (RescoreSearchContext rescoreContext : searchContext.rescore()) {
|
||||
numDocs = Math.max(rescoreContext.window(), numDocs);
|
||||
}
|
||||
topDocs = searchContext.searcher().search(query, numDocs);
|
||||
}
|
||||
|
@ -108,32 +108,32 @@ public final class QueryRescorer implements Rescorer {
|
||||
@Override
|
||||
public void rescore(TopDocs topDocs, SearchContext context, RescoreSearchContext rescoreContext) throws IOException {
|
||||
assert rescoreContext != null;
|
||||
QueryRescoreContext rescore = ((QueryRescoreContext) rescoreContext);
|
||||
TopDocs queryTopDocs = context.queryResult().topDocs();
|
||||
if (queryTopDocs == null || queryTopDocs.totalHits == 0 || queryTopDocs.scoreDocs.length == 0) {
|
||||
if (topDocs == null || topDocs.totalHits == 0 || topDocs.scoreDocs.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
QueryRescoreContext rescore = (QueryRescoreContext) rescoreContext;
|
||||
ContextIndexSearcher searcher = context.searcher();
|
||||
topDocs = searcher.search(rescore.query(), new TopDocsFilter(queryTopDocs), queryTopDocs.scoreDocs.length);
|
||||
context.queryResult().topDocs(merge(queryTopDocs, topDocs, rescore));
|
||||
TopDocsFilter filter = new TopDocsFilter(topDocs, rescoreContext.window());
|
||||
TopDocs rescored = searcher.search(rescore.query(), filter, rescoreContext.window());
|
||||
context.queryResult().topDocs(merge(topDocs, rescored, rescore));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Explanation explain(int topLevelDocId, SearchContext context, RescoreSearchContext rescoreContext) throws IOException {
|
||||
QueryRescoreContext rescore = ((QueryRescoreContext) context.rescore());
|
||||
public Explanation explain(int topLevelDocId, SearchContext context, RescoreSearchContext rescoreContext,
|
||||
Explanation sourceExplanation) throws IOException {
|
||||
QueryRescoreContext rescore = ((QueryRescoreContext) rescoreContext);
|
||||
ContextIndexSearcher searcher = context.searcher();
|
||||
Explanation primaryExplain = searcher.explain(context.query(), topLevelDocId);
|
||||
if (primaryExplain == null) {
|
||||
if (sourceExplanation == null) {
|
||||
// this should not happen but just in case
|
||||
return new ComplexExplanation(false, 0.0f, "nothing matched");
|
||||
}
|
||||
Explanation rescoreExplain = searcher.explain(rescore.query(), topLevelDocId);
|
||||
float primaryWeight = rescore.queryWeight();
|
||||
ComplexExplanation prim = new ComplexExplanation(primaryExplain.isMatch(),
|
||||
primaryExplain.getValue() * primaryWeight,
|
||||
ComplexExplanation prim = new ComplexExplanation(sourceExplanation.isMatch(),
|
||||
sourceExplanation.getValue() * primaryWeight,
|
||||
"product of:");
|
||||
prim.addDetail(primaryExplain);
|
||||
prim.addDetail(sourceExplanation);
|
||||
prim.addDetail(new Explanation(primaryWeight, "primaryWeight"));
|
||||
if (rescoreExplain != null && rescoreExplain.isMatch()) {
|
||||
float secondaryWeight = rescore.rescoreQueryWeight();
|
||||
@ -341,14 +341,14 @@ public final class QueryRescorer implements Rescorer {
|
||||
|
||||
private final int[] docIds;
|
||||
|
||||
public TopDocsFilter(TopDocs topDocs) {
|
||||
this.docIds = new int[topDocs.scoreDocs.length];
|
||||
public TopDocsFilter(TopDocs topDocs, int max) {
|
||||
ScoreDoc[] scoreDocs = topDocs.scoreDocs;
|
||||
for (int i = 0; i < scoreDocs.length; i++) {
|
||||
max = Math.min(max, scoreDocs.length);
|
||||
this.docIds = new int[max];
|
||||
for (int i = 0; i < max; i++) {
|
||||
docIds[i] = scoreDocs[i].doc;
|
||||
}
|
||||
Arrays.sort(docIds);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -411,7 +411,7 @@ public final class QueryRescorer implements Rescorer {
|
||||
|
||||
@Override
|
||||
public void extractTerms(SearchContext context, RescoreSearchContext rescoreContext, Set<Term> termsSet) {
|
||||
((QueryRescoreContext) context.rescore()).query().extractTerms(termsSet);
|
||||
((QueryRescoreContext) rescoreContext).query().extractTerms(termsSet);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -19,12 +19,12 @@
|
||||
|
||||
package org.elasticsearch.search.rescore;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.elasticsearch.common.xcontent.ToXContent;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.index.query.QueryBuilder;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class RescoreBuilder implements ToXContent {
|
||||
|
||||
private Rescorer rescorer;
|
||||
@ -44,16 +44,20 @@ public class RescoreBuilder implements ToXContent {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Integer windowSize() {
|
||||
return windowSize;
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return rescorer == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
||||
if (rescorer != null) {
|
||||
builder.startObject("rescore");
|
||||
if (windowSize != null) {
|
||||
builder.field("window_size", windowSize);
|
||||
}
|
||||
rescorer.toXContent(builder, params);
|
||||
builder.endObject();
|
||||
if (windowSize != null) {
|
||||
builder.field("window_size", windowSize);
|
||||
}
|
||||
rescorer.toXContent(builder, params);
|
||||
return builder;
|
||||
}
|
||||
|
||||
|
@ -32,6 +32,16 @@ public class RescoreParseElement implements SearchParseElement {
|
||||
|
||||
@Override
|
||||
public void parse(XContentParser parser, SearchContext context) throws Exception {
|
||||
if (parser.currentToken() == XContentParser.Token.START_ARRAY) {
|
||||
while (parser.nextToken() != XContentParser.Token.END_ARRAY) {
|
||||
parseSingleRescoreContext(parser, context);
|
||||
}
|
||||
} else {
|
||||
parseSingleRescoreContext(parser, context);
|
||||
}
|
||||
}
|
||||
|
||||
private void parseSingleRescoreContext(XContentParser parser, SearchContext context) throws Exception {
|
||||
String fieldName = null;
|
||||
RescoreSearchContext rescoreContext = null;
|
||||
Integer windowSize = null;
|
||||
@ -62,7 +72,7 @@ public class RescoreParseElement implements SearchParseElement {
|
||||
if (windowSize != null) {
|
||||
rescoreContext.setWindowSize(windowSize.intValue());
|
||||
}
|
||||
context.rescore(rescoreContext);
|
||||
context.addRescore(rescoreContext);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -19,9 +19,7 @@
|
||||
|
||||
package org.elasticsearch.search.rescore;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import org.elasticsearch.ElasticsearchException;
|
||||
import org.elasticsearch.common.component.AbstractComponent;
|
||||
import org.elasticsearch.common.inject.Inject;
|
||||
@ -30,7 +28,8 @@ import org.elasticsearch.search.SearchParseElement;
|
||||
import org.elasticsearch.search.SearchPhase;
|
||||
import org.elasticsearch.search.internal.SearchContext;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
*/
|
||||
@ -54,13 +53,13 @@ public class RescorePhase extends AbstractComponent implements SearchPhase {
|
||||
|
||||
@Override
|
||||
public void execute(SearchContext context) throws ElasticsearchException {
|
||||
final RescoreSearchContext ctx = context.rescore();
|
||||
final Rescorer rescorer = ctx.rescorer();
|
||||
try {
|
||||
rescorer.rescore(context.queryResult().topDocs(), context, ctx);
|
||||
for (RescoreSearchContext ctx : context.rescore()) {
|
||||
ctx.rescorer().rescore(context.queryResult().topDocs(), context, ctx);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new ElasticsearchException("Rescore Phase Failed", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -54,13 +54,15 @@ public interface Rescorer {
|
||||
/**
|
||||
* Executes an {@link Explanation} phase on the rescorer.
|
||||
*
|
||||
* @param topLevelDocId the global / top-level document ID to explain
|
||||
* @param context the current {@link SearchContext}
|
||||
* @param rescoreContext TODO
|
||||
* @param topLevelDocId the global / top-level document ID to explain
|
||||
* @param context the explanation for the results being fed to this rescorer
|
||||
* @param rescoreContext context for this rescorer
|
||||
* @param sourceExplanation explanation of the source of the documents being fed into this rescore
|
||||
* @return the explain for the given top level document ID.
|
||||
* @throws IOException if an {@link IOException} occurs
|
||||
*/
|
||||
public Explanation explain(int topLevelDocId, SearchContext context, RescoreSearchContext rescoreContext) throws IOException;
|
||||
public Explanation explain(int topLevelDocId, SearchContext context, RescoreSearchContext rescoreContext,
|
||||
Explanation sourceExplanation) throws IOException;
|
||||
|
||||
/**
|
||||
* Parses the {@link RescoreSearchContext} for this impelementation
|
||||
|
@ -205,12 +205,12 @@ class TestSearchContext extends SearchContext {
|
||||
}
|
||||
|
||||
@Override
|
||||
public RescoreSearchContext rescore() {
|
||||
public List<RescoreSearchContext> rescore() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rescore(RescoreSearchContext rescore) {
|
||||
public void addRescore(RescoreSearchContext rescore) {
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -21,8 +21,10 @@ package org.elasticsearch.search.rescore;
|
||||
|
||||
|
||||
|
||||
import org.apache.lucene.search.Explanation;
|
||||
import org.apache.lucene.util.English;
|
||||
import org.elasticsearch.action.index.IndexRequestBuilder;
|
||||
import org.elasticsearch.action.search.SearchRequestBuilder;
|
||||
import org.elasticsearch.action.search.SearchResponse;
|
||||
import org.elasticsearch.action.search.SearchType;
|
||||
import org.elasticsearch.common.lucene.search.function.CombineFunction;
|
||||
@ -203,24 +205,8 @@ public class QueryRescorerTests extends ElasticsearchIntegrationTest {
|
||||
@Test
|
||||
// forces QUERY_THEN_FETCH because of https://github.com/elasticsearch/elasticsearch/issues/4829
|
||||
public void testEquivalence() throws Exception {
|
||||
client().admin()
|
||||
.indices()
|
||||
.prepareCreate("test")
|
||||
.addMapping(
|
||||
"type1",
|
||||
jsonBuilder().startObject().startObject("type1").startObject("properties").startObject("field1")
|
||||
.field("analyzer", "whitespace").field("type", "string").endObject().endObject().endObject().endObject())
|
||||
.setSettings(ImmutableSettings.settingsBuilder().put("index.number_of_shards", between(1, 5)).put("index.number_of_replicas", between(0, 1))).execute().actionGet();
|
||||
ensureGreen();
|
||||
int numDocs = indexRandomNumbers("whitespace", between(1,5));
|
||||
|
||||
int numDocs = atLeast(100);
|
||||
IndexRequestBuilder[] docs = new IndexRequestBuilder[numDocs];
|
||||
for (int i = 0; i < numDocs; i++) {
|
||||
docs[i] = client().prepareIndex("test", "type1", String.valueOf(i)).setSource("field1", English.intToEnglish(i));
|
||||
}
|
||||
|
||||
indexRandom(true, docs);
|
||||
ensureGreen();
|
||||
final int iters = atLeast(50);
|
||||
for (int i = 0; i < iters; i++) {
|
||||
int resultSize = between(5, 30);
|
||||
@ -335,19 +321,19 @@ public class QueryRescorerTests extends ElasticsearchIntegrationTest {
|
||||
|
||||
String[] scoreModes = new String[]{ "max", "min", "avg", "total", "multiply", "" };
|
||||
String[] descriptionModes = new String[]{ "max of:", "min of:", "avg of:", "sum of:", "product of:", "sum of:" };
|
||||
for (int i = 0; i < scoreModes.length; i++) {
|
||||
QueryRescorer rescoreQuery = RescoreBuilder.queryRescorer(QueryBuilders.matchQuery("field1", "the quick brown").boost(4.0f))
|
||||
for (int innerMode = 0; innerMode < scoreModes.length; innerMode++) {
|
||||
QueryRescorer innerRescoreQuery = RescoreBuilder.queryRescorer(QueryBuilders.matchQuery("field1", "the quick brown").boost(4.0f))
|
||||
.setQueryWeight(0.5f).setRescoreQueryWeight(0.4f);
|
||||
|
||||
if (!"".equals(scoreModes[i])) {
|
||||
rescoreQuery.setScoreMode(scoreModes[i]);
|
||||
if (!"".equals(scoreModes[innerMode])) {
|
||||
innerRescoreQuery.setScoreMode(scoreModes[innerMode]);
|
||||
}
|
||||
|
||||
SearchResponse searchResponse = client()
|
||||
.prepareSearch()
|
||||
.setSearchType(SearchType.DFS_QUERY_THEN_FETCH)
|
||||
.setQuery(QueryBuilders.matchQuery("field1", "the quick brown").operator(MatchQueryBuilder.Operator.OR))
|
||||
.setRescorer(rescoreQuery).setRescoreWindow(5).setExplain(true).execute()
|
||||
.setRescorer(innerRescoreQuery).setRescoreWindow(5).setExplain(true).execute()
|
||||
.actionGet();
|
||||
assertHitCount(searchResponse, 3);
|
||||
assertFirstHit(searchResponse, hasId("1"));
|
||||
@ -355,29 +341,41 @@ public class QueryRescorerTests extends ElasticsearchIntegrationTest {
|
||||
assertThirdHit(searchResponse, hasId("3"));
|
||||
|
||||
for (int j = 0; j < 3; j++) {
|
||||
assertThat(searchResponse.getHits().getAt(j).explanation().getDescription(), equalTo(descriptionModes[i]));
|
||||
assertThat(searchResponse.getHits().getAt(j).explanation().getDescription(), equalTo(descriptionModes[innerMode]));
|
||||
}
|
||||
|
||||
for (int outerMode = 0; outerMode < scoreModes.length; outerMode++) {
|
||||
QueryRescorer outerRescoreQuery = RescoreBuilder.queryRescorer(QueryBuilders.matchQuery("field1", "the quick brown")
|
||||
.boost(4.0f)).setQueryWeight(0.5f).setRescoreQueryWeight(0.4f);
|
||||
|
||||
if (!"".equals(scoreModes[outerMode])) {
|
||||
outerRescoreQuery.setScoreMode(scoreModes[outerMode]);
|
||||
}
|
||||
|
||||
searchResponse = client()
|
||||
.prepareSearch()
|
||||
.setSearchType(SearchType.DFS_QUERY_THEN_FETCH)
|
||||
.setQuery(QueryBuilders.matchQuery("field1", "the quick brown").operator(MatchQueryBuilder.Operator.OR))
|
||||
.addRescorer(innerRescoreQuery).setRescoreWindow(5)
|
||||
.addRescorer(outerRescoreQuery).setRescoreWindow(10)
|
||||
.setExplain(true).get();
|
||||
assertHitCount(searchResponse, 3);
|
||||
assertFirstHit(searchResponse, hasId("1"));
|
||||
assertSecondHit(searchResponse, hasId("2"));
|
||||
assertThirdHit(searchResponse, hasId("3"));
|
||||
|
||||
for (int j = 0; j < 3; j++) {
|
||||
Explanation explanation = searchResponse.getHits().getAt(j).explanation();
|
||||
assertThat(explanation.getDescription(), equalTo(descriptionModes[outerMode]));
|
||||
assertThat(explanation.getDetails()[0].getDetails()[0].getDescription(), equalTo(descriptionModes[innerMode]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testScoring() throws Exception {
|
||||
client().admin()
|
||||
.indices()
|
||||
.prepareCreate("test")
|
||||
.addMapping(
|
||||
"type1",
|
||||
jsonBuilder().startObject().startObject("type1").startObject("properties").startObject("field1")
|
||||
.field("index", "not_analyzed").field("type", "string").endObject().endObject().endObject().endObject())
|
||||
.setSettings(ImmutableSettings.settingsBuilder().put("index.number_of_shards", between(1,5)).put("index.number_of_replicas", between(0,1))).get();
|
||||
int numDocs = atLeast(100);
|
||||
IndexRequestBuilder[] docs = new IndexRequestBuilder[numDocs];
|
||||
for (int i = 0; i < numDocs; i++) {
|
||||
docs[i] = client().prepareIndex("test", "type1", String.valueOf(i)).setSource("field1", English.intToEnglish(i));
|
||||
}
|
||||
|
||||
indexRandom(true, docs);
|
||||
ensureGreen();
|
||||
int numDocs = indexRandomNumbers("keyword", between(1,5));
|
||||
|
||||
String[] scoreModes = new String[]{ "max", "min", "avg", "total", "multiply", "" };
|
||||
float primaryWeight = 1.1f;
|
||||
@ -461,4 +459,60 @@ public class QueryRescorerTests extends ElasticsearchIntegrationTest {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultipleRescores() throws Exception {
|
||||
int numDocs = indexRandomNumbers("keyword", 1);
|
||||
QueryRescorer eightIsGreat = RescoreBuilder.queryRescorer(
|
||||
QueryBuilders.functionScoreQuery(QueryBuilders.termQuery("field1", English.intToEnglish(8))).boostMode(CombineFunction.REPLACE)
|
||||
.add(ScoreFunctionBuilders.scriptFunction("1000.0f"))).setScoreMode("total");
|
||||
QueryRescorer sevenIsBetter = RescoreBuilder.queryRescorer(
|
||||
QueryBuilders.functionScoreQuery(QueryBuilders.termQuery("field1", English.intToEnglish(7))).boostMode(CombineFunction.REPLACE)
|
||||
.add(ScoreFunctionBuilders.scriptFunction("10000.0f"))).setScoreMode("total");
|
||||
|
||||
// First set the rescore window large enough that both rescores take effect
|
||||
SearchRequestBuilder request = client().prepareSearch().setRescoreWindow(numDocs);
|
||||
request.addRescorer(eightIsGreat).addRescorer(sevenIsBetter);
|
||||
SearchResponse response = request.get();
|
||||
assertFirstHit(response, hasId("7"));
|
||||
assertSecondHit(response, hasId("8"));
|
||||
|
||||
// Now squash the second rescore window so it never gets to see a seven
|
||||
response = request.setSize(1).clearRescorers().addRescorer(eightIsGreat).addRescorer(sevenIsBetter, 1).get();
|
||||
assertFirstHit(response, hasId("8"));
|
||||
// We have no idea what the second hit will be because we didn't get a chance to look for seven
|
||||
|
||||
// Now use one rescore to drag the number we're looking for into the window of another
|
||||
QueryRescorer ninetyIsGood = RescoreBuilder.queryRescorer(
|
||||
QueryBuilders.functionScoreQuery(QueryBuilders.queryString("*ninety*")).boostMode(CombineFunction.REPLACE)
|
||||
.add(ScoreFunctionBuilders.scriptFunction("1000.0f"))).setScoreMode("total");
|
||||
QueryRescorer oneToo = RescoreBuilder.queryRescorer(
|
||||
QueryBuilders.functionScoreQuery(QueryBuilders.queryString("*one*")).boostMode(CombineFunction.REPLACE)
|
||||
.add(ScoreFunctionBuilders.scriptFunction("1000.0f"))).setScoreMode("total");
|
||||
request.clearRescorers().addRescorer(ninetyIsGood).addRescorer(oneToo, 10);
|
||||
response = request.setSize(2).get();
|
||||
assertFirstHit(response, hasId("91"));
|
||||
assertFirstHit(response, hasScore(2001.0f));
|
||||
assertSecondHit(response, hasScore(1001.0f)); // Not sure which one it is but it is ninety something
|
||||
}
|
||||
|
||||
private int indexRandomNumbers(String analyzer, int shards) throws Exception {
|
||||
client().admin()
|
||||
.indices()
|
||||
.prepareCreate("test")
|
||||
.addMapping(
|
||||
"type1",
|
||||
jsonBuilder().startObject().startObject("type1").startObject("properties").startObject("field1")
|
||||
.field("analyzer", analyzer).field("type", "string").endObject().endObject().endObject().endObject())
|
||||
.setSettings(ImmutableSettings.settingsBuilder().put("index.number_of_shards", shards).put("index.number_of_replicas", between(0,1))).get();
|
||||
int numDocs = atLeast(100);
|
||||
IndexRequestBuilder[] docs = new IndexRequestBuilder[numDocs];
|
||||
for (int i = 0; i < numDocs; i++) {
|
||||
docs[i] = client().prepareIndex("test", "type1", String.valueOf(i)).setSource("field1", English.intToEnglish(i));
|
||||
}
|
||||
|
||||
indexRandom(true, docs);
|
||||
ensureGreen();
|
||||
return numDocs;
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user