Skip to content

Commit

Permalink
function_score: use query and filter together
Browse files Browse the repository at this point in the history
Before, if filter and query was defined for function_score, then the
filter was silently ignored. Now, if both is defined then function score
query wraps this in a filtered_query.

closes #8638
closes #8675
  • Loading branch information
brwe committed Jan 19, 2015
1 parent 54fbac5 commit bf1838d
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 3 deletions.
20 changes: 20 additions & 0 deletions src/main/java/org/elasticsearch/index/query/QueryBuilders.java
Expand Up @@ -504,6 +504,26 @@ public static FunctionScoreQueryBuilder functionScoreQuery(FilterBuilder filterB
return new FunctionScoreQueryBuilder(filterBuilder);
}

/**
* A query that allows to define a custom scoring function.
*
* @param queryBuilder The query to custom score
* @param filterBuilder The filterBuilder to custom score
*/
public static FunctionScoreQueryBuilder functionScoreQuery(QueryBuilder queryBuilder, FilterBuilder filterBuilder) {
return new FunctionScoreQueryBuilder(queryBuilder, filterBuilder);
}

/**
* A query that allows to define a custom scoring function.
*
* @param queryBuilder The query to custom score
* @param filterBuilder The filterBuilder to custom score
*/
public static FunctionScoreQueryBuilder functionScoreQuery(QueryBuilder queryBuilder, FilterBuilder filterBuilder, ScoreFunctionBuilder function) {
return (new FunctionScoreQueryBuilder(queryBuilder, filterBuilder)).add(function);
}

/**
* A more like this query that finds documents that are "like" the provided {@link MoreLikeThisQueryBuilder#likeText(String)}
* which is checked against the fields the query is constructed with.
Expand Down
Expand Up @@ -53,21 +53,50 @@ public class FunctionScoreQueryBuilder extends BaseQueryBuilder implements Boost
private ArrayList<ScoreFunctionBuilder> scoreFunctions = new ArrayList<>();
private Float minScore = null;

/**
* Creates a function_score query that executes on documents that match query a query.
* Query and filter will be wrapped into a filtered_query.
*
* @param queryBuilder the query that defines which documents the function_score query will be executed on.
*/
public FunctionScoreQueryBuilder(QueryBuilder queryBuilder) {
this.queryBuilder = queryBuilder;
this.filterBuilder = null;
}

/**
* Creates a function_score query that executes on documents that match query a query.
* Query and filter will be wrapped into a filtered_query.
*
* @param filterBuilder the filter that defines which documents the function_score query will be executed on.
*/
public FunctionScoreQueryBuilder(FilterBuilder filterBuilder) {
this.filterBuilder = filterBuilder;
this.queryBuilder = null;
}

/**
* Creates a function_score query that executes on documents that match query and filter.
* Query and filter will be wrapped into a filtered_query.
*
* @param queryBuilder a query that will; be wrapped in a filtered query.
* @param filterBuilder the filter for the filtered query.
*/
public FunctionScoreQueryBuilder(QueryBuilder queryBuilder, FilterBuilder filterBuilder) {
this.filterBuilder = filterBuilder;
this.queryBuilder = queryBuilder;
}

public FunctionScoreQueryBuilder() {
this.filterBuilder = null;
this.queryBuilder = null;
}

/**
* Creates a function_score query that will execute the function scoreFunctionBuilder on all documents.
*
* @param scoreFunctionBuilder score function that is executed
*/
public FunctionScoreQueryBuilder(ScoreFunctionBuilder scoreFunctionBuilder) {
if (scoreFunctionBuilder == null) {
throw new ElasticsearchIllegalArgumentException("function_score: function must not be null");
Expand All @@ -78,6 +107,12 @@ public FunctionScoreQueryBuilder(ScoreFunctionBuilder scoreFunctionBuilder) {
this.scoreFunctions.add(scoreFunctionBuilder);
}

/**
* Adds a score function that will will execute the function scoreFunctionBuilder on all documents matching the filter.
*
* @param filter the filter that defines which documents the function_score query will be executed on.
* @param scoreFunctionBuilder score function that is executed
*/
public FunctionScoreQueryBuilder add(FilterBuilder filter, ScoreFunctionBuilder scoreFunctionBuilder) {
if (scoreFunctionBuilder == null) {
throw new ElasticsearchIllegalArgumentException("function_score: function must not be null");
Expand All @@ -87,6 +122,11 @@ public FunctionScoreQueryBuilder add(FilterBuilder filter, ScoreFunctionBuilder
return this;
}

/**
* Adds a score function that will will execute the function scoreFunctionBuilder on all documents.
*
* @param scoreFunctionBuilder score function that is executed
*/
public FunctionScoreQueryBuilder add(ScoreFunctionBuilder scoreFunctionBuilder) {
if (scoreFunctionBuilder == null) {
throw new ElasticsearchIllegalArgumentException("function_score: function must not be null");
Expand All @@ -96,21 +136,35 @@ public FunctionScoreQueryBuilder add(ScoreFunctionBuilder scoreFunctionBuilder)
return this;
}

/**
* Score mode defines how results of individual score functions will be aggregated.
* Can be first, avg, max, sum, min, multiply
*/
public FunctionScoreQueryBuilder scoreMode(String scoreMode) {
this.scoreMode = scoreMode;
return this;
}

/**
* Score mode defines how the combined result of score functions will influence the final score together with the sub query score.
* Can be replace, avg, max, sum, min, multiply
*/
public FunctionScoreQueryBuilder boostMode(String boostMode) {
this.boostMode = boostMode;
return this;
}

/**
* Score mode defines how the combined result of score functions will influence the final score together with the sub query score.
*/
public FunctionScoreQueryBuilder boostMode(CombineFunction combineFunction) {
this.boostMode = combineFunction.getName();
return this;
}

/**
* Tha maximum boost that will be applied by function score.
*/
public FunctionScoreQueryBuilder maxBoost(float maxBoost) {
this.maxBoost = maxBoost;
return this;
Expand All @@ -132,7 +186,8 @@ protected void doXContent(XContentBuilder builder, Params params) throws IOExcep
if (queryBuilder != null) {
builder.field("query");
queryBuilder.toXContent(builder, params);
} else if (filterBuilder != null) {
}
if (filterBuilder != null) {
builder.field("filter");
filterBuilder.toXContent(builder, params);
}
Expand Down
Expand Up @@ -30,6 +30,7 @@
import org.elasticsearch.common.lucene.search.MatchAllDocsFilter;
import org.elasticsearch.common.lucene.search.Queries;
import org.elasticsearch.common.lucene.search.XConstantScoreQuery;
import org.elasticsearch.common.lucene.search.XFilteredQuery;
import org.elasticsearch.common.lucene.search.function.*;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.query.QueryParseContext;
Expand Down Expand Up @@ -80,6 +81,7 @@ public Query parse(QueryParseContext parseContext) throws IOException, QueryPars
XContentParser parser = parseContext.parser();

Query query = null;
Filter filter = null;
float boost = 1.0f;

FiltersFunctionScoreQuery.ScoreMode scoreMode = FiltersFunctionScoreQuery.ScoreMode.Multiply;
Expand All @@ -101,7 +103,7 @@ public Query parse(QueryParseContext parseContext) throws IOException, QueryPars
} else if ("query".equals(currentFieldName)) {
query = parseContext.parseInnerQuery();
} else if ("filter".equals(currentFieldName)) {
query = new XConstantScoreQuery(parseContext.parseInnerFilter());
filter = parseContext.parseInnerFilter();
} else if ("score_mode".equals(currentFieldName) || "scoreMode".equals(currentFieldName)) {
scoreMode = parseScoreMode(parseContext, parser);
} else if ("boost_mode".equals(currentFieldName) || "boostMode".equals(currentFieldName)) {
Expand Down Expand Up @@ -143,8 +145,12 @@ public Query parse(QueryParseContext parseContext) throws IOException, QueryPars
singleFunctionName = currentFieldName;
}
}
if (query == null) {
if (query == null && filter == null) {
query = Queries.newMatchAllQuery();
} else if (query == null && filter != null) {
query = new XConstantScoreQuery(filter);
} else if (query != null && filter != null) {
query = new XFilteredQuery(query, filter);
}
// if all filter elements returned null, just use the query
if (filterFunctions.isEmpty()) {
Expand Down
Expand Up @@ -29,6 +29,7 @@
import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilder;
import org.elasticsearch.index.query.functionscore.weight.WeightBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.test.ElasticsearchIntegrationTest;
import org.junit.Test;
Expand Down Expand Up @@ -513,5 +514,40 @@ protected void assertMinScoreSearchResponses(int numDocs, SearchResponse searchR
pos++;
}
}

@Test
public void testFilterAndQueryGiven() throws IOException, ExecutionException, InterruptedException {
assertAcked(prepareCreate("test").addMapping(
"type",
jsonBuilder().startObject().startObject("type").startObject("properties")
.startObject("filter_field").field("type", "string").endObject()
.startObject("query_field").field("type", "string").endObject()
.startObject("num").field("type", "float").endObject().endObject().endObject().endObject()));
ensureYellow();

List<IndexRequestBuilder> indexRequests = new ArrayList<>();
for (int i = 0; i < 20; i++) {
indexRequests.add(
client().prepareIndex()
.setType("type")
.setId(Integer.toString(i))
.setIndex("test")
.setSource(
jsonBuilder().startObject().field("query_field", Integer.toString(i % 3)).field("filter_field", Integer.toString(i % 2)).field("num", i).endObject()));
}

indexRandom(true, true, indexRequests);

SearchResponse response = client().search(
searchRequest().source(
searchSource().query(
functionScoreQuery(termQuery("query_field", "0"), termFilter("filter_field", "0"), scriptFunction("doc['num'].value")).boostMode("replace")))).get();

assertSearchResponse(response);
assertThat(response.getHits().totalHits(), equalTo(4l));
for (SearchHit hit : response.getHits().getHits()) {
assertThat(Float.parseFloat(hit.getId()), equalTo(hit.getScore()));
}
}
}

0 comments on commit bf1838d

Please sign in to comment.