LUCENE-2190: Added a new class CustomScoreProvider to function package that can be subclassed to provide custom scoring to CustomScoreQuery

git-svn-id: https://svn.apache.org/repos/asf/lucene/java/trunk@912386 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Uwe Schindler 2010-02-21 18:27:05 +00:00
parent 8b165b40b5
commit 30b45bf592
6 changed files with 347 additions and 250 deletions

View File

@ -20,6 +20,9 @@ Changes in backwards compatibility policy
toString. These are advanced APIs and subject to change suddenly. toString. These are advanced APIs and subject to change suddenly.
(Tim Smith via Mike McCandless) (Tim Smith via Mike McCandless)
* LUCENE-2190: Removed deprecated customScore() and customExplain()
methods from experimental CustomScoreQuery. (Uwe Schindler)
Changes in runtime behavior Changes in runtime behavior
* LUCENE-1923: Made IndexReader.toString() produce something * LUCENE-1923: Made IndexReader.toString() produce something
@ -259,10 +262,16 @@ API Changes
* LUCENE-1972 (3.0.1 only): Restore SortField.getComparatorSource * LUCENE-1972 (3.0.1 only): Restore SortField.getComparatorSource
(it was accidentally removed in 3.0.0) (John Wang via Uwe Schindler) (it was accidentally removed in 3.0.0) (John Wang via Uwe Schindler)
* LUCENE-2190: Added setNextReader method to CustomScoreQuery, which * LUCENE-2190: Added a new class CustomScoreProvider to function package
is necessary with per-segment searching to notify the subclass that can be subclassed to provide custom scoring to CustomScoreQuery.
which reader the int doc, passed to customScore, refers to. (Paul The methods in CustomScoreQuery that did this before were deprecated
chez Jamespot via Mike McCandless) and replaced by a method getCustomScoreProvider(IndexReader) that
returns a custom score implementation using the above class. The change
is necessary with per-segment searching, as CustomScoreQuery is
a stateless class (like all other Queries) and does not know about
the currently searched segment. This API works similar to Filter's
getDocIdSet(IndexReader). (Paul chez Jamespot via Mike McCandless,
Uwe Schindler)
* LUCENE-2080: Deprecate Version.LUCENE_CURRENT, as using this constant * LUCENE-2080: Deprecate Version.LUCENE_CURRENT, as using this constant
will cause backwards compatibility problems when upgrading Lucene. See will cause backwards compatibility problems when upgrading Lucene. See

View File

@ -43,7 +43,7 @@
<property name="dev.version" value="3.1-dev"/> <property name="dev.version" value="3.1-dev"/>
<property name="version" value="${dev.version}"/> <property name="version" value="${dev.version}"/>
<property name="backwards.branch" value="lucene_3_0_back_compat_tests"/> <property name="backwards.branch" value="lucene_3_0_back_compat_tests"/>
<property name="backwards.rev" value="912375"/> <property name="backwards.rev" value="912385"/>
<property name="spec.version" value="${version}"/> <property name="spec.version" value="${version}"/>
<property name="year" value="2000-${current.year}"/> <property name="year" value="2000-${current.year}"/>
<property name="final.name" value="lucene-${name}-${version}"/> <property name="final.name" value="lucene-${name}-${version}"/>

View File

