1 package org.apache.lucene.search;
4 * Licensed to the Apache Software Foundation (ASF) under one or more
5 * contributor license agreements. See the NOTICE file distributed with
6 * this work for additional information regarding copyright ownership.
7 * The ASF licenses this file to You under the Apache License, Version 2.0
8 * (the "License"); you may not use this file except in compliance with
9 * the License. You may obtain a copy of the License at
11 * http://www.apache.org/licenses/LICENSE-2.0
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
20 import org.apache.lucene.index.IndexReader;
21 import org.apache.lucene.index.Term;
22 import org.apache.lucene.util.ToStringUtils;
24 import java.io.IOException;
28 * A query that wraps another query or a filter and simply returns a constant score equal to the
29 * query boost for every document that matches the filter or query.
30 * For queries it therefore simply strips of all scores and returns a constant one.
32 * <p><b>NOTE</b>: if the wrapped filter is an instance of
33 * {@link CachingWrapperFilter}, you'll likely want to
34 * enforce deletions in the filter (using either {@link
35 * CachingWrapperFilter.DeletesMode#RECACHE} or {@link
36 * CachingWrapperFilter.DeletesMode#DYNAMIC}).
38 public class ConstantScoreQuery extends Query {
39 protected final Filter filter;
40 protected final Query query;
42 /** Strips off scores from the passed in Query. The hits will get a constant score
43 * dependent on the boost factor of this query. */
44 public ConstantScoreQuery(Query query) {
46 throw new NullPointerException("Query may not be null");
51 /** Wraps a Filter as a Query. The hits will get a constant score
52 * dependent on the boost factor of this query.
53 * If you simply want to strip off scores from a Query, no longer use
54 * {@code new ConstantScoreQuery(new QueryWrapperFilter(query))}, instead
55 * use {@link #ConstantScoreQuery(Query)}!
57 public ConstantScoreQuery(Filter filter) {
59 throw new NullPointerException("Filter may not be null");
64 /** Returns the encapsulated filter, returns {@code null} if a query is wrapped. */
65 public Filter getFilter() {
69 /** Returns the encapsulated query, returns {@code null} if a filter is wrapped. */
70 public Query getQuery() {
75 public Query rewrite(IndexReader reader) throws IOException {
77 Query rewritten = query.rewrite(reader);
78 if (rewritten != query) {
79 rewritten = new ConstantScoreQuery(rewritten);
80 rewritten.setBoost(this.getBoost());
88 public void extractTerms(Set<Term> terms) {
89 // TODO: OK to not add any terms when wrapped a filter
90 // and used with MultiSearcher, but may not be OK for
92 // If a query was wrapped, we delegate to query.
94 query.extractTerms(terms);
97 protected class ConstantWeight extends Weight {
98 private final Weight innerWeight;
99 private final Similarity similarity;
100 private float queryNorm;
101 private float queryWeight;
103 public ConstantWeight(Searcher searcher) throws IOException {
104 this.similarity = getSimilarity(searcher);
105 this.innerWeight = (query == null) ? null : query.createWeight(searcher);
109 public Query getQuery() {
110 return ConstantScoreQuery.this;
114 public float getValue() {
119 public float sumOfSquaredWeights() throws IOException {
120 // we calculate sumOfSquaredWeights of the inner weight, but ignore it (just to initialize everything)
121 if (innerWeight != null) innerWeight.sumOfSquaredWeights();
122 queryWeight = getBoost();
123 return queryWeight * queryWeight;
127 public void normalize(float norm) {
128 this.queryNorm = norm;
129 queryWeight *= this.queryNorm;
130 // we normalize the inner weight, but ignore it (just to initialize everything)
131 if (innerWeight != null) innerWeight.normalize(norm);
135 public Scorer scorer(IndexReader reader, boolean scoreDocsInOrder, boolean topScorer) throws IOException {
136 final DocIdSetIterator disi;
137 if (filter != null) {
138 assert query == null;
139 final DocIdSet dis = filter.getDocIdSet(reader);
142 disi = dis.iterator();
144 assert query != null && innerWeight != null;
146 innerWeight.scorer(reader, scoreDocsInOrder, topScorer);
150 return new ConstantScorer(similarity, disi, this);
154 public boolean scoresDocsOutOfOrder() {
155 return (innerWeight != null) ? innerWeight.scoresDocsOutOfOrder() : false;
159 public Explanation explain(IndexReader reader, int doc) throws IOException {
160 final Scorer cs = scorer(reader, true, false);
161 final boolean exists = (cs != null && cs.advance(doc) == doc);
163 final ComplexExplanation result = new ComplexExplanation();
165 result.setDescription(ConstantScoreQuery.this.toString() + ", product of:");
166 result.setValue(queryWeight);
167 result.setMatch(Boolean.TRUE);
168 result.addDetail(new Explanation(getBoost(), "boost"));
169 result.addDetail(new Explanation(queryNorm, "queryNorm"));
171 result.setDescription(ConstantScoreQuery.this.toString() + " doesn't match id " + doc);
173 result.setMatch(Boolean.FALSE);
179 protected class ConstantScorer extends Scorer {
180 final DocIdSetIterator docIdSetIterator;
181 final float theScore;
183 public ConstantScorer(Similarity similarity, DocIdSetIterator docIdSetIterator, Weight w) throws IOException {
185 theScore = w.getValue();
186 this.docIdSetIterator = docIdSetIterator;
190 public int nextDoc() throws IOException {
191 return docIdSetIterator.nextDoc();
196 return docIdSetIterator.docID();
200 public float score() throws IOException {
205 public int advance(int target) throws IOException {
206 return docIdSetIterator.advance(target);
209 private Collector wrapCollector(final Collector collector) {
210 return new Collector() {
212 public void setScorer(Scorer scorer) throws IOException {
213 // we must wrap again here, but using the scorer passed in as parameter:
214 collector.setScorer(new ConstantScorer(ConstantScorer.this.getSimilarity(),
215 scorer, ConstantScorer.this.weight));
219 public void collect(int doc) throws IOException {
220 collector.collect(doc);
224 public void setNextReader(IndexReader reader, int docBase) throws IOException {
225 collector.setNextReader(reader, docBase);
229 public boolean acceptsDocsOutOfOrder() {
230 return collector.acceptsDocsOutOfOrder();
235 // this optimization allows out of order scoring as top scorer!
237 public void score(Collector collector) throws IOException {
238 if (docIdSetIterator instanceof Scorer) {
239 ((Scorer) docIdSetIterator).score(wrapCollector(collector));
241 super.score(collector);
245 // this optimization allows out of order scoring as top scorer,
246 // TODO: theoretically this method should not be called because its protected and
247 // this class does not use it, it should be public in Scorer!
249 protected boolean score(Collector collector, int max, int firstDocID) throws IOException {
250 if (docIdSetIterator instanceof Scorer) {
251 return ((Scorer) docIdSetIterator).score(wrapCollector(collector), max, firstDocID);
253 return super.score(collector, max, firstDocID);
259 public Weight createWeight(Searcher searcher) throws IOException {
260 return new ConstantScoreQuery.ConstantWeight(searcher);
264 public String toString(String field) {
265 return new StringBuilder("ConstantScore(")
266 .append((query == null) ? filter.toString() : query.toString(field))
268 .append(ToStringUtils.boost(getBoost()))
273 public boolean equals(Object o) {
274 if (this == o) return true;
275 if (!super.equals(o))
277 if (o instanceof ConstantScoreQuery) {
278 final ConstantScoreQuery other = (ConstantScoreQuery) o;
280 ((this.filter == null) ? other.filter == null : this.filter.equals(other.filter)) &&
281 ((this.query == null) ? other.query == null : this.query.equals(other.query));
287 public int hashCode() {
288 return 31 * super.hashCode() +
289 ((query == null) ? filter : query).hashCode();