diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/client/action/search/SearchRequestBuilder.java b/modules/elasticsearch/src/main/java/org/elasticsearch/client/action/search/SearchRequestBuilder.java index 67c5c69e00e..273395c9163 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/client/action/search/SearchRequestBuilder.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/client/action/search/SearchRequestBuilder.java @@ -229,6 +229,14 @@ public class SearchRequestBuilder extends BaseRequestBuilder0. */ diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/common/lucene/MinimumScoreCollector.java b/modules/elasticsearch/src/main/java/org/elasticsearch/common/lucene/MinimumScoreCollector.java new file mode 100644 index 00000000000..4adea9fa3d5 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/common/lucene/MinimumScoreCollector.java @@ -0,0 +1,66 @@ +/* + * Licensed to Elastic Search and Shay Banon under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. Elastic Search licenses this + * file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.common.lucene; + +import org.apache.lucene.index.IndexReader; +import org.apache.lucene.search.Collector; +import org.apache.lucene.search.ScoreCachingWrappingScorer; +import org.apache.lucene.search.Scorer; + +import java.io.IOException; + +/** + * + */ +public class MinimumScoreCollector extends Collector { + + private final Collector collector; + + private final float minimumScore; + + private Scorer scorer; + + public MinimumScoreCollector(Collector collector, float minimumScore) { + this.collector = collector; + this.minimumScore = minimumScore; + } + + @Override public void setScorer(Scorer scorer) throws IOException { + if (!(scorer instanceof ScoreCachingWrappingScorer)) { + scorer = new ScoreCachingWrappingScorer(scorer); + } + this.scorer = scorer; + collector.setScorer(scorer); + } + + @Override public void collect(int doc) throws IOException { + if (scorer.score() > minimumScore) { + collector.collect(doc); + } + } + + @Override public void setNextReader(IndexReader reader, int docBase) throws IOException { + collector.setNextReader(reader, docBase); + } + + @Override public boolean acceptsDocsOutOfOrder() { + return collector.acceptsDocsOutOfOrder(); + } +} \ No newline at end of file diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/common/lucene/MultiCollector.java b/modules/elasticsearch/src/main/java/org/elasticsearch/common/lucene/MultiCollector.java index aa42a76e4cd..94b403a9ed4 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/common/lucene/MultiCollector.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/common/lucene/MultiCollector.java @@ -41,7 +41,8 @@ public class MultiCollector extends Collector { } @Override public void setScorer(Scorer scorer) throws IOException { - if (collectors.length > 0) { + // always wrap it in a scorer wrapper + if (!(scorer instanceof ScoreCachingWrappingScorer)) { scorer = new ScoreCachingWrappingScorer(scorer); } collector.setScorer(scorer); diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/search/builder/SearchSourceBuilder.java b/modules/elasticsearch/src/main/java/org/elasticsearch/search/builder/SearchSourceBuilder.java index ab3ca67d3a2..42984bb6449 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/search/builder/SearchSourceBuilder.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/search/builder/SearchSourceBuilder.java @@ -88,6 +88,8 @@ public class SearchSourceBuilder implements ToXContent { private boolean trackScores = false; + private Float minScore; + private List fieldNames; private List scriptFields; @@ -176,6 +178,14 @@ public class SearchSourceBuilder implements ToXContent { return this; } + /** + * Sets the minimum score below which docs will be filtered out. + */ + public SearchSourceBuilder minScore(float minScore) { + this.minScore = minScore; + return this; + } + /** * An optional query parser name to use. */ @@ -434,6 +444,10 @@ public class SearchSourceBuilder implements ToXContent { } } + if (minScore != null) { + builder.field("min_score", minScore); + } + if (version != null) { builder.field("version", version); } diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/search/internal/ContextIndexSearcher.java b/modules/elasticsearch/src/main/java/org/elasticsearch/search/internal/ContextIndexSearcher.java index bf06cf65e2e..e4307c78fc3 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/search/internal/ContextIndexSearcher.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/search/internal/ContextIndexSearcher.java @@ -23,6 +23,7 @@ import org.apache.lucene.index.IndexReader; import org.apache.lucene.search.*; import org.elasticsearch.common.collect.Lists; import org.elasticsearch.common.collect.Maps; +import org.elasticsearch.common.lucene.MinimumScoreCollector; import org.elasticsearch.common.lucene.MultiCollector; import org.elasticsearch.common.lucene.search.ExtendedIndexSearcher; import org.elasticsearch.common.lucene.search.FilteredCollector; @@ -147,6 +148,11 @@ public class ContextIndexSearcher extends ExtendedIndexSearcher { collector = new MultiCollector(collector, collectors.toArray(new Collector[collectors.size()])); } } + // apply the minimum score after multi collector so we filter facets as well + if (searchContext.minimumScore() != null) { + collector = new MinimumScoreCollector(collector, searchContext.minimumScore()); + } + // we only compute the doc id set once since within a context, we execute the same query always... if (searchContext.timeout() != null) { searchContext.queryResult().searchTimedOut(false); diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/search/internal/SearchContext.java b/modules/elasticsearch/src/main/java/org/elasticsearch/search/internal/SearchContext.java index dca84711e2c..131b04e6a5d 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/search/internal/SearchContext.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/search/internal/SearchContext.java @@ -115,6 +115,8 @@ public class SearchContext implements Releasable { private Sort sort; + private Float minimumScore; + private boolean trackScores = false; // when sorting, track scores as well... private String queryParserName; @@ -302,6 +304,15 @@ public class SearchContext implements Releasable { return timeout; } + public SearchContext minimumScore(float minimumScore) { + this.minimumScore = minimumScore; + return this; + } + + public Float minimumScore() { + return this.minimumScore; + } + public SearchContext sort(Sort sort) { this.sort = sort; return this; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/search/query/MinScoreParseElement.java b/modules/elasticsearch/src/main/java/org/elasticsearch/search/query/MinScoreParseElement.java new file mode 100644 index 00000000000..0465441d743 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/search/query/MinScoreParseElement.java @@ -0,0 +1,37 @@ +/* + * Licensed to Elastic Search and Shay Banon under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. Elastic Search licenses this + * file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.search.query; + +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.search.SearchParseElement; +import org.elasticsearch.search.internal.SearchContext; + +/** + * @author kimchy (shay.banon) + */ +public class MinScoreParseElement implements SearchParseElement { + + @Override public void parse(XContentParser parser, SearchContext context) throws Exception { + XContentParser.Token token = parser.currentToken(); + if (token.isValue()) { + context.minimumScore(parser.floatValue()); + } + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/search/query/QueryPhase.java b/modules/elasticsearch/src/main/java/org/elasticsearch/search/query/QueryPhase.java index 9cebe206771..f53f13c1910 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/search/query/QueryPhase.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/search/query/QueryPhase.java @@ -67,6 +67,8 @@ public class QueryPhase implements SearchPhase { .put("sort", new SortParseElement()) .put("trackScores", new TrackScoresParseElement()) .put("track_scores", new TrackScoresParseElement()) + .put("min_score", new MinScoreParseElement()) + .put("minScore", new MinScoreParseElement()) .putAll(facetPhase.parseElements()); return parseElements.build(); }