mirror of https://github.com/apache/lucene.git
LUCENE-3514: also support IndexSearcher.searchAfter when Sort is used
git-svn-id: https://svn.apache.org/repos/asf/lucene/dev/trunk@1339137 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
560a287e68
commit
ad7914c5b2
|
@ -266,6 +266,11 @@ Changes in backwards compatibility policy
|
|||
* LUCENE-3970: Rename Fields.getUniqueFieldCount -> .size() and
|
||||
Terms.getUniqueTermCount -> .size(). (Iulius Curt via Mike McCandless)
|
||||
|
||||
* LUCENE-3514: IndexSearcher.setDefaultFieldSortScoring was removed
|
||||
and replaced with per-search control via new expert search methods
|
||||
that take two booleans indicating whether hit scores and max
|
||||
score should be computed. (Mike McCandless)
|
||||
|
||||
Changes in Runtime Behavior
|
||||
|
||||
* LUCENE-2846: omitNorms now behaves like omitTermFrequencyAndPositions, if you
|
||||
|
@ -857,6 +862,10 @@ New features
|
|||
* LUCENE-4039: Add AddIndexesTask to benchmark, which uses IW.addIndexes.
|
||||
(Shai Erera)
|
||||
|
||||
* LUCENE-3514: Added IndexSearcher.searchAfter when Sort is used,
|
||||
returning results after a specified FieldDoc for deep
|
||||
paging. (Mike McCandless)
|
||||
|
||||
Optimizations
|
||||
|
||||
* LUCENE-2588: Don't store unnecessary suffixes when writing the terms
|
||||
|
|
|
@ -190,6 +190,10 @@ public abstract class FieldComparator<T> {
|
|||
}
|
||||
}
|
||||
|
||||
/** Returns negative result if the doc's value is less
|
||||
* than the provided value. */
|
||||
public abstract int compareDocToValue(int doc, T value) throws IOException;
|
||||
|
||||
public static abstract class NumericComparator<T extends Number> extends FieldComparator<T> {
|
||||
protected final T missingValue;
|
||||
protected final String field;
|
||||
|
@ -274,8 +278,18 @@ public abstract class FieldComparator<T> {
|
|||
public Byte value(int slot) {
|
||||
return Byte.valueOf(values[slot]);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareDocToValue(int doc, Byte value) {
|
||||
byte docValue = currentReaderValues[doc];
|
||||
// Test for docValue == 0 to save Bits.get method call for
|
||||
// the common case (doc has value and value is non-zero):
|
||||
if (docsWithField != null && docValue == 0 && !docsWithField.get(doc)) {
|
||||
docValue = missingValue;
|
||||
}
|
||||
return docValue - value.byteValue();
|
||||
}
|
||||
}
|
||||
|
||||
/** Parses field's values as double (using {@link
|
||||
* FieldCache#getDoubles} and sorts by ascending value */
|
||||
|
@ -351,6 +365,24 @@ public abstract class FieldComparator<T> {
|
|||
public Double value(int slot) {
|
||||
return Double.valueOf(values[slot]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareDocToValue(int doc, Double valueObj) {
|
||||
final double value = valueObj.doubleValue();
|
||||
double docValue = currentReaderValues[doc];
|
||||
// Test for docValue == 0 to save Bits.get method call for
|
||||
// the common case (doc has value and value is non-zero):
|
||||
if (docsWithField != null && docValue == 0 && !docsWithField.get(doc)) {
|
||||
docValue = missingValue;
|
||||
}
|
||||
if (docValue < value) {
|
||||
return -1;
|
||||
} else if (docValue > value) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Uses float index values to sort by ascending value */
|
||||
|
@ -415,6 +447,19 @@ public abstract class FieldComparator<T> {
|
|||
public Double value(int slot) {
|
||||
return Double.valueOf(values[slot]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareDocToValue(int doc, Double valueObj) {
|
||||
final double value = valueObj.doubleValue();
|
||||
final double docValue = currentReaderValues.getFloat(doc);
|
||||
if (docValue < value) {
|
||||
return -1;
|
||||
} else if (docValue > value) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Parses field's values as float (using {@link
|
||||
|
@ -494,6 +539,24 @@ public abstract class FieldComparator<T> {
|
|||
public Float value(int slot) {
|
||||
return Float.valueOf(values[slot]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareDocToValue(int doc, Float valueObj) {
|
||||
final float value = valueObj.floatValue();
|
||||
float docValue = currentReaderValues[doc];
|
||||
// Test for docValue == 0 to save Bits.get method call for
|
||||
// the common case (doc has value and value is non-zero):
|
||||
if (docsWithField != null && docValue == 0 && !docsWithField.get(doc)) {
|
||||
docValue = missingValue;
|
||||
}
|
||||
if (docValue < value) {
|
||||
return -1;
|
||||
} else if (docValue > value) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Parses field's values as short (using {@link
|
||||
|
@ -556,6 +619,18 @@ public abstract class FieldComparator<T> {
|
|||
public Short value(int slot) {
|
||||
return Short.valueOf(values[slot]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareDocToValue(int doc, Short valueObj) {
|
||||
final short value = valueObj.shortValue();
|
||||
short docValue = currentReaderValues[doc];
|
||||
// Test for docValue == 0 to save Bits.get method call for
|
||||
// the common case (doc has value and value is non-zero):
|
||||
if (docsWithField != null && docValue == 0 && !docsWithField.get(doc)) {
|
||||
docValue = missingValue;
|
||||
}
|
||||
return docValue - value;
|
||||
}
|
||||
}
|
||||
|
||||
/** Parses field's values as int (using {@link
|
||||
|
@ -640,6 +715,24 @@ public abstract class FieldComparator<T> {
|
|||
public Integer value(int slot) {
|
||||
return Integer.valueOf(values[slot]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareDocToValue(int doc, Integer valueObj) {
|
||||
final int value = valueObj.intValue();
|
||||
int docValue = currentReaderValues[doc];
|
||||
// Test for docValue == 0 to save Bits.get method call for
|
||||
// the common case (doc has value and value is non-zero):
|
||||
if (docsWithField != null && docValue == 0 && !docsWithField.get(doc)) {
|
||||
docValue = missingValue;
|
||||
}
|
||||
if (docValue < value) {
|
||||
return -1;
|
||||
} else if (docValue > value) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Loads int index values and sorts by ascending value. */
|
||||
|
@ -708,6 +801,19 @@ public abstract class FieldComparator<T> {
|
|||
public Long value(int slot) {
|
||||
return Long.valueOf(values[slot]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareDocToValue(int doc, Long valueObj) {
|
||||
final long value = valueObj.longValue();
|
||||
final long docValue = currentReaderValues.getInt(doc);
|
||||
if (docValue < value) {
|
||||
return -1;
|
||||
} else if (docValue > value) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Parses field's values as long (using {@link
|
||||
|
@ -788,6 +894,24 @@ public abstract class FieldComparator<T> {
|
|||
public Long value(int slot) {
|
||||
return Long.valueOf(values[slot]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareDocToValue(int doc, Long valueObj) {
|
||||
final long value = valueObj.longValue();
|
||||
long docValue = currentReaderValues[doc];
|
||||
// Test for docValue == 0 to save Bits.get method call for
|
||||
// the common case (doc has value and value is non-zero):
|
||||
if (docsWithField != null && docValue == 0 && !docsWithField.get(doc)) {
|
||||
docValue = missingValue;
|
||||
}
|
||||
if (docValue < value) {
|
||||
return -1;
|
||||
} else if (docValue > value) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Sorts by descending relevance. NOTE: if you are
|
||||
|
@ -815,12 +939,14 @@ public abstract class FieldComparator<T> {
|
|||
@Override
|
||||
public int compareBottom(int doc) throws IOException {
|
||||
float score = scorer.score();
|
||||
assert !Float.isNaN(score);
|
||||
return bottom > score ? -1 : (bottom < score ? 1 : 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void copy(int slot, int doc) throws IOException {
|
||||
scores[slot] = scorer.score();
|
||||
assert !Float.isNaN(scores[slot]);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -857,6 +983,22 @@ public abstract class FieldComparator<T> {
|
|||
// sorts descending:
|
||||
return second.compareTo(first);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareDocToValue(int doc, Float valueObj) throws IOException {
|
||||
final float value = valueObj.floatValue();
|
||||
float docValue = scorer.score();
|
||||
assert !Float.isNaN(docValue);
|
||||
if (docValue < value) {
|
||||
// reverse of FloatComparator
|
||||
return 1;
|
||||
} else if (docValue > value) {
|
||||
// reverse of FloatComparator
|
||||
return -1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Sorts by ascending docID */
|
||||
|
@ -904,6 +1046,19 @@ public abstract class FieldComparator<T> {
|
|||
public Integer value(int slot) {
|
||||
return Integer.valueOf(docIDs[slot]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareDocToValue(int doc, Integer valueObj) {
|
||||
final int value = valueObj.intValue();
|
||||
int docValue = docBase + doc;
|
||||
if (docValue < value) {
|
||||
return -1;
|
||||
} else if (docValue > value) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Sorts by field's natural Term sort order, using
|
||||
|
@ -998,6 +1153,20 @@ public abstract class FieldComparator<T> {
|
|||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareDocToValue(int doc, BytesRef value) {
|
||||
BytesRef docValue = termsIndex.getTerm(doc, tempBR);
|
||||
if (docValue == null) {
|
||||
if (value == null) {
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
} else if (value == null) {
|
||||
return 1;
|
||||
}
|
||||
return docValue.compareTo(value);
|
||||
}
|
||||
|
||||
/** Base class for specialized (per bit width of the
|
||||
* ords) per-segment comparator. NOTE: this is messy;
|
||||
* we do this only because hotspot can't reliably inline
|
||||
|
@ -1038,6 +1207,11 @@ public abstract class FieldComparator<T> {
|
|||
}
|
||||
return val1.compareTo(val2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareDocToValue(int doc, BytesRef value) {
|
||||
return TermOrdValComparator.this.compareDocToValue(doc, value);
|
||||
}
|
||||
}
|
||||
|
||||
// Used per-segment when bit width of doc->ord is 8:
|
||||
|
@ -1385,6 +1559,11 @@ public abstract class FieldComparator<T> {
|
|||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareDocToValue(int doc, BytesRef value) {
|
||||
return termsIndex.getBytes(doc, tempBR).compareTo(value);
|
||||
}
|
||||
|
||||
// TODO: would be nice to share these specialized impls
|
||||
// w/ TermOrdValComparator
|
||||
|
||||
|
@ -1422,6 +1601,11 @@ public abstract class FieldComparator<T> {
|
|||
assert val2 != null;
|
||||
return comp.compare(val1, val2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareDocToValue(int doc, BytesRef value) {
|
||||
return TermOrdValDocValuesComparator.this.compareDocToValue(doc, value);
|
||||
}
|
||||
}
|
||||
|
||||
// Used per-segment when bit width of doc->ord is 8:
|
||||
|
@ -1801,6 +1985,11 @@ public abstract class FieldComparator<T> {
|
|||
}
|
||||
return val1.compareTo(val2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareDocToValue(int doc, BytesRef value) {
|
||||
return docTerms.getTerm(doc, tempBR).compareTo(value);
|
||||
}
|
||||
}
|
||||
|
||||
/** Sorts by field's natural Term sort order. All
|
||||
|
@ -1869,6 +2058,11 @@ public abstract class FieldComparator<T> {
|
|||
assert val2 != null;
|
||||
return val1.compareTo(val2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareDocToValue(int doc, BytesRef value) {
|
||||
return docTerms.getBytes(doc, tempBR).compareTo(value);
|
||||
}
|
||||
}
|
||||
|
||||
final protected static int binarySearch(BytesRef br, DocTermsIndex a, BytesRef key) {
|
||||
|
|
|
@ -304,14 +304,51 @@ public class IndexSearcher {
|
|||
* <code>sort</code>.
|
||||
*
|
||||
* <p>NOTE: this does not compute scores by default; use
|
||||
* {@link IndexSearcher#setDefaultFieldSortScoring} to
|
||||
* enable scoring.
|
||||
* {@link IndexSearcher#search(Query,Filter,int,Sort,boolean,boolean)} to
|
||||
* control scoring.
|
||||
*
|
||||
* @throws BooleanQuery.TooManyClauses
|
||||
*/
|
||||
public TopFieldDocs search(Query query, Filter filter, int n,
|
||||
Sort sort) throws IOException {
|
||||
return search(createNormalizedWeight(wrapFilter(query, filter)), n, sort);
|
||||
return search(createNormalizedWeight(wrapFilter(query, filter)), n, sort, false, false);
|
||||
}
|
||||
|
||||
/** Search implementation with arbitrary sorting, plus
|
||||
* control over whether hit scores and max score
|
||||
* should be computed. Finds
|
||||
* the top <code>n</code> hits for <code>query</code>, applying
|
||||
* <code>filter</code> if non-null, and sorting the hits by the criteria in
|
||||
* <code>sort</code>. If <code>doDocScores</code> is <code>true</code>
|
||||
* then the score of each hit will be computed and
|
||||
* returned. If <code>doMaxScore</code> is
|
||||
* <code>true</code> then the maximum score over all
|
||||
* collected hits will be computed.
|
||||
*
|
||||
* @throws BooleanQuery.TooManyClauses
|
||||
*/
|
||||
public TopFieldDocs search(Query query, Filter filter, int n,
|
||||
Sort sort, boolean doDocScores, boolean doMaxScore) throws IOException {
|
||||
return search(createNormalizedWeight(wrapFilter(query, filter)), n, sort, doDocScores, doMaxScore);
|
||||
}
|
||||
|
||||
/** Finds the top <code>n</code>
|
||||
* hits for <code>query</code>, applying <code>filter</code> if non-null,
|
||||
* where all results are after a previous result (<code>after</code>).
|
||||
* <p>
|
||||
* By passing the bottom result from a previous page as <code>after</code>,
|
||||
* this method can be used for efficient 'deep-paging' across potentially
|
||||
* large result sets.
|
||||
*
|
||||
* @throws BooleanQuery.TooManyClauses
|
||||
*/
|
||||
public TopDocs searchAfter(ScoreDoc after, Query query, Filter filter, int n, Sort sort) throws IOException {
|
||||
if (after != null && !(after instanceof FieldDoc)) {
|
||||
// TODO: if we fix type safety of TopFieldDocs we can
|
||||
// remove this
|
||||
throw new IllegalArgumentException("after must be a FieldDoc; got " + after);
|
||||
}
|
||||
return search(createNormalizedWeight(wrapFilter(query, filter)), (FieldDoc) after, n, sort, true, false, false);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -324,7 +361,52 @@ public class IndexSearcher {
|
|||
*/
|
||||
public TopFieldDocs search(Query query, int n,
|
||||
Sort sort) throws IOException {
|
||||
return search(createNormalizedWeight(query), n, sort);
|
||||
return search(createNormalizedWeight(query), n, sort, false, false);
|
||||
}
|
||||
|
||||
/** Finds the top <code>n</code>
|
||||
* hits for <code>query</code> where all results are after a previous
|
||||
* result (<code>after</code>).
|
||||
* <p>
|
||||
* By passing the bottom result from a previous page as <code>after</code>,
|
||||
* this method can be used for efficient 'deep-paging' across potentially
|
||||
* large result sets.
|
||||
*
|
||||
* @throws BooleanQuery.TooManyClauses
|
||||
*/
|
||||
public TopDocs searchAfter(ScoreDoc after, Query query, int n, Sort sort) throws IOException {
|
||||
if (after != null && !(after instanceof FieldDoc)) {
|
||||
// TODO: if we fix type safety of TopFieldDocs we can
|
||||
// remove this
|
||||
throw new IllegalArgumentException("after must be a FieldDoc; got " + after);
|
||||
}
|
||||
return search(createNormalizedWeight(query), (FieldDoc) after, n, sort, true, false, false);
|
||||
}
|
||||
|
||||
/** Finds the top <code>n</code>
|
||||
* hits for <code>query</code> where all results are after a previous
|
||||
* result (<code>after</code>), allowing control over
|
||||
* whether hit scores and max score should be computed.
|
||||
* <p>
|
||||
* By passing the bottom result from a previous page as <code>after</code>,
|
||||
* this method can be used for efficient 'deep-paging' across potentially
|
||||
* large result sets. If <code>doDocScores</code> is <code>true</code>
|
||||
* then the score of each hit will be computed and
|
||||
* returned. If <code>doMaxScore</code> is
|
||||
* <code>true</code> then the maximum score over all
|
||||
* collected hits will be computed.
|
||||
*
|
||||
* @throws BooleanQuery.TooManyClauses
|
||||
*/
|
||||
public TopDocs searchAfter(ScoreDoc after, Query query, Filter filter, int n, Sort sort,
|
||||
boolean doDocScores, boolean doMaxScore) throws IOException {
|
||||
if (after != null && !(after instanceof FieldDoc)) {
|
||||
// TODO: if we fix type safety of TopFieldDocs we can
|
||||
// remove this
|
||||
throw new IllegalArgumentException("after must be a FieldDoc; got " + after);
|
||||
}
|
||||
return search(createNormalizedWeight(wrapFilter(query, filter)), (FieldDoc) after, n, sort, true,
|
||||
doDocScores, doMaxScore);
|
||||
}
|
||||
|
||||
/** Expert: Low-level search implementation. Finds the top <code>n</code>
|
||||
|
@ -383,7 +465,9 @@ public class IndexSearcher {
|
|||
return collector.topDocs();
|
||||
}
|
||||
|
||||
/** Expert: Low-level search implementation with arbitrary sorting. Finds
|
||||
/** Expert: Low-level search implementation with arbitrary
|
||||
* sorting and control over whether hit scores and max
|
||||
* score should be computed. Finds
|
||||
* the top <code>n</code> hits for <code>query</code> and sorting the hits
|
||||
* by the criteria in <code>sort</code>.
|
||||
*
|
||||
|
@ -393,12 +477,13 @@ public class IndexSearcher {
|
|||
* @throws BooleanQuery.TooManyClauses
|
||||
*/
|
||||
protected TopFieldDocs search(Weight weight,
|
||||
final int nDocs, Sort sort) throws IOException {
|
||||
return search(weight, nDocs, sort, true);
|
||||
final int nDocs, Sort sort,
|
||||
boolean doDocScores, boolean doMaxScore) throws IOException {
|
||||
return search(weight, null, nDocs, sort, true, doDocScores, doMaxScore);
|
||||
}
|
||||
|
||||
/**
|
||||
* Just like {@link #search(Weight, int, Sort)}, but you choose
|
||||
* Just like {@link #search(Weight, int, Sort, boolean, boolean)}, but you choose
|
||||
* whether or not the fields in the returned {@link FieldDoc} instances should
|
||||
* be set by specifying fillFields.
|
||||
*
|
||||
|
@ -408,27 +493,29 @@ public class IndexSearcher {
|
|||
* then pass that to {@link #search(AtomicReaderContext[], Weight,
|
||||
* Collector)}.</p>
|
||||
*/
|
||||
protected TopFieldDocs search(Weight weight, int nDocs,
|
||||
Sort sort, boolean fillFields)
|
||||
protected TopFieldDocs search(Weight weight, FieldDoc after, int nDocs,
|
||||
Sort sort, boolean fillFields,
|
||||
boolean doDocScores, boolean doMaxScore)
|
||||
throws IOException {
|
||||
|
||||
if (sort == null) throw new NullPointerException();
|
||||
|
||||
if (executor == null) {
|
||||
// use all leaves here!
|
||||
return search (leafContexts, weight, nDocs, sort, fillFields);
|
||||
return search(leafContexts, weight, after, nDocs, sort, fillFields, doDocScores, doMaxScore);
|
||||
} else {
|
||||
final TopFieldCollector topCollector = TopFieldCollector.create(sort, nDocs,
|
||||
after,
|
||||
fillFields,
|
||||
fieldSortDoTrackScores,
|
||||
fieldSortDoMaxScore,
|
||||
doDocScores,
|
||||
doMaxScore,
|
||||
false);
|
||||
|
||||
final Lock lock = new ReentrantLock();
|
||||
final ExecutionHelper<TopFieldDocs> runner = new ExecutionHelper<TopFieldDocs>(executor);
|
||||
for (int i = 0; i < leafSlices.length; i++) { // search each leaf slice
|
||||
runner.submit(
|
||||
new SearcherCallableWithSort(lock, this, leafSlices[i], weight, nDocs, topCollector, sort));
|
||||
new SearcherCallableWithSort(lock, this, leafSlices[i], weight, after, nDocs, topCollector, sort, doDocScores, doMaxScore));
|
||||
}
|
||||
int totalHits = 0;
|
||||
float maxScore = Float.NEGATIVE_INFINITY;
|
||||
|
@ -447,18 +534,12 @@ public class IndexSearcher {
|
|||
|
||||
|
||||
/**
|
||||
* Just like {@link #search(Weight, int, Sort)}, but you choose
|
||||
* Just like {@link #search(Weight, int, Sort, boolean, boolean)}, but you choose
|
||||
* whether or not the fields in the returned {@link FieldDoc} instances should
|
||||
* be set by specifying fillFields.
|
||||
*
|
||||
* <p>NOTE: this does not compute scores by default. If you
|
||||
* need scores, create a {@link TopFieldCollector}
|
||||
* instance by calling {@link TopFieldCollector#create} and
|
||||
* then pass that to {@link #search(AtomicReaderContext[], Weight,
|
||||
* Collector)}.</p>
|
||||
*/
|
||||
protected TopFieldDocs search(AtomicReaderContext[] leaves, Weight weight, int nDocs,
|
||||
Sort sort, boolean fillFields) throws IOException {
|
||||
protected TopFieldDocs search(AtomicReaderContext[] leaves, Weight weight, FieldDoc after, int nDocs,
|
||||
Sort sort, boolean fillFields, boolean doDocScores, boolean doMaxScore) throws IOException {
|
||||
// single thread
|
||||
int limit = reader.maxDoc();
|
||||
if (limit == 0) {
|
||||
|
@ -466,8 +547,9 @@ public class IndexSearcher {
|
|||
}
|
||||
nDocs = Math.min(nDocs, limit);
|
||||
|
||||
TopFieldCollector collector = TopFieldCollector.create(sort, nDocs,
|
||||
fillFields, fieldSortDoTrackScores, fieldSortDoMaxScore, !weight.scoresDocsOutOfOrder());
|
||||
TopFieldCollector collector = TopFieldCollector.create(sort, nDocs, after,
|
||||
fillFields, doDocScores,
|
||||
doMaxScore, !weight.scoresDocsOutOfOrder());
|
||||
search(leaves, weight, collector);
|
||||
return (TopFieldDocs) collector.topDocs();
|
||||
}
|
||||
|
@ -553,26 +635,6 @@ public class IndexSearcher {
|
|||
return weight.explain(leafContexts[n], deBasedDoc);
|
||||
}
|
||||
|
||||
private boolean fieldSortDoTrackScores;
|
||||
private boolean fieldSortDoMaxScore;
|
||||
|
||||
/** By default, no scores are computed when sorting by
|
||||
* field (using {@link #search(Query,Filter,int,Sort)}).
|
||||
* You can change that, per IndexSearcher instance, by
|
||||
* calling this method. Note that this will incur a CPU
|
||||
* cost.
|
||||
*
|
||||
* @param doTrackScores If true, then scores are
|
||||
* returned for every matching document in {@link
|
||||
* TopFieldDocs}.
|
||||
*
|
||||
* @param doMaxScore If true, then the max score for all
|
||||
* matching docs is computed. */
|
||||
public void setDefaultFieldSortScoring(boolean doTrackScores, boolean doMaxScore) {
|
||||
fieldSortDoTrackScores = doTrackScores;
|
||||
fieldSortDoMaxScore = doMaxScore;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a normalized weight for a top-level {@link Query}.
|
||||
* The query is rewritten by this method and {@link Query#createWeight} called,
|
||||
|
@ -626,7 +688,7 @@ public class IndexSearcher {
|
|||
}
|
||||
|
||||
public TopDocs call() throws IOException {
|
||||
final TopDocs docs = searcher.search (slice.leaves, weight, after, nDocs);
|
||||
final TopDocs docs = searcher.search(slice.leaves, weight, after, nDocs);
|
||||
final ScoreDoc[] scoreDocs = docs.scoreDocs;
|
||||
//it would be so nice if we had a thread-safe insert
|
||||
lock.lock();
|
||||
|
@ -657,9 +719,13 @@ public class IndexSearcher {
|
|||
private final TopFieldCollector hq;
|
||||
private final Sort sort;
|
||||
private final LeafSlice slice;
|
||||
private final FieldDoc after;
|
||||
private final boolean doDocScores;
|
||||
private final boolean doMaxScore;
|
||||
|
||||
public SearcherCallableWithSort(Lock lock, IndexSearcher searcher, LeafSlice slice, Weight weight,
|
||||
int nDocs, TopFieldCollector hq, Sort sort) {
|
||||
FieldDoc after, int nDocs, TopFieldCollector hq, Sort sort,
|
||||
boolean doDocScores, boolean doMaxScore) {
|
||||
this.lock = lock;
|
||||
this.searcher = searcher;
|
||||
this.weight = weight;
|
||||
|
@ -667,6 +733,9 @@ public class IndexSearcher {
|
|||
this.hq = hq;
|
||||
this.sort = sort;
|
||||
this.slice = slice;
|
||||
this.after = after;
|
||||
this.doDocScores = doDocScores;
|
||||
this.doMaxScore = doMaxScore;
|
||||
}
|
||||
|
||||
private final class FakeScorer extends Scorer {
|
||||
|
@ -707,7 +776,7 @@ public class IndexSearcher {
|
|||
|
||||
public TopFieldDocs call() throws IOException {
|
||||
assert slice.leaves.length == 1;
|
||||
final TopFieldDocs docs = searcher.search (slice.leaves, weight, nDocs, sort, true);
|
||||
final TopFieldDocs docs = searcher.search(slice.leaves, weight, after, nDocs, sort, true, doDocScores, doMaxScore);
|
||||
lock.lock();
|
||||
try {
|
||||
final int base = slice.leaves[0].docBase;
|
||||
|
@ -718,6 +787,11 @@ public class IndexSearcher {
|
|||
fakeScorer.score = scoreDoc.score;
|
||||
hq.collect(scoreDoc.doc-base);
|
||||
}
|
||||
|
||||
// Carry over maxScore from sub:
|
||||
if (doMaxScore && docs.getMaxScore() > hq.maxScore) {
|
||||
hq.maxScore = docs.getMaxScore();
|
||||
}
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
|
|
|
@ -45,7 +45,7 @@ public class TopDocs {
|
|||
|
||||
/** Sets the maximum score value encountered. */
|
||||
public void setMaxScore(float maxScore) {
|
||||
this.maxScore=maxScore;
|
||||
this.maxScore = maxScore;
|
||||
}
|
||||
|
||||
/** Constructs a TopDocs with a default maxScore=Float.NaN. */
|
||||
|
|
|
@ -843,6 +843,166 @@ public abstract class TopFieldCollector extends TopDocsCollector<Entry> {
|
|||
|
||||
}
|
||||
|
||||
/*
|
||||
* Implements a TopFieldCollector when after != null.
|
||||
*/
|
||||
private final static class PagingFieldCollector extends TopFieldCollector {
|
||||
|
||||
Scorer scorer;
|
||||
int collectedHits;
|
||||
final FieldComparator<?>[] comparators;
|
||||
final int[] reverseMul;
|
||||
final FieldValueHitQueue<Entry> queue;
|
||||
final boolean trackDocScores;
|
||||
final boolean trackMaxScore;
|
||||
final FieldDoc after;
|
||||
int afterDoc;
|
||||
|
||||
public PagingFieldCollector(
|
||||
FieldValueHitQueue<Entry> queue, FieldDoc after, int numHits, boolean fillFields,
|
||||
boolean trackDocScores, boolean trackMaxScore)
|
||||
throws IOException {
|
||||
super(queue, numHits, fillFields);
|
||||
this.queue = queue;
|
||||
this.trackDocScores = trackDocScores;
|
||||
this.trackMaxScore = trackMaxScore;
|
||||
this.after = after;
|
||||
comparators = queue.getComparators();
|
||||
reverseMul = queue.getReverseMul();
|
||||
|
||||
// Must set maxScore to NEG_INF, or otherwise Math.max always returns NaN.
|
||||
maxScore = Float.NEGATIVE_INFINITY;
|
||||
}
|
||||
|
||||
void updateBottom(int doc, float score) {
|
||||
bottom.doc = docBase + doc;
|
||||
bottom.score = score;
|
||||
bottom = pq.updateTop();
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
@Override
|
||||
public void collect(int doc) throws IOException {
|
||||
totalHits++;
|
||||
|
||||
//System.out.println(" collect doc=" + doc);
|
||||
|
||||
// Check if this hit was already collected on a
|
||||
// previous page:
|
||||
boolean sameValues = true;
|
||||
for(int compIDX=0;compIDX<comparators.length;compIDX++) {
|
||||
final FieldComparator comp = comparators[compIDX];
|
||||
|
||||
final int cmp = reverseMul[compIDX] * comp.compareDocToValue(doc, after.fields[compIDX]);
|
||||
if (cmp < 0) {
|
||||
// Already collected on a previous page
|
||||
//System.out.println(" skip: before");
|
||||
return;
|
||||
} else if (cmp > 0) {
|
||||
// Not yet collected
|
||||
sameValues = false;
|
||||
//System.out.println(" keep: after");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Tie-break by docID:
|
||||
if (sameValues && doc <= afterDoc) {
|
||||
// Already collected on a previous page
|
||||
//System.out.println(" skip: tie-break");
|
||||
return;
|
||||
}
|
||||
|
||||
collectedHits++;
|
||||
|
||||
float score = Float.NaN;
|
||||
if (trackMaxScore) {
|
||||
score = scorer.score();
|
||||
if (score > maxScore) {
|
||||
maxScore = score;
|
||||
}
|
||||
}
|
||||
|
||||
if (queueFull) {
|
||||
// Fastmatch: return if this hit is not competitive
|
||||
for (int i = 0;; i++) {
|
||||
final int c = reverseMul[i] * comparators[i].compareBottom(doc);
|
||||
if (c < 0) {
|
||||
// Definitely not competitive.
|
||||
return;
|
||||
} else if (c > 0) {
|
||||
// Definitely competitive.
|
||||
break;
|
||||
} else if (i == comparators.length - 1) {
|
||||
// This is the equals case.
|
||||
if (doc + docBase > bottom.doc) {
|
||||
// Definitely not competitive
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// This hit is competitive - replace bottom element in queue & adjustTop
|
||||
for (int i = 0; i < comparators.length; i++) {
|
||||
comparators[i].copy(bottom.slot, doc);
|
||||
}
|
||||
|
||||
// Compute score only if it is competitive.
|
||||
if (trackDocScores && !trackMaxScore) {
|
||||
score = scorer.score();
|
||||
}
|
||||
updateBottom(doc, score);
|
||||
|
||||
for (int i = 0; i < comparators.length; i++) {
|
||||
comparators[i].setBottom(bottom.slot);
|
||||
}
|
||||
} else {
|
||||
// Startup transient: queue hasn't gathered numHits yet
|
||||
final int slot = collectedHits - 1;
|
||||
//System.out.println(" slot=" + slot);
|
||||
// Copy hit into queue
|
||||
for (int i = 0; i < comparators.length; i++) {
|
||||
comparators[i].copy(slot, doc);
|
||||
}
|
||||
|
||||
// Compute score only if it is competitive.
|
||||
if (trackDocScores && !trackMaxScore) {
|
||||
score = scorer.score();
|
||||
}
|
||||
bottom = pq.add(new Entry(slot, docBase + doc, score));
|
||||
queueFull = collectedHits == numHits;
|
||||
if (queueFull) {
|
||||
for (int i = 0; i < comparators.length; i++) {
|
||||
comparators[i].setBottom(bottom.slot);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setScorer(Scorer scorer) throws IOException {
|
||||
this.scorer = scorer;
|
||||
for (int i = 0; i < comparators.length; i++) {
|
||||
comparators[i].setScorer(scorer);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean acceptsDocsOutOfOrder() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNextReader(AtomicReaderContext context) throws IOException {
|
||||
docBase = context.docBase;
|
||||
afterDoc = after.doc - docBase;
|
||||
for (int i = 0; i < comparators.length; i++) {
|
||||
queue.setComparator(i, comparators[i].setNextReader(context));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final ScoreDoc[] EMPTY_SCOREDOCS = new ScoreDoc[0];
|
||||
|
||||
private final boolean fillFields;
|
||||
|
@ -909,6 +1069,52 @@ public abstract class TopFieldCollector extends TopDocsCollector<Entry> {
|
|||
boolean fillFields, boolean trackDocScores, boolean trackMaxScore,
|
||||
boolean docsScoredInOrder)
|
||||
throws IOException {
|
||||
return create(sort, numHits, null, fillFields, trackDocScores, trackMaxScore, docsScoredInOrder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link TopFieldCollector} from the given
|
||||
* arguments.
|
||||
*
|
||||
* <p><b>NOTE</b>: The instances returned by this method
|
||||
* pre-allocate a full array of length
|
||||
* <code>numHits</code>.
|
||||
*
|
||||
* @param sort
|
||||
* the sort criteria (SortFields).
|
||||
* @param numHits
|
||||
* the number of results to collect.
|
||||
* @param after
|
||||
* only hits after this FieldDoc will be collected
|
||||
* @param fillFields
|
||||
* specifies whether the actual field values should be returned on
|
||||
* the results (FieldDoc).
|
||||
* @param trackDocScores
|
||||
* specifies whether document scores should be tracked and set on the
|
||||
* results. Note that if set to false, then the results' scores will
|
||||
* be set to Float.NaN. Setting this to true affects performance, as
|
||||
* it incurs the score computation on each competitive result.
|
||||
* Therefore if document scores are not required by the application,
|
||||
* it is recommended to set it to false.
|
||||
* @param trackMaxScore
|
||||
* specifies whether the query's maxScore should be tracked and set
|
||||
* on the resulting {@link TopDocs}. Note that if set to false,
|
||||
* {@link TopDocs#getMaxScore()} returns Float.NaN. Setting this to
|
||||
* true affects performance as it incurs the score computation on
|
||||
* each result. Also, setting this true automatically sets
|
||||
* <code>trackDocScores</code> to true as well.
|
||||
* @param docsScoredInOrder
|
||||
* specifies whether documents are scored in doc Id order or not by
|
||||
* the given {@link Scorer} in {@link #setScorer(Scorer)}.
|
||||
* @return a {@link TopFieldCollector} instance which will sort the results by
|
||||
* the sort criteria.
|
||||
* @throws IOException
|
||||
*/
|
||||
public static TopFieldCollector create(Sort sort, int numHits, FieldDoc after,
|
||||
boolean fillFields, boolean trackDocScores, boolean trackMaxScore,
|
||||
boolean docsScoredInOrder)
|
||||
throws IOException {
|
||||
|
||||
if (sort.fields.length == 0) {
|
||||
throw new IllegalArgumentException("Sort must contain at least one field");
|
||||
}
|
||||
|
@ -918,43 +1124,56 @@ public abstract class TopFieldCollector extends TopDocsCollector<Entry> {
|
|||
}
|
||||
|
||||
FieldValueHitQueue<Entry> queue = FieldValueHitQueue.create(sort.fields, numHits);
|
||||
if (queue.getComparators().length == 1) {
|
||||
|
||||
if (after == null) {
|
||||
if (queue.getComparators().length == 1) {
|
||||
if (docsScoredInOrder) {
|
||||
if (trackMaxScore) {
|
||||
return new OneComparatorScoringMaxScoreCollector(queue, numHits, fillFields);
|
||||
} else if (trackDocScores) {
|
||||
return new OneComparatorScoringNoMaxScoreCollector(queue, numHits, fillFields);
|
||||
} else {
|
||||
return new OneComparatorNonScoringCollector(queue, numHits, fillFields);
|
||||
}
|
||||
} else {
|
||||
if (trackMaxScore) {
|
||||
return new OutOfOrderOneComparatorScoringMaxScoreCollector(queue, numHits, fillFields);
|
||||
} else if (trackDocScores) {
|
||||
return new OutOfOrderOneComparatorScoringNoMaxScoreCollector(queue, numHits, fillFields);
|
||||
} else {
|
||||
return new OutOfOrderOneComparatorNonScoringCollector(queue, numHits, fillFields);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// multiple comparators.
|
||||
if (docsScoredInOrder) {
|
||||
if (trackMaxScore) {
|
||||
return new OneComparatorScoringMaxScoreCollector(queue, numHits, fillFields);
|
||||
return new MultiComparatorScoringMaxScoreCollector(queue, numHits, fillFields);
|
||||
} else if (trackDocScores) {
|
||||
return new OneComparatorScoringNoMaxScoreCollector(queue, numHits, fillFields);
|
||||
return new MultiComparatorScoringNoMaxScoreCollector(queue, numHits, fillFields);
|
||||
} else {
|
||||
return new OneComparatorNonScoringCollector(queue, numHits, fillFields);
|
||||
return new MultiComparatorNonScoringCollector(queue, numHits, fillFields);
|
||||
}
|
||||
} else {
|
||||
if (trackMaxScore) {
|
||||
return new OutOfOrderOneComparatorScoringMaxScoreCollector(queue, numHits, fillFields);
|
||||
return new OutOfOrderMultiComparatorScoringMaxScoreCollector(queue, numHits, fillFields);
|
||||
} else if (trackDocScores) {
|
||||
return new OutOfOrderOneComparatorScoringNoMaxScoreCollector(queue, numHits, fillFields);
|
||||
return new OutOfOrderMultiComparatorScoringNoMaxScoreCollector(queue, numHits, fillFields);
|
||||
} else {
|
||||
return new OutOfOrderOneComparatorNonScoringCollector(queue, numHits, fillFields);
|
||||
return new OutOfOrderMultiComparatorNonScoringCollector(queue, numHits, fillFields);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// multiple comparators.
|
||||
if (docsScoredInOrder) {
|
||||
if (trackMaxScore) {
|
||||
return new MultiComparatorScoringMaxScoreCollector(queue, numHits, fillFields);
|
||||
} else if (trackDocScores) {
|
||||
return new MultiComparatorScoringNoMaxScoreCollector(queue, numHits, fillFields);
|
||||
} else {
|
||||
return new MultiComparatorNonScoringCollector(queue, numHits, fillFields);
|
||||
}
|
||||
} else {
|
||||
if (trackMaxScore) {
|
||||
return new OutOfOrderMultiComparatorScoringMaxScoreCollector(queue, numHits, fillFields);
|
||||
} else if (trackDocScores) {
|
||||
return new OutOfOrderMultiComparatorScoringNoMaxScoreCollector(queue, numHits, fillFields);
|
||||
} else {
|
||||
return new OutOfOrderMultiComparatorNonScoringCollector(queue, numHits, fillFields);
|
||||
if (after.fields == null) {
|
||||
throw new IllegalArgumentException("after.fields wasn't set; you must pass fillFields=true for the previous search");
|
||||
}
|
||||
|
||||
if (after.fields.length != sort.getSort().length) {
|
||||
throw new IllegalArgumentException("after.fields has " + after.fields.length + " values but sort has " + sort.getSort().length);
|
||||
}
|
||||
|
||||
return new PagingFieldCollector(queue, after, numHits, fillFields, trackDocScores, trackMaxScore);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -21,20 +21,19 @@ package org.apache.lucene.search;
|
|||
/** Represents hits returned by {@link
|
||||
* IndexSearcher#search(Query,Filter,int,Sort)}.
|
||||
*/
|
||||
public class TopFieldDocs
|
||||
extends TopDocs {
|
||||
public class TopFieldDocs extends TopDocs {
|
||||
|
||||
/** The fields which were used to sort results by. */
|
||||
public SortField[] fields;
|
||||
/** The fields which were used to sort results by. */
|
||||
public SortField[] fields;
|
||||
|
||||
/** Creates one of these objects.
|
||||
* @param totalHits Total number of hits for the query.
|
||||
* @param scoreDocs The top hits for the query.
|
||||
* @param fields The sort criteria used to find the top hits.
|
||||
* @param maxScore The maximum score encountered.
|
||||
*/
|
||||
public TopFieldDocs (int totalHits, ScoreDoc[] scoreDocs, SortField[] fields, float maxScore) {
|
||||
super (totalHits, scoreDocs, maxScore);
|
||||
this.fields = fields;
|
||||
}
|
||||
/** Creates one of these objects.
|
||||
* @param totalHits Total number of hits for the query.
|
||||
* @param scoreDocs The top hits for the query.
|
||||
* @param fields The sort criteria used to find the top hits.
|
||||
* @param maxScore The maximum score encountered.
|
||||
*/
|
||||
public TopFieldDocs (int totalHits, ScoreDoc[] scoreDocs, SortField[] fields, float maxScore) {
|
||||
super (totalHits, scoreDocs, maxScore);
|
||||
this.fields = fields;
|
||||
}
|
||||
}
|
|
@ -139,6 +139,10 @@ final class JustCompileSearch {
|
|||
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareDocToValue(int doc, Object value) {
|
||||
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
|
||||
}
|
||||
}
|
||||
|
||||
static final class JustCompileFieldComparatorSource extends FieldComparatorSource {
|
||||
|
|
|
@ -188,6 +188,14 @@ class ElevationComparatorSource extends FieldComparatorSource {
|
|||
public Integer value(int slot) {
|
||||
return Integer.valueOf(values[slot]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareDocToValue(int doc, Integer valueObj) throws IOException {
|
||||
final int value = valueObj.intValue();
|
||||
final int docValue = docVal(doc);
|
||||
// values will be small enough that there is no overflow concern
|
||||
return value - docValue;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,12 +17,25 @@ package org.apache.lucene.search;
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.apache.lucene.codecs.Codec;
|
||||
import org.apache.lucene.document.Document;
|
||||
import org.apache.lucene.document.DoubleField;
|
||||
import org.apache.lucene.document.FloatDocValuesField;
|
||||
import org.apache.lucene.document.FloatField;
|
||||
import org.apache.lucene.document.IntDocValuesField;
|
||||
import org.apache.lucene.document.IntField;
|
||||
import org.apache.lucene.document.LongField;
|
||||
import org.apache.lucene.document.SortedBytesDocValuesField;
|
||||
import org.apache.lucene.document.StraightBytesDocValuesField;
|
||||
import org.apache.lucene.document.StringField;
|
||||
import org.apache.lucene.document.TextField;
|
||||
import org.apache.lucene.index.IndexReader;
|
||||
import org.apache.lucene.index.RandomIndexWriter;
|
||||
import org.apache.lucene.index.Term;
|
||||
import org.apache.lucene.store.Directory;
|
||||
import org.apache.lucene.util.BytesRef;
|
||||
import org.apache.lucene.util.English;
|
||||
import org.apache.lucene.util.LuceneTestCase;
|
||||
import org.apache.lucene.util._TestUtil;
|
||||
|
@ -30,11 +43,19 @@ import org.apache.lucene.util._TestUtil;
|
|||
/**
|
||||
* Tests IndexSearcher's searchAfter() method
|
||||
*/
|
||||
|
||||
public class TestSearchAfter extends LuceneTestCase {
|
||||
private Directory dir;
|
||||
private IndexReader reader;
|
||||
private IndexSearcher searcher;
|
||||
|
||||
boolean supportsDocValues = Codec.getDefault().getName().equals("Lucene3x") == false;
|
||||
|
||||
private static SortField useDocValues(SortField field) {
|
||||
field.setUseIndexValues(true);
|
||||
return field;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
|
@ -45,6 +66,25 @@ public class TestSearchAfter extends LuceneTestCase {
|
|||
Document document = new Document();
|
||||
document.add(newField("english", English.intToEnglish(i), TextField.TYPE_UNSTORED));
|
||||
document.add(newField("oddeven", (i % 2 == 0) ? "even" : "odd", TextField.TYPE_UNSTORED));
|
||||
document.add(newField("byte", "" + ((byte) random().nextInt()), StringField.TYPE_UNSTORED));
|
||||
document.add(newField("short", "" + ((short) random().nextInt()), StringField.TYPE_UNSTORED));
|
||||
document.add(new IntField("int", random().nextInt()));
|
||||
document.add(new LongField("long", random().nextLong()));
|
||||
|
||||
document.add(new FloatField("float", random().nextFloat()));
|
||||
document.add(new DoubleField("double", random().nextDouble()));
|
||||
document.add(newField("bytes", _TestUtil.randomRealisticUnicodeString(random()), StringField.TYPE_UNSTORED));
|
||||
document.add(newField("bytesval", _TestUtil.randomRealisticUnicodeString(random()), StringField.TYPE_UNSTORED));
|
||||
document.add(new DoubleField("double", random().nextDouble()));
|
||||
|
||||
if (supportsDocValues) {
|
||||
document.add(new IntDocValuesField("intdocvalues", random().nextInt()));
|
||||
document.add(new FloatDocValuesField("floatdocvalues", random().nextFloat()));
|
||||
document.add(new SortedBytesDocValuesField("sortedbytesdocvalues", new BytesRef(_TestUtil.randomRealisticUnicodeString(random()))));
|
||||
document.add(new SortedBytesDocValuesField("sortedbytesdocvaluesval", new BytesRef(_TestUtil.randomRealisticUnicodeString(random()))));
|
||||
document.add(new StraightBytesDocValuesField("straightbytesdocvalues", new BytesRef(_TestUtil.randomRealisticUnicodeString(random()))));
|
||||
}
|
||||
|
||||
iw.addDocument(document);
|
||||
}
|
||||
reader = iw.getReader();
|
||||
|
@ -63,7 +103,7 @@ public class TestSearchAfter extends LuceneTestCase {
|
|||
// because the first page has a null 'after', we get a normal collector.
|
||||
// so we need to run the test a few times to ensure we will collect multiple
|
||||
// pages.
|
||||
int n = atLeast(10);
|
||||
int n = atLeast(20);
|
||||
for (int i = 0; i < n; i++) {
|
||||
Filter odd = new QueryWrapperFilter(new TermQuery(new Term("oddeven", "odd")));
|
||||
assertQuery(new MatchAllDocsQuery(), null);
|
||||
|
@ -78,13 +118,67 @@ public class TestSearchAfter extends LuceneTestCase {
|
|||
}
|
||||
|
||||
void assertQuery(Query query, Filter filter) throws Exception {
|
||||
assertQuery(query, filter, null);
|
||||
assertQuery(query, filter, Sort.RELEVANCE);
|
||||
assertQuery(query, filter, Sort.INDEXORDER);
|
||||
for(int rev=0;rev<2;rev++) {
|
||||
boolean reversed = rev == 1;
|
||||
assertQuery(query, filter, new Sort(new SortField[] {new SortField("byte", SortField.Type.BYTE, reversed)}));
|
||||
assertQuery(query, filter, new Sort(new SortField[] {new SortField("short", SortField.Type.SHORT, reversed)}));
|
||||
assertQuery(query, filter, new Sort(new SortField[] {new SortField("int", SortField.Type.INT, reversed)}));
|
||||
assertQuery(query, filter, new Sort(new SortField[] {new SortField("long", SortField.Type.LONG, reversed)}));
|
||||
assertQuery(query, filter, new Sort(new SortField[] {new SortField("float", SortField.Type.FLOAT, reversed)}));
|
||||
assertQuery(query, filter, new Sort(new SortField[] {new SortField("double", SortField.Type.DOUBLE, reversed)}));
|
||||
assertQuery(query, filter, new Sort(new SortField[] {new SortField("bytes", SortField.Type.STRING, reversed)}));
|
||||
assertQuery(query, filter, new Sort(new SortField[] {new SortField("bytesval", SortField.Type.STRING_VAL, reversed)}));
|
||||
if (supportsDocValues) {
|
||||
assertQuery(query, filter, new Sort(new SortField[] {useDocValues(new SortField("intdocvalues", SortField.Type.INT, reversed))}));
|
||||
assertQuery(query, filter, new Sort(new SortField[] {useDocValues(new SortField("floatdocvalues", SortField.Type.FLOAT, reversed))}));
|
||||
assertQuery(query, filter, new Sort(new SortField[] {useDocValues(new SortField("sortedbytesdocvalues", SortField.Type.STRING, reversed))}));
|
||||
assertQuery(query, filter, new Sort(new SortField[] {useDocValues(new SortField("sortedbytesdocvaluesval", SortField.Type.STRING_VAL, reversed))}));
|
||||
assertQuery(query, filter, new Sort(new SortField[] {useDocValues(new SortField("straightbytesdocvalues", SortField.Type.STRING_VAL, reversed))}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void assertQuery(Query query, Filter filter, Sort sort) throws Exception {
|
||||
int maxDoc = searcher.getIndexReader().maxDoc();
|
||||
TopDocs all = searcher.search(query, filter, maxDoc);
|
||||
TopDocs all;
|
||||
int pageSize = _TestUtil.nextInt(random(), 1, maxDoc*2);
|
||||
if (VERBOSE) {
|
||||
System.out.println("\nassertQuery: query=" + query + " filter=" + filter + " sort=" + sort + " pageSize=" + pageSize);
|
||||
}
|
||||
final boolean doMaxScore = random().nextBoolean();
|
||||
if (sort == null) {
|
||||
all = searcher.search(query, filter, maxDoc);
|
||||
} else if (sort == Sort.RELEVANCE) {
|
||||
all = searcher.search(query, filter, maxDoc, sort, true, doMaxScore);
|
||||
} else {
|
||||
all = searcher.search(query, filter, maxDoc, sort);
|
||||
}
|
||||
if (VERBOSE) {
|
||||
System.out.println(" all.totalHits=" + all.totalHits);
|
||||
}
|
||||
int pageStart = 0;
|
||||
ScoreDoc lastBottom = null;
|
||||
while (pageStart < all.totalHits) {
|
||||
TopDocs paged = searcher.searchAfter(lastBottom, query, filter, pageSize);
|
||||
TopDocs paged;
|
||||
if (sort == null) {
|
||||
if (VERBOSE) {
|
||||
System.out.println(" iter lastBottom=" + lastBottom);
|
||||
}
|
||||
paged = searcher.searchAfter(lastBottom, query, filter, pageSize);
|
||||
} else {
|
||||
if (VERBOSE) {
|
||||
System.out.println(" iter lastBottom=" + lastBottom + (lastBottom == null ? "" : " fields=" + Arrays.toString(((FieldDoc) lastBottom).fields)));
|
||||
}
|
||||
if (sort == Sort.RELEVANCE) {
|
||||
paged = searcher.searchAfter(lastBottom, query, filter, pageSize, sort, true, doMaxScore);
|
||||
} else {
|
||||
paged = searcher.searchAfter(lastBottom, query, filter, pageSize, sort);
|
||||
}
|
||||
}
|
||||
|
||||
if (paged.scoreDocs.length == 0) {
|
||||
break;
|
||||
}
|
||||
|
@ -98,8 +192,14 @@ public class TestSearchAfter extends LuceneTestCase {
|
|||
static void assertPage(int pageStart, TopDocs all, TopDocs paged) {
|
||||
assertEquals(all.totalHits, paged.totalHits);
|
||||
for (int i = 0; i < paged.scoreDocs.length; i++) {
|
||||
assertEquals(all.scoreDocs[pageStart + i].doc, paged.scoreDocs[i].doc);
|
||||
assertEquals(all.scoreDocs[pageStart + i].score, paged.scoreDocs[i].score, 0f);
|
||||
ScoreDoc sd1 = all.scoreDocs[pageStart + i];
|
||||
ScoreDoc sd2 = paged.scoreDocs[i];
|
||||
assertEquals(sd1.doc, sd2.doc);
|
||||
assertEquals(sd1.score, sd2.score, 0f);
|
||||
if (sd1 instanceof FieldDoc) {
|
||||
assertTrue(sd2 instanceof FieldDoc);
|
||||
assertEquals(((FieldDoc) sd1).fields, ((FieldDoc) sd2).fields);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,9 +44,11 @@ import org.apache.lucene.document.StringField;
|
|||
import org.apache.lucene.document.TextField;
|
||||
import org.apache.lucene.index.AtomicReaderContext;
|
||||
import org.apache.lucene.index.CorruptIndexException;
|
||||
import org.apache.lucene.index.DirectoryReader;
|
||||
import org.apache.lucene.index.DocValues;
|
||||
import org.apache.lucene.index.IndexReader;
|
||||
import org.apache.lucene.index.IndexWriter;
|
||||
import org.apache.lucene.index.IndexWriterConfig;
|
||||
import org.apache.lucene.index.IndexableField;
|
||||
import org.apache.lucene.index.MultiReader;
|
||||
import org.apache.lucene.index.RandomIndexWriter;
|
||||
|
@ -218,7 +220,6 @@ public class TestSort extends LuceneTestCase {
|
|||
IndexReader reader = writer.getReader();
|
||||
writer.close ();
|
||||
IndexSearcher s = newSearcher(reader);
|
||||
s.setDefaultFieldSortScoring(true, true);
|
||||
return s;
|
||||
}
|
||||
|
||||
|
@ -734,6 +735,15 @@ public class TestSort extends LuceneTestCase {
|
|||
public Integer value(int slot) {
|
||||
return Integer.valueOf(slotValues[slot]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareDocToValue(int doc, Integer valueObj) {
|
||||
final int value = valueObj.intValue();
|
||||
final int docValue = docValues[doc];
|
||||
|
||||
// values are small enough that overflow won't happen
|
||||
return docValue - value;
|
||||
}
|
||||
}
|
||||
|
||||
static class MyFieldComparatorSource extends FieldComparatorSource {
|
||||
|
@ -889,7 +899,7 @@ public class TestSort extends LuceneTestCase {
|
|||
|
||||
// try to pick a query that will result in an unnormalized
|
||||
// score greater than 1 to test for correct normalization
|
||||
final TopDocs docs1 = full.search(queryE,null,nDocs,sort);
|
||||
final TopDocs docs1 = full.search(queryE,null,nDocs,sort,true,true);
|
||||
|
||||
// a filter that only allows through the first hit
|
||||
Filter filt = new Filter() {
|
||||
|
@ -903,7 +913,7 @@ public class TestSort extends LuceneTestCase {
|
|||
}
|
||||
};
|
||||
|
||||
TopDocs docs2 = full.search(queryE, filt, nDocs, sort);
|
||||
TopDocs docs2 = full.search(queryE, filt, nDocs, sort,true,true);
|
||||
|
||||
assertEquals(docs1.scoreDocs[0].score, docs2.scoreDocs[0].score, 1e-6);
|
||||
}
|
||||
|
@ -1244,7 +1254,7 @@ public class TestSort extends LuceneTestCase {
|
|||
String expectedResult) throws IOException {
|
||||
|
||||
//ScoreDoc[] result = searcher.search (query, null, 1000, sort).scoreDocs;
|
||||
TopDocs hits = searcher.search(query, null, Math.max(1, expectedResult.length()), sort);
|
||||
TopDocs hits = searcher.search(query, null, Math.max(1, expectedResult.length()), sort, true, true);
|
||||
ScoreDoc[] result = hits.scoreDocs;
|
||||
assertEquals(expectedResult.length(),hits.totalHits);
|
||||
StringBuilder buff = new StringBuilder(10);
|
||||
|
@ -1478,4 +1488,38 @@ public class TestSort extends LuceneTestCase {
|
|||
r.close();
|
||||
dir.close();
|
||||
}
|
||||
|
||||
public void testMaxScore() throws Exception {
|
||||
Directory d = newDirectory();
|
||||
// Not RIW because we need exactly 2 segs:
|
||||
IndexWriter w = new IndexWriter(d, new IndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(random())));
|
||||
int id = 0;
|
||||
for(int seg=0;seg<2;seg++) {
|
||||
for(int docIDX=0;docIDX<10;docIDX++) {
|
||||
Document doc = new Document();
|
||||
doc.add(newField("id", ""+docIDX, StringField.TYPE_STORED));
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for(int i=0;i<id;i++) {
|
||||
sb.append(' ');
|
||||
sb.append("text");
|
||||
}
|
||||
doc.add(newField("body", sb.toString(), TextField.TYPE_UNSTORED));
|
||||
w.addDocument(doc);
|
||||
id++;
|
||||
}
|
||||
w.commit();
|
||||
}
|
||||
|
||||
IndexReader r = DirectoryReader.open(w, true);
|
||||
w.close();
|
||||
Query q = new TermQuery(new Term("body", "text"));
|
||||
IndexSearcher s = newSearcher(r);
|
||||
float maxScore = s.search(q , 10).getMaxScore();
|
||||
assertEquals(maxScore, s.search(q, null, 3, Sort.INDEXORDER, random().nextBoolean(), true).getMaxScore(), 0.0);
|
||||
assertEquals(maxScore, s.search(q, null, 3, Sort.RELEVANCE, random().nextBoolean(), true).getMaxScore(), 0.0);
|
||||
assertEquals(maxScore, s.search(q, null, 3, new Sort(new SortField[] {new SortField("id", SortField.Type.INT, false)}), random().nextBoolean(), true).getMaxScore(), 0.0);
|
||||
assertEquals(maxScore, s.search(q, null, 3, new Sort(new SortField[] {new SortField("id", SortField.Type.INT, true)}), random().nextBoolean(), true).getMaxScore(), 0.0);
|
||||
r.close();
|
||||
d.close();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -444,7 +444,6 @@ public class TestBlockJoin extends LuceneTestCase {
|
|||
}
|
||||
|
||||
final IndexSearcher s = newSearcher(r);
|
||||
s.setDefaultFieldSortScoring(true, true);
|
||||
|
||||
final IndexSearcher joinS = newSearcher(joinR);
|
||||
|
||||
|
|
|
@ -64,7 +64,7 @@ public class CustomScoreQuery extends Query {
|
|||
* computation. This parameter is optional - it can be null.
|
||||
*/
|
||||
public CustomScoreQuery(Query subQuery, Query scoringQuery) {
|
||||
this(subQuery, scoringQuery!=null ? // don't want an array that contains a single null..
|
||||
this(subQuery, scoringQuery!=null ? // don't want an array that contains a single null..
|
||||
new Query[] {scoringQuery} : new Query[0]);
|
||||
}
|
||||
|
||||
|
|
|
@ -184,5 +184,18 @@ public abstract class ValueSource {
|
|||
public Double value(int slot) {
|
||||
return values[slot];
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareDocToValue(int doc, Double valueObj) {
|
||||
final double value = valueObj.doubleValue();
|
||||
final double docValue = docVals.doubleVal(doc);
|
||||
if (docValue < value) {
|
||||
return -1;
|
||||
} else if (docValue > value) {
|
||||
return -1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -132,7 +132,6 @@ public class TestValueSources extends LuceneTestCase {
|
|||
|
||||
reader = iw.getReader();
|
||||
searcher = newSearcher(reader);
|
||||
searcher.setDefaultFieldSortScoring(true, true);
|
||||
iw.close();
|
||||
}
|
||||
|
||||
|
|
|
@ -118,4 +118,16 @@ public final class SlowCollatedStringComparator extends FieldComparator<String>
|
|||
return collator.compare(first, second);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareDocToValue(int doc, String value) {
|
||||
final BytesRef br = currentDocTerms.getTerm(doc, tempBR);
|
||||
final String docValue;
|
||||
if (br == null) {
|
||||
docValue = null;
|
||||
} else {
|
||||
docValue = br.utf8ToString();
|
||||
}
|
||||
return compareValues(docValue, value);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -560,9 +560,14 @@ public class QueryElevationComponent extends SearchComponent implements SolrCore
|
|||
public Integer value(int slot) {
|
||||
return values[slot];
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareDocToValue(int doc, Integer valueObj) throws IOException {
|
||||
final int value = valueObj.intValue();
|
||||
final int docValue = docVal(doc);
|
||||
return docValue - value; // values will be small enough that there is no overflow concern
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -138,6 +138,12 @@ public class RandomSortField extends FieldType {
|
|||
public Integer value(int slot) {
|
||||
return values[slot];
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareDocToValue(int doc, Integer valueObj) {
|
||||
// values will be positive... no overflow possible.
|
||||
return hash(doc+seed) - valueObj.intValue();
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
|
|
@ -121,6 +121,11 @@ class TermOrdValComparator_SML extends FieldComparator<Comparable> {
|
|||
return TermOrdValComparator_SML.createComparator(context.reader(), this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareDocToValue(int doc, Comparable docValue) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
// Base class for specialized (per bit width of the
|
||||
// ords) per-segment comparator. NOTE: this is messy;
|
||||
// we do this only because hotspot can't reliably inline
|
||||
|
@ -216,6 +221,20 @@ class TermOrdValComparator_SML extends FieldComparator<Comparable> {
|
|||
public BytesRef value(int slot) {
|
||||
return values==null ? parent.NULL_VAL : values[slot];
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareDocToValue(int doc, BytesRef value) {
|
||||
final BytesRef docValue = termsIndex.getTerm(doc, tempBR);
|
||||
if (docValue == null) {
|
||||
if (value == null) {
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
} else if (value == null) {
|
||||
return -1;
|
||||
}
|
||||
return docValue.compareTo(value);
|
||||
}
|
||||
}
|
||||
|
||||
// Used per-segment when bit width of doc->ord is 8:
|
||||
|
|
Loading…
Reference in New Issue