function_score: use query and filter together
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
This commit is contained in:
parent
75e1500e68
commit
7230e605fe
|
@ -420,6 +420,26 @@ public abstract class QueryBuilders {
|
|||
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.
|
||||
|
|
|
@ -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");
|
||||
|
@ -78,6 +107,12 @@ public class FunctionScoreQueryBuilder extends BaseQueryBuilder implements Boost
|
|||
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");
|
||||
|
@ -87,6 +122,11 @@ public class FunctionScoreQueryBuilder extends BaseQueryBuilder implements Boost
|
|||
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");
|
||||
|
@ -96,21 +136,35 @@ public class FunctionScoreQueryBuilder extends BaseQueryBuilder implements Boost
|
|||
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;
|
||||
|
@ -132,7 +186,8 @@ public class FunctionScoreQueryBuilder extends BaseQueryBuilder implements Boost
|
|||
if (queryBuilder != null) {
|
||||
builder.field("query");
|
||||
queryBuilder.toXContent(builder, params);
|
||||
} else if (filterBuilder != null) {
|
||||
}
|
||||
if (filterBuilder != null) {
|
||||
builder.field("filter");
|
||||
filterBuilder.toXContent(builder, params);
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ import com.google.common.collect.ImmutableMap;
|
|||
import com.google.common.collect.ImmutableMap.Builder;
|
||||
import org.apache.lucene.search.ConstantScoreQuery;
|
||||
import org.apache.lucene.search.Filter;
|
||||
import org.apache.lucene.search.FilteredQuery;
|
||||
import org.apache.lucene.search.Query;
|
||||
import org.elasticsearch.ElasticsearchParseException;
|
||||
import org.elasticsearch.common.ParseField;
|
||||
|
@ -84,6 +85,7 @@ public class FunctionScoreQueryParser implements QueryParser {
|
|||
XContentParser parser = parseContext.parser();
|
||||
|
||||
Query query = null;
|
||||
Filter filter = null;
|
||||
float boost = 1.0f;
|
||||
|
||||
FiltersFunctionScoreQuery.ScoreMode scoreMode = FiltersFunctionScoreQuery.ScoreMode.Multiply;
|
||||
|
@ -105,7 +107,7 @@ public class FunctionScoreQueryParser implements QueryParser {
|
|||
} else if ("query".equals(currentFieldName)) {
|
||||
query = parseContext.parseInnerQuery();
|
||||
} else if ("filter".equals(currentFieldName)) {
|
||||
query = new ConstantScoreQuery(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)) {
|
||||
|
@ -147,8 +149,12 @@ public class FunctionScoreQueryParser implements QueryParser {
|
|||
singleFunctionName = currentFieldName;
|
||||
}
|
||||
}
|
||||
if (query == null) {
|
||||
if (query == null && filter == null) {
|
||||
query = Queries.newMatchAllQuery();
|
||||
} else if (query == null && filter != null) {
|
||||
query = new ConstantScoreQuery(filter);
|
||||
} else if (query != null && filter != null) {
|
||||
query = new FilteredQuery(query, filter);
|
||||
}
|
||||
// if all filter elements returned null, just use the query
|
||||
if (filterFunctions.isEmpty()) {
|
||||
|
|
|
@ -29,6 +29,7 @@ import org.elasticsearch.common.xcontent.XContentBuilder;
|
|||
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;
|
||||
|
@ -513,5 +514,40 @@ public class FunctionScoreTests extends ElasticsearchIntegrationTest {
|
|||
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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue