Skip to content

Commit

Permalink
Support multiple rescores
Browse files Browse the repository at this point in the history
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
  • Loading branch information
nik9000 authored and jpountz committed Jan 23, 2014
1 parent 37f80c8 commit 93a8e80
Show file tree
Hide file tree
Showing 17 changed files with 339 additions and 124 deletions.
52 changes: 52 additions & 0 deletions docs/reference/search/request/rescore.asciidoc
Expand Up @@ -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.
Expand Up @@ -126,13 +126,10 @@ protected ExplainResponse shardOperation(ExplainRequest request, int shardId) th
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
Expand Down
Expand Up @@ -794,13 +794,66 @@ public SearchRequestBuilder addSuggestion(SuggestBuilder.SuggestionBuilder<?> su
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;
}

Expand Down Expand Up @@ -980,9 +1033,4 @@ private HighlightBuilder highlightBuilder() {
private SuggestBuilder suggestBuilder() {
return sourceBuilder().suggest();
}

private RescoreBuilder rescoreBuilder() {
return sourceBuilder().rescore();
}

}
Expand Up @@ -399,12 +399,12 @@ public void suggest(SuggestionSearchContext suggest) {
}

@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();
}

Expand Down
Expand Up @@ -48,6 +48,7 @@

import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

Expand Down Expand Up @@ -114,7 +115,8 @@ public static HighlightBuilder highlight() {

private SuggestBuilder suggestBuilder;

private RescoreBuilder rescoreBuilder;
private List<RescoreBuilder> rescoreBuilders;
private Integer defaultRescoreWindowSize;

private ObjectFloatOpenHashMap<String> indexBoost = null;

Expand Down Expand Up @@ -438,6 +440,16 @@ public SearchSourceBuilder aggregations(XContentBuilder facets) {
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.
*/
Expand Down Expand Up @@ -473,11 +485,17 @@ public SuggestBuilder suggest() {
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;
}

/**
Expand Down Expand Up @@ -898,8 +916,36 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws
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) {
Expand Down
5 changes: 3 additions & 2 deletions src/main/java/org/elasticsearch/search/dfs/DfsPhase.java
Expand Up @@ -32,6 +32,7 @@
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;
Expand Down Expand Up @@ -70,8 +71,8 @@ public void execute(SearchContext context) {
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);
Expand Down
Expand Up @@ -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;
Expand All @@ -28,7 +27,6 @@
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;
Expand Down Expand Up @@ -61,14 +59,10 @@ public boolean hitExecutionNeeded(SearchContext context) {
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);
Expand Down
Expand Up @@ -70,6 +70,7 @@
import org.elasticsearch.search.suggest.SuggestionSearchContext;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
Expand Down Expand Up @@ -160,7 +161,7 @@ public class DefaultSearchContext extends SearchContext {

private SuggestionSearchContext suggest;

private RescoreSearchContext rescore;
private List<RescoreSearchContext> rescore;

private SearchLookup searchLookup;

Expand Down Expand Up @@ -342,12 +343,18 @@ public void suggest(SuggestionSearchContext suggest) {
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() {
Expand Down
Expand Up @@ -134,11 +134,11 @@ public static SearchContext current() {
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();

Expand Down
7 changes: 4 additions & 3 deletions src/main/java/org/elasticsearch/search/query/QueryPhase.java
Expand Up @@ -33,6 +33,7 @@
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;
Expand Down Expand Up @@ -115,9 +116,9 @@ public void execute(SearchContext searchContext) throws QueryPhaseExecutionExcep
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);
}
Expand Down

0 comments on commit 93a8e80

Please sign in to comment.