2
* Licensed to the Apache Software Foundation (ASF) under one or more
3
* contributor license agreements. See the NOTICE file distributed with
4
* this work for additional information regarding copyright ownership.
5
* The ASF licenses this file to You under the Apache License, Version 2.0
6
* (the "License"); you may not use this file except in compliance with
7
* the License. You may obtain a copy of the License at
9
* http://www.apache.org/licenses/LICENSE-2.0
11
* Unless required by applicable law or agreed to in writing, software
12
* distributed under the License is distributed on an "AS IS" BASIS,
13
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
* See the License for the specific language governing permissions and
15
* limitations under the License.
18
package org.apache.solr.search.function;
20
import org.apache.lucene.index.IndexReader;
21
import org.apache.lucene.search.*;
22
import org.apache.solr.search.SolrIndexReader;
24
import java.io.IOException;
30
* Returns a score for each document based on a ValueSource,
31
* often some function of the value of a field.
33
* <b>Note: This API is experimental and may change in non backward-compatible ways in the future</b>
35
* @version $Id: FunctionQuery.java 1065312 2011-01-30 16:08:25Z rmuir $
37
public class FunctionQuery extends Query {
41
* @param func defines the function to be used for scoring
43
public FunctionQuery(ValueSource func) {
47
/** @return The associated ValueSource */
48
public ValueSource getValueSource() {
53
public Query rewrite(IndexReader reader) throws IOException {
58
public void extractTerms(Set terms) {}
60
protected class FunctionWeight extends Weight {
61
protected Searcher searcher;
62
protected float queryNorm;
63
protected float queryWeight;
64
protected Map context;
66
public FunctionWeight(Searcher searcher) throws IOException {
67
this.searcher = searcher;
68
this.context = func.newContext();
69
func.createWeight(context, searcher);
73
public Query getQuery() {
74
return FunctionQuery.this;
78
public float getValue() {
83
public float sumOfSquaredWeights() throws IOException {
84
queryWeight = getBoost();
85
return queryWeight * queryWeight;
89
public void normalize(float norm) {
90
this.queryNorm = norm;
91
queryWeight *= this.queryNorm;
95
public Scorer scorer(IndexReader reader, boolean scoreDocsInOrder, boolean topScorer) throws IOException {
96
return new AllScorer(getSimilarity(searcher), reader, this);
100
public Explanation explain(IndexReader reader, int doc) throws IOException {
101
SolrIndexReader topReader = (SolrIndexReader)reader;
102
SolrIndexReader[] subReaders = topReader.getLeafReaders();
103
int[] offsets = topReader.getLeafOffsets();
104
int readerPos = SolrIndexReader.readerIndex(doc, offsets);
105
int readerBase = offsets[readerPos];
106
return ((AllScorer)scorer(subReaders[readerPos], true, true)).explain(doc-readerBase);
110
protected class AllScorer extends Scorer {
111
final IndexReader reader;
112
final FunctionWeight weight;
116
final DocValues vals;
117
final boolean hasDeletions;
119
public AllScorer(Similarity similarity, IndexReader reader, FunctionWeight w) throws IOException {
120
super(similarity, w);
122
this.qWeight = w.getValue();
123
this.reader = reader;
124
this.maxDoc = reader.maxDoc();
125
this.hasDeletions = reader.hasDeletions();
126
vals = func.getValues(weight.context, reader);
135
// instead of matching all docs, we could also embed a query.
136
// the score could either ignore the subscore, or boost it.
137
// Containment: floatline(foo:myTerm, "myFloatField", 1.0, 0.0f)
138
// Boost: foo:myTerm^floatline("myFloatField",1.0,0.0f)
139
public int nextDoc() throws IOException {
143
return doc=NO_MORE_DOCS;
145
if (hasDeletions && reader.isDeleted(doc)) continue;
151
public int advance(int target) throws IOException {
152
// this will work even if target==NO_MORE_DOCS
157
// instead of matching all docs, we could also embed a query.
158
// the score could either ignore the subscore, or boost it.
159
// Containment: floatline(foo:myTerm, "myFloatField", 1.0, 0.0f)
160
// Boost: foo:myTerm^floatline("myFloatField",1.0,0.0f)
161
public boolean next() throws IOException {
167
if (hasDeletions && reader.isDeleted(doc)) continue;
168
// todo: maybe allow score() to throw a specific exception
169
// and continue on to the next document if it is thrown...
170
// that may be useful, but exceptions aren't really good
181
public float score() throws IOException {
182
float score = qWeight * vals.floatVal(doc);
184
// Current Lucene priority queues can't handle NaN and -Infinity, so
185
// map to -Float.MAX_VALUE. This conditional handles both -infinity
186
// and NaN since comparisons with NaN are always false.
187
return score>Float.NEGATIVE_INFINITY ? score : -Float.MAX_VALUE;
190
public boolean skipTo(int target) throws IOException {
195
public Explanation explain(int doc) throws IOException {
196
float sc = qWeight * vals.floatVal(doc);
198
Explanation result = new ComplexExplanation
199
(true, sc, "FunctionQuery(" + func + "), product of:");
201
result.addDetail(vals.explain(doc));
202
result.addDetail(new Explanation(getBoost(), "boost"));
203
result.addDetail(new Explanation(weight.queryNorm,"queryNorm"));
210
public Weight createWeight(Searcher searcher) throws IOException {
211
return new FunctionQuery.FunctionWeight(searcher);
215
/** Prints a user-readable version of this query. */
217
public String toString(String field)
219
float boost = getBoost();
220
return (boost!=1.0?"(":"") + func.toString()
221
+ (boost==1.0 ? "" : ")^"+boost);
225
/** Returns true if <code>o</code> is equal to this. */
227
public boolean equals(Object o) {
228
if (FunctionQuery.class != o.getClass()) return false;
229
FunctionQuery other = (FunctionQuery)o;
230
return this.getBoost() == other.getBoost()
231
&& this.func.equals(other.func);
234
/** Returns a hash code value for this object. */
236
public int hashCode() {
237
return func.hashCode()*31 + Float.floatToIntBits(getBoost());