@ -27,6 +27,7 @@ import org.apache.lucene.analysis.WhitespaceAnalyzer;
import org.apache.lucene.document.Document; import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field; import org.apache.lucene.document.Field;
import org.apache.lucene.index.IndexWriter; import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.Term; import org.apache.lucene.index.Term;
import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query; import org.apache.lucene.search.Query;
@ -36,6 +37,7 @@ import org.apache.lucene.search.SortField;
import org.apache.lucene.search.TermQuery; import org.apache.lucene.search.TermQuery;
import org.apache.lucene.search.TopDocs; import org.apache.lucene.search.TopDocs;
import org.apache.lucene.search.function.CustomScoreQuery; import org.apache.lucene.search.function.CustomScoreQuery;
import org.apache.lucene.search.function.CustomScoreProvider;
import org.apache.lucene.search.function.FieldScoreQuery; import org.apache.lucene.search.function.FieldScoreQuery;
import org.apache.lucene.search.function.FieldScoreQuery.Type; import org.apache.lucene.search.function.FieldScoreQuery.Type;
import org.apache.lucene.spatial.geohash.GeoHashUtils; import org.apache.lucene.spatial.geohash.GeoHashUtils;
@ -189,20 +191,26 @@ public class TestCartesian extends TestCase{
CustomScoreQuery customScore = new CustomScoreQuery(dq.getQuery(tq),fsQuery){ CustomScoreQuery customScore = new CustomScoreQuery(dq.getQuery(tq),fsQuery){
@Override @Override
public float customScore(int doc, float subQueryScore, float valSrcScore){ protected CustomScoreProvider getCustomScoreProvider(IndexReader reader) {
System.out.println(doc); return new CustomScoreProvider(reader) {
if (dq.distanceFilter.getDistance(doc) == null) @Override // TODO: broken, as reader is not used!
return 0; public float customScore(int doc, float subQueryScore, float valSrcScore){
System.out.println(doc);
if (dq.distanceFilter.getDistance(doc) == null)
return 0;
double distance = dq.distanceFilter.getDistance(doc); double distance = dq.distanceFilter.getDistance(doc);
// boost score shouldn't exceed 1 // boost score shouldn't exceed 1
if (distance < 1.0d) if (distance < 1.0d)
distance = 1.0d; distance = 1.0d;
//boost by distance is invertly proportional to //boost by distance is invertly proportional to
// to distance from center point to location // to distance from center point to location
float score = (float) ((miles - distance) / miles ); float score = (float) ((miles - distance) / miles );
return score * subQueryScore; return score * subQueryScore;
}
};
} }
}; };
// Create a distance sort // Create a distance sort
// As the radius filter has performed the distance calculations // As the radius filter has performed the distance calculations
@ -276,20 +284,26 @@ public class TestCartesian extends TestCase{
CustomScoreQuery customScore = new CustomScoreQuery(dq.getQuery(tq),fsQuery){ CustomScoreQuery customScore = new CustomScoreQuery(dq.getQuery(tq),fsQuery){
@Override @Override
public float customScore(int doc, float subQueryScore, float valSrcScore){ protected CustomScoreProvider getCustomScoreProvider(IndexReader reader) {
System.out.println(doc); return new CustomScoreProvider(reader) {
if (dq.distanceFilter.getDistance(doc) == null) @Override // TODO: broken, as reader is not used!
return 0; public float customScore(int doc, float subQueryScore, float valSrcScore){
System.out.println(doc);
if (dq.distanceFilter.getDistance(doc) == null)
return 0;
double distance = dq.distanceFilter.getDistance(doc); double distance = dq.distanceFilter.getDistance(doc);
// boost score shouldn't exceed 1 // boost score shouldn't exceed 1
if (distance < 1.0d) if (distance < 1.0d)
distance = 1.0d; distance = 1.0d;
//boost by distance is invertly proportional to //boost by distance is invertly proportional to
// to distance from center point to location // to distance from center point to location
float score = (float) ((miles - distance) / miles ); float score = (float) ((miles - distance) / miles );
return score * subQueryScore; return score * subQueryScore;
}
};
} }
}; };
// Create a distance sort // Create a distance sort
// As the radius filter has performed the distance calculations // As the radius filter has performed the distance calculations
@ -363,23 +377,27 @@ public class TestCartesian extends TestCase{
FieldScoreQuery fsQuery = new FieldScoreQuery("geo_distance", Type.FLOAT); FieldScoreQuery fsQuery = new FieldScoreQuery("geo_distance", Type.FLOAT);
CustomScoreQuery customScore = new CustomScoreQuery(dq.getQuery(tq),fsQuery){ CustomScoreQuery customScore = new CustomScoreQuery(dq.getQuery(tq),fsQuery){
@Override
@Override protected CustomScoreProvider getCustomScoreProvider(IndexReader reader) {
return new CustomScoreProvider(reader) {
@Override // TODO: broken, as reader is not used!
public float customScore(int doc, float subQueryScore, float valSrcScore){ public float customScore(int doc, float subQueryScore, float valSrcScore){
//System.out.println(doc); //System.out.println(doc);
if (dq.distanceFilter.getDistance(doc) == null) if (dq.distanceFilter.getDistance(doc) == null)
return 0; return 0;
double distance = dq.distanceFilter.getDistance(doc); double distance = dq.distanceFilter.getDistance(doc);
// boost score shouldn't exceed 1 // boost score shouldn't exceed 1
if (distance < 1.0d) if (distance < 1.0d)
distance = 1.0d; distance = 1.0d;
//boost by distance is invertly proportional to //boost by distance is invertly proportional to
// to distance from center point to location // to distance from center point to location
float score = (float) ( (miles - distance) / miles ); float score = (float) ( (miles - distance) / miles );
return score * subQueryScore; return score * subQueryScore;
} }
}; };
}
};
// Create a distance sort // Create a distance sort
// As the radius filter has performed the distance calculations // As the radius filter has performed the distance calculations
// already, pass in the filter to reuse the results. // already, pass in the filter to reuse the results.
@ -452,23 +470,27 @@ public class TestCartesian extends TestCase{
FieldScoreQuery fsQuery = new FieldScoreQuery("geo_distance", Type.FLOAT); FieldScoreQuery fsQuery = new FieldScoreQuery("geo_distance", Type.FLOAT);
CustomScoreQuery customScore = new CustomScoreQuery(tq,fsQuery){ CustomScoreQuery customScore = new CustomScoreQuery(tq,fsQuery){
@Override
@Override protected CustomScoreProvider getCustomScoreProvider(IndexReader reader) {
public float customScore(int doc, float subQueryScore, float valSrcScore){ return new CustomScoreProvider(reader) {
//System.out.println(doc); @Override // TODO: broken, as reader is not used!
if (dq.distanceFilter.getDistance(doc) == null) public float customScore(int doc, float subQueryScore, float valSrcScore){
return 0; //System.out.println(doc);
if (dq.distanceFilter.getDistance(doc) == null)
double distance = dq.distanceFilter.getDistance(doc); return 0;
// boost score shouldn't exceed 1
if (distance < 1.0d) double distance = dq.distanceFilter.getDistance(doc);
distance = 1.0d; // boost score shouldn't exceed 1
//boost by distance is invertly proportional to if (distance < 1.0d)
// to distance from center point to location distance = 1.0d;
float score = (float) ( (miles - distance) / miles ); //boost by distance is invertly proportional to
return score * subQueryScore; // to distance from center point to location
} float score = (float) ( (miles - distance) / miles );
}; return score * subQueryScore;
}
};
}
};
// Create a distance sort // Create a distance sort
// As the radius filter has performed the distance calculations // As the radius filter has performed the distance calculations
// already, pass in the filter to reuse the results. // already, pass in the filter to reuse the results.

View File

@ -0,0 +1,163 @@
package org.apache.lucene.search.function;
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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.
*/
import java.io.IOException;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.search.ComplexExplanation;
import org.apache.lucene.search.Explanation;
import org.apache.lucene.search.FieldCache; // for javadocs
/**
* An instance of this subclass should be returned by
* {@link CustomScoreQuery#getCustomScoreProvider}, if you want
* to modify the custom score calculation of a {@link CustomScoreQuery}.
* <p>Since Lucene 2.9, queries operate on each segment of an index separately,
* so the protected {@link #reader} field can be used to resolve doc IDs,
* as the supplied <code>doc</code> ID is per-segment and without knowledge
* of the IndexReader you cannot access the document or {@link FieldCache}.
*
* @lucene.experimental
* @since 2.9.2
*/
public class CustomScoreProvider {
protected final IndexReader reader;
/**
* Creates a new instance of the provider class for the given {@link IndexReader}.
*/
public CustomScoreProvider(IndexReader reader) {
this.reader = reader;
}
/**
* Compute a custom score by the subQuery score and a number of
* {@link ValueSourceQuery} scores.
* <p>
* Subclasses can override this method to modify the custom score.
* <p>
* If your custom scoring is different than the default herein you
* should override at least one of the two customScore() methods.
* If the number of ValueSourceQueries is always &lt; 2 it is
* sufficient to override the other
* {@link #customScore(int, float, float) customScore()}
* method, which is simpler.
* <p>
* The default computation herein is a multiplication of given scores:
* <pre>
* ModifiedScore = valSrcScore * valSrcScores[0] * valSrcScores[1] * ...
* </pre>
*
* @param doc id of scored doc.
* @param subQueryScore score of that doc by the subQuery.
* @param valSrcScores scores of that doc by the ValueSourceQuery.
* @return custom score.
*/
public float customScore(int doc, float subQueryScore, float valSrcScores[]) throws IOException {
if (valSrcScores.length == 1) {
return customScore(doc, subQueryScore, valSrcScores[0]);
}
if (valSrcScores.length == 0) {
return customScore(doc, subQueryScore, 1);
}
float score = subQueryScore;
for(int i = 0; i < valSrcScores.length; i++) {
score *= valSrcScores[i];
}
return score;
}
/**
* Compute a custom score by the subQuery score and the ValueSourceQuery score.
* <p>
* Subclasses can override this method to modify the custom score.
* <p>
* If your custom scoring is different than the default herein you
* should override at least one of the two customScore() methods.
* If the number of ValueSourceQueries is always &lt; 2 it is
* sufficient to override this customScore() method, which is simpler.
* <p>
* The default computation herein is a multiplication of the two scores:
* <pre>
* ModifiedScore = subQueryScore * valSrcScore
* </pre>
*
* @param doc id of scored doc.
* @param subQueryScore score of that doc by the subQuery.
* @param valSrcScore score of that doc by the ValueSourceQuery.
* @return custom score.
*/
public float customScore(int doc, float subQueryScore, float valSrcScore) throws IOException {
return subQueryScore * valSrcScore;
}
/**
* Explain the custom score.
* Whenever overriding {@link #customScore(int, float, float[])},
* this method should also be overridden to provide the correct explanation
* for the part of the custom scoring.
*
* @param doc doc being explained.
* @param subQueryExpl explanation for the sub-query part.
* @param valSrcExpls explanation for the value source part.
* @return an explanation for the custom score
*/
public Explanation customExplain(int doc, Explanation subQueryExpl, Explanation valSrcExpls[]) throws IOException {
if (valSrcExpls.length == 1) {
return customExplain(doc, subQueryExpl, valSrcExpls[0]);
}
if (valSrcExpls.length == 0) {
return subQueryExpl;
}
float valSrcScore = 1;
for (int i = 0; i < valSrcExpls.length; i++) {
valSrcScore *= valSrcExpls[i].getValue();
}
Explanation exp = new Explanation( valSrcScore * subQueryExpl.getValue(), "custom score: product of:");
exp.addDetail(subQueryExpl);
for (int i = 0; i < valSrcExpls.length; i++) {
exp.addDetail(valSrcExpls[i]);
}
return exp;
}
/**
* Explain the custom score.
* Whenever overriding {@link #customScore(int, float, float)},
* this method should also be overridden to provide the correct explanation
* for the part of the custom scoring.
*
* @param doc doc being explained.
* @param subQueryExpl explanation for the sub-query part.
* @param valSrcExpl explanation for the value source part.
* @return an explanation for the custom score
*/
public Explanation customExplain(int doc, Explanation subQueryExpl, Explanation valSrcExpl) throws IOException {
float valSrcScore = 1;
if (valSrcExpl != null) {
valSrcScore *= valSrcExpl.getValue();
}
Explanation exp = new Explanation( valSrcScore * subQueryExpl.getValue(), "custom score: product of:");
exp.addDetail(subQueryExpl);
exp.addDetail(valSrcExpl);
return exp;
}
}

View File

@ -40,7 +40,7 @@ import org.apache.lucene.util.ToStringUtils;
* For most simple/convenient use cases this query is likely to be a * For most simple/convenient use cases this query is likely to be a
* {@link org.apache.lucene.search.function.FieldScoreQuery FieldScoreQuery}</li> * {@link org.apache.lucene.search.function.FieldScoreQuery FieldScoreQuery}</li>
* </ol> * </ol>
* Subclasses can modify the computation by overriding {@link #customScore(int, float, float)}. * Subclasses can modify the computation by overriding {@link #getCustomScoreProvider}.
* *
* @lucene.experimental * @lucene.experimental
*/ */
@ -80,7 +80,6 @@ public class CustomScoreQuery extends Query {
* This parameter is optional - it can be null or even an empty array. * This parameter is optional - it can be null or even an empty array.
*/ */
public CustomScoreQuery(Query subQuery, ValueSourceQuery... valSrcQueries) { public CustomScoreQuery(Query subQuery, ValueSourceQuery... valSrcQueries) {
super();
this.subQuery = subQuery; this.subQuery = subQuery;
this.valSrcQueries = valSrcQueries!=null? this.valSrcQueries = valSrcQueries!=null?
valSrcQueries : new ValueSourceQuery[0]; valSrcQueries : new ValueSourceQuery[0];
@ -90,11 +89,23 @@ public class CustomScoreQuery extends Query {
/*(non-Javadoc) @see org.apache.lucene.search.Query#rewrite(org.apache.lucene.index.IndexReader) */ /*(non-Javadoc) @see org.apache.lucene.search.Query#rewrite(org.apache.lucene.index.IndexReader) */
@Override @Override
public Query rewrite(IndexReader reader) throws IOException { public Query rewrite(IndexReader reader) throws IOException {
subQuery = subQuery.rewrite(reader); CustomScoreQuery clone = null;
for(int i = 0; i < valSrcQueries.length; i++) {
valSrcQueries[i] = (ValueSourceQuery) valSrcQueries[i].rewrite(reader); final Query sq = subQuery.rewrite(reader);
if (sq != subQuery) {
clone = (CustomScoreQuery) clone();
clone.subQuery = sq;
} }
return this;
for(int i = 0; i < valSrcQueries.length; i++) {
final ValueSourceQuery v = (ValueSourceQuery) valSrcQueries[i].rewrite(reader);
if (v != valSrcQueries[i]) {
if (clone == null) clone = (CustomScoreQuery) clone();
clone.valSrcQueries[i] = v;
}
}
return (clone == null) ? this : clone;
} }
/*(non-Javadoc) @see org.apache.lucene.search.Query#extractTerms(java.util.Set) */ /*(non-Javadoc) @see org.apache.lucene.search.Query#extractTerms(java.util.Set) */
@ -139,7 +150,8 @@ public class CustomScoreQuery extends Query {
} }
CustomScoreQuery other = (CustomScoreQuery)o; CustomScoreQuery other = (CustomScoreQuery)o;
if (this.getBoost() != other.getBoost() || if (this.getBoost() != other.getBoost() ||
!this.subQuery.equals(other.subQuery)|| !this.subQuery.equals(other.subQuery) ||
this.strict != other.strict ||
this.valSrcQueries.length != other.valSrcQueries.length) { this.valSrcQueries.length != other.valSrcQueries.length) {
return false; return false;
} }
@ -150,132 +162,17 @@ public class CustomScoreQuery extends Query {
@Override @Override
public int hashCode() { public int hashCode() {
return (getClass().hashCode() + subQuery.hashCode() + Arrays.hashCode(valSrcQueries)) return (getClass().hashCode() + subQuery.hashCode() + Arrays.hashCode(valSrcQueries))
^ Float.floatToIntBits(getBoost()); ^ Float.floatToIntBits(getBoost()) ^ (strict ? 1234 : 4321);
} }
/** /**
* Compute a custom score by the subQuery score and a number of * Returns a {@link CustomScoreProvider} that calculates the custom scores
* ValueSourceQuery scores. * for the given {@link IndexReader}. The default implementation returns a default
* <p> * implementation as specified in the docs of {@link CustomScoreProvider}.
* Subclasses can override this method to modify the custom score. * @since 2.9.2
* <p>
* If your custom scoring is different than the default herein you
* should override at least one of the two customScore() methods.
* If the number of ValueSourceQueries is always &lt; 2 it is
* sufficient to override the other
* {@link #customScore(int, float, float) customScore()}
* method, which is simpler.
* <p>
* The default computation herein is a multiplication of given scores:
* <pre>
* ModifiedScore = valSrcScore * valSrcScores[0] * valSrcScores[1] * ...
* </pre>
*
* @param doc id of scored doc.
* @param subQueryScore score of that doc by the subQuery.
* @param valSrcScores scores of that doc by the ValueSourceQuery.
* @return custom score.
*/ */
public float customScore(int doc, float subQueryScore, float valSrcScores[]) { protected CustomScoreProvider getCustomScoreProvider(IndexReader reader) throws IOException {
if (valSrcScores.length == 1) { return new CustomScoreProvider(reader);
return customScore(doc, subQueryScore, valSrcScores[0]);
}
if (valSrcScores.length == 0) {
return customScore(doc, subQueryScore, 1);
}
float score = subQueryScore;
for(int i = 0; i < valSrcScores.length; i++) {
score *= valSrcScores[i];
}
return score;
}
/**
* Compute a custom score by the subQuery score and the ValueSourceQuery score.
* <p>
* Subclasses can override this method to modify the custom score.
* <p>
* If your custom scoring is different than the default herein you
* should override at least one of the two customScore() methods.
* If the number of ValueSourceQueries is always &lt; 2 it is
* sufficient to override this customScore() method, which is simpler.
* <p>
* The default computation herein is a multiplication of the two scores:
* <pre>
* ModifiedScore = subQueryScore * valSrcScore
* </pre>
*
* <p><b>NOTE</b>: The doc is relative to the current
* reader, last passed to {@link #setNextReader}.
*
* @param doc id of scored doc.
* @param subQueryScore score of that doc by the subQuery.
* @param valSrcScore score of that doc by the ValueSourceQuery.
* @return custom score.
*/
public float customScore(int doc, float subQueryScore, float valSrcScore) {
return subQueryScore * valSrcScore;
}
/**
* Called when the scoring switches to another reader.
*
* @param reader
* next IndexReader
*/
public void setNextReader(IndexReader reader) throws IOException {
}
/**
* Explain the custom score.
* Whenever overriding {@link #customScore(int, float, float[])},
* this method should also be overridden to provide the correct explanation
* for the part of the custom scoring.
*
* @param doc doc being explained.
* @param subQueryExpl explanation for the sub-query part.
* @param valSrcExpls explanation for the value source part.
* @return an explanation for the custom score
*/
public Explanation customExplain(int doc, Explanation subQueryExpl, Explanation valSrcExpls[]) {
if (valSrcExpls.length == 1) {
return customExplain(doc, subQueryExpl, valSrcExpls[0]);
}
if (valSrcExpls.length == 0) {
return subQueryExpl;
}
float valSrcScore = 1;
for (int i = 0; i < valSrcExpls.length; i++) {
valSrcScore *= valSrcExpls[i].getValue();
}
Explanation exp = new Explanation( valSrcScore * subQueryExpl.getValue(), "custom score: product of:");
exp.addDetail(subQueryExpl);
for (int i = 0; i < valSrcExpls.length; i++) {
exp.addDetail(valSrcExpls[i]);
}
return exp;
}
/**
* Explain the custom score.
* Whenever overriding {@link #customScore(int, float, float)},
* this method should also be overridden to provide the correct explanation
* for the part of the custom scoring.
*
* @param doc doc being explained.
* @param subQueryExpl explanation for the sub-query part.
* @param valSrcExpl explanation for the value source part.
* @return an explanation for the custom score
*/
public Explanation customExplain(int doc, Explanation subQueryExpl, Explanation valSrcExpl) {
float valSrcScore = 1;
if (valSrcExpl != null) {
valSrcScore *= valSrcExpl.getValue();
}
Explanation exp = new Explanation( valSrcScore * subQueryExpl.getValue(), "custom score: product of:");
exp.addDetail(subQueryExpl);
exp.addDetail(valSrcExpl);
return exp;
} }
//=========================== W E I G H T ============================ //=========================== W E I G H T ============================
@ -371,7 +268,7 @@ public class CustomScoreQuery extends Query {
for(int i = 0; i < valSrcWeights.length; i++) { for(int i = 0; i < valSrcWeights.length; i++) {
valSrcExpls[i] = valSrcWeights[i].explain(reader, doc); valSrcExpls[i] = valSrcWeights[i].explain(reader, doc);
} }
Explanation customExp = customExplain(doc,subQueryExpl,valSrcExpls); Explanation customExp = CustomScoreQuery.this.getCustomScoreProvider(reader).customExplain(doc,subQueryExpl,valSrcExpls);
float sc = getValue() * customExp.getValue(); float sc = getValue() * customExp.getValue();
Explanation res = new ComplexExplanation( Explanation res = new ComplexExplanation(
true, sc, CustomScoreQuery.this.toString() + ", product of:"); true, sc, CustomScoreQuery.this.toString() + ", product of:");
@ -398,6 +295,7 @@ public class CustomScoreQuery extends Query {
private Scorer subQueryScorer; private Scorer subQueryScorer;
private Scorer[] valSrcScorers; private Scorer[] valSrcScorers;
private IndexReader reader; private IndexReader reader;
private final CustomScoreProvider provider;
private float vScores[]; // reused in score() to avoid allocating this array for each doc private float vScores[]; // reused in score() to avoid allocating this array for each doc
// constructor // constructor
@ -409,7 +307,7 @@ public class CustomScoreQuery extends Query {
this.valSrcScorers = valSrcScorers; this.valSrcScorers = valSrcScorers;
this.reader = reader; this.reader = reader;
this.vScores = new float[valSrcScorers.length]; this.vScores = new float[valSrcScorers.length];
setNextReader(reader); this.provider = CustomScoreQuery.this.getCustomScoreProvider(reader);
} }
@Override @Override
@ -434,7 +332,7 @@ public class CustomScoreQuery extends Query {
for (int i = 0; i < valSrcScorers.length; i++) { for (int i = 0; i < valSrcScorers.length; i++) {
vScores[i] = valSrcScorers[i].score(); vScores[i] = valSrcScorers[i].score();
} }
return qWeight * customScore(subQueryScorer.docID(), subQueryScorer.score(), vScores); return qWeight * provider.customScore(subQueryScorer.docID(), subQueryScorer.score(), vScores);
} }
@Override @Override

View File

@ -33,7 +33,6 @@ import org.apache.lucene.index.IndexReader;
/** /**
* Test CustomScoreQuery search. * Test CustomScoreQuery search.
*/ */
@SuppressWarnings({"MagicNumber"})
public class TestCustomScoreQuery extends FunctionTestSetup { public class TestCustomScoreQuery extends FunctionTestSetup {
/* @override constructor */ /* @override constructor */
@ -96,23 +95,26 @@ public class TestCustomScoreQuery extends FunctionTestSetup {
public String name() { public String name() {
return "customAdd"; return "customAdd";
} }
/*(non-Javadoc) @see org.apache.lucene.search.function.CustomScoreQuery#customScore(int, float, float) */
@Override @Override
public float customScore(int doc, float subQueryScore, float valSrcScore) { protected CustomScoreProvider getCustomScoreProvider(IndexReader reader) {
return subQueryScore + valSrcScore; return new CustomScoreProvider(reader) {
} @Override
public float customScore(int doc, float subQueryScore, float valSrcScore) {
return subQueryScore + valSrcScore;
}
/* (non-Javadoc)@see org.apache.lucene.search.function.CustomScoreQuery#customExplain(int, org.apache.lucene.search.Explanation, org.apache.lucene.search.Explanation)*/ @Override
@Override public Explanation customExplain(int doc, Explanation subQueryExpl, Explanation valSrcExpl) {
public Explanation customExplain(int doc, Explanation subQueryExpl, Explanation valSrcExpl) { float valSrcScore = valSrcExpl == null ? 0 : valSrcExpl.getValue();
float valSrcScore = valSrcExpl == null ? 0 : valSrcExpl.getValue(); Explanation exp = new Explanation(valSrcScore + subQueryExpl.getValue(), "custom score: sum of:");
Explanation exp = new Explanation(valSrcScore + subQueryExpl.getValue(), "custom score: sum of:"); exp.addDetail(subQueryExpl);
exp.addDetail(subQueryExpl); if (valSrcExpl != null) {
if (valSrcExpl != null) { exp.addDetail(valSrcExpl);
exp.addDetail(valSrcExpl); }
} return exp;
return exp; }
};
} }
} }
@ -130,52 +132,55 @@ public class TestCustomScoreQuery extends FunctionTestSetup {
return "customMulAdd"; return "customMulAdd";
} }
/*(non-Javadoc) @see org.apache.lucene.search.function.CustomScoreQuery#customScore(int, float, float) */
@Override @Override
public float customScore(int doc, float subQueryScore, float valSrcScores[]) { protected CustomScoreProvider getCustomScoreProvider(IndexReader reader) {
if (valSrcScores.length == 0) { return new CustomScoreProvider(reader) {
return subQueryScore; @Override
} public float customScore(int doc, float subQueryScore, float valSrcScores[]) {
if (valSrcScores.length == 1) { if (valSrcScores.length == 0) {
return subQueryScore + valSrcScores[0]; return subQueryScore;
// confirm that skipping beyond the last doc, on the }
// previous reader, hits NO_MORE_DOCS if (valSrcScores.length == 1) {
} return subQueryScore + valSrcScores[0];
return (subQueryScore + valSrcScores[0]) * valSrcScores[1]; // we know there are two // confirm that skipping beyond the last doc, on the
} // previous reader, hits NO_MORE_DOCS
}
return (subQueryScore + valSrcScores[0]) * valSrcScores[1]; // we know there are two
}
/* (non-Javadoc)@see org.apache.lucene.search.function.CustomScoreQuery#customExplain(int, org.apache.lucene.search.Explanation, org.apache.lucene.search.Explanation)*/ @Override
@Override public Explanation customExplain(int doc, Explanation subQueryExpl, Explanation valSrcExpls[]) {
public Explanation customExplain(int doc, Explanation subQueryExpl, Explanation valSrcExpls[]) { if (valSrcExpls.length == 0) {
if (valSrcExpls.length == 0) { return subQueryExpl;
return subQueryExpl; }
} Explanation exp = new Explanation(valSrcExpls[0].getValue() + subQueryExpl.getValue(), "sum of:");
Explanation exp = new Explanation(valSrcExpls[0].getValue() + subQueryExpl.getValue(), "sum of:"); exp.addDetail(subQueryExpl);
exp.addDetail(subQueryExpl); exp.addDetail(valSrcExpls[0]);
exp.addDetail(valSrcExpls[0]); if (valSrcExpls.length == 1) {
if (valSrcExpls.length == 1) { exp.setDescription("CustomMulAdd, sum of:");
exp.setDescription("CustomMulAdd, sum of:"); return exp;
return exp; }
} Explanation exp2 = new Explanation(valSrcExpls[1].getValue() * exp.getValue(), "custom score: product of:");
Explanation exp2 = new Explanation(valSrcExpls[1].getValue() * exp.getValue(), "custom score: product of:"); exp2.addDetail(valSrcExpls[1]);
exp2.addDetail(valSrcExpls[1]); exp2.addDetail(exp);
exp2.addDetail(exp); return exp2;
return exp2; }
};
} }
} }
private final class CustomExternalQuery extends CustomScoreQuery { private final class CustomExternalQuery extends CustomScoreQuery {
private IndexReader reader;
private int[] values;
public float customScore(int doc, float subScore, float valSrcScore) { @Override
assertTrue(doc <= reader.maxDoc()); protected CustomScoreProvider getCustomScoreProvider(IndexReader reader) throws IOException {
return (float) values[doc]; final int[] values = FieldCache.DEFAULT.getInts(reader, INT_FIELD);
} return new CustomScoreProvider(reader) {
@Override
public void setNextReader(IndexReader r) throws IOException { public float customScore(int doc, float subScore, float valSrcScore) throws IOException {
reader = r; assertTrue(doc <= reader.maxDoc());
values = FieldCache.DEFAULT.getInts(r, INT_FIELD); return (float) values[doc];
}
};
} }
public CustomExternalQuery(Query q) { public CustomExternalQuery(Query q) {