Index: CHANGES.txt
===================================================================
--- CHANGES.txt (revision 770123)
+++ CHANGES.txt (working copy)
@@ -121,6 +121,13 @@
is deprecated in favor of the new TimeLimitingCollector which
extends Collector. (Shai Erera via Mike McCandless)
+13. LUCENE-1593: HitQueue's constructor has changed to accept an additional
+ boolean to indicate whether pre-population of the queue with sentinel
+ objects should be performed.
+ Also, scoresDocInOrder was added to Query with a default impl which returns
+ false. Note that in 3.0 it will be changed to return true.
+ (Shai Erera via Michael McCandless)
+
Bug fixes
1. LUCENE-1415: MultiPhraseQuery has incorrect hashCode() and equals()
@@ -149,6 +156,10 @@
6. LUCENE-1600: Don't call String.intern unnecessarily in some cases
when loading documents from the index. (P Eger via Mike
McCandless)
+
+7. LUCENE-1593: MultiSearcher and ParallelMultiSearcher did not break ties (in
+ sort) by doc Id in a consistent manner (i.e., if Sort.FIELD_DOC was used vs.
+ when it wasn't). (Shai Erera via Michael McCandless)
New features
Index: contrib/benchmark/src/java/org/apache/lucene/benchmark/byTask/tasks/ReadTask.java
===================================================================
--- contrib/benchmark/src/java/org/apache/lucene/benchmark/byTask/tasks/ReadTask.java (revision 770123)
+++ contrib/benchmark/src/java/org/apache/lucene/benchmark/byTask/tasks/ReadTask.java (working copy)
@@ -103,7 +103,7 @@
if (numHits > 0) {
if (sort != null) {
TopFieldCollector collector = TopFieldCollector.create(sort, numHits,
- true, withScore(), withMaxScore());
+ true, withScore(), withMaxScore(), q.scoresDocInOrder());
searcher.search(q, collector);
hits = collector.topDocs();
} else {
Index: src/demo/org/apache/lucene/demo/SearchFiles.java
===================================================================
--- src/demo/org/apache/lucene/demo/SearchFiles.java (revision 770123)
+++ src/demo/org/apache/lucene/demo/SearchFiles.java (working copy)
@@ -193,7 +193,8 @@
int hitsPerPage, boolean raw, boolean interactive) throws IOException {
// Collect enough docs to show 5 pages
- TopScoreDocCollector collector = new TopScoreDocCollector(5 * hitsPerPage);
+ TopScoreDocCollector collector = TopScoreDocCollector.create(
+ 5 * hitsPerPage, query.scoresDocInOrder());
searcher.search(query, collector);
ScoreDoc[] hits = collector.topDocs().scoreDocs;
@@ -212,7 +213,7 @@
break;
}
- collector = new TopScoreDocCollector(numTotalHits);
+ collector = TopScoreDocCollector.create(numTotalHits, query.scoresDocInOrder());
searcher.search(query, collector);
hits = collector.topDocs().scoreDocs;
}
Index: src/java/org/apache/lucene/search/BooleanQuery.java
===================================================================
--- src/java/org/apache/lucene/search/BooleanQuery.java (revision 770123)
+++ src/java/org/apache/lucene/search/BooleanQuery.java (working copy)
@@ -221,6 +221,9 @@
* and scores documents in document number order.
*/
public Scorer scorer(IndexReader reader) throws IOException {
+ // TODO (3.0): instantiate either BS or BS2, according to
+ // allowDocsOutOfOrder (basically, try to inline BS2.score(Collector)'s
+ // logic.
BooleanScorer2 result = new BooleanScorer2(similarity,
minNrShouldMatch,
allowDocsOutOfOrder);
@@ -475,4 +478,11 @@
+ getMinimumNumberShouldMatch();
}
+ public boolean scoresDocInOrder() {
+ // TODO (3.0): For backwards compatibility, this will be chnaged in 3.0 to
+ // return true according to allowDocsOutOfOrder and the number of required
+ // and prohibited clauses.
+ return false;
+ }
+
}
Index: src/java/org/apache/lucene/search/BooleanScorer.java
===================================================================
--- src/java/org/apache/lucene/search/BooleanScorer.java (revision 770123)
+++ src/java/org/apache/lucene/search/BooleanScorer.java (working copy)
@@ -99,21 +99,23 @@
throws IOException {
int mask = 0;
if (required || prohibited) {
- if (nextMask == 0)
- throw new IndexOutOfBoundsException
- ("More than 32 required/prohibited clauses in query.");
+ if (nextMask == 0) {
+ throw new IndexOutOfBoundsException(
+ "More than 32 required/prohibited clauses in query.");
+ }
mask = nextMask;
nextMask = nextMask << 1;
- } else
- mask = 0;
+ }
- if (!prohibited)
+ if (!prohibited) {
maxCoord++;
-
- if (prohibited)
+ if (required) {
+ requiredMask |= mask; // update required mask
+ }
+ } else {
+ // prohibited
prohibitedMask |= mask; // update prohibited mask
- else if (required)
- requiredMask |= mask; // update required mask
+ }
scorers = new SubScorer(scorer, required, prohibited,
bucketTable.newCollector(mask), scorers);
@@ -121,8 +123,10 @@
private final void computeCoordFactors() {
coordFactors = new float[maxCoord];
- for (int i = 0; i < maxCoord; i++)
- coordFactors[i] = getSimilarity().coord(i, maxCoord-1);
+ Similarity sim = getSimilarity();
+ for (int i = 0; i < maxCoord; i++) {
+ coordFactors[i] = sim.coord(i, maxCoord - 1);
+ }
}
private int end;
@@ -130,8 +134,7 @@
/** @deprecated use {@link #score(Collector)} instead. */
public void score(HitCollector hc) throws IOException {
- next();
- score(hc, Integer.MAX_VALUE);
+ score(new HitCollectorWrapper(hc));
}
public void score(Collector collector) throws IOException {
@@ -145,9 +148,9 @@
}
protected boolean score(Collector collector, int max) throws IOException {
- if (coordFactors == null)
+ if (coordFactors == null) {
computeCoordFactors();
-
+ }
boolean more;
Bucket tmp;
@@ -241,8 +244,9 @@
}
public float score() {
- if (coordFactors == null)
+ if (coordFactors == null) {
computeCoordFactors();
+ }
return current.score * coordFactors[current.coord];
}
Index: src/java/org/apache/lucene/search/BooleanScorer2.java
===================================================================
--- src/java/org/apache/lucene/search/BooleanScorer2.java (revision 770123)
+++ src/java/org/apache/lucene/search/BooleanScorer2.java (working copy)
@@ -36,10 +36,10 @@
private ArrayList prohibitedScorers = new ArrayList();
private class Coordinator {
+ float[] coordFactors = null;
int maxCoord = 0; // to be increased for each non prohibited scorer
+ int nrMatchers; // to be increased by score() of match counting scorers.
- private float[] coordFactors = null;
-
void init() { // use after all scorers have been added.
coordFactors = new float[maxCoord + 1];
Similarity sim = getSimilarity();
@@ -48,15 +48,6 @@
}
}
- int nrMatchers; // to be increased by score() of match counting scorers.
-
- void initDoc() {
- nrMatchers = 0;
- }
-
- float coordFactor() {
- return coordFactors[nrMatchers];
- }
}
private final Coordinator coordinator;
@@ -85,7 +76,7 @@
* @param allowDocsOutOfOrder Whether it is allowed to return documents out of order.
* This can accelerate the scoring of disjunction queries.
*/
- public BooleanScorer2(Similarity similarity, int minNrShouldMatch, boolean allowDocsOutOfOrder) {
+ public BooleanScorer2(Similarity similarity, int minNrShouldMatch, boolean allowDocsOutOfOrder) throws IOException {
super(similarity);
if (minNrShouldMatch < 0) {
throw new IllegalArgumentException("Minimum number of optional scorers should not be negative");
@@ -105,7 +96,7 @@
* at least one of the optional scorers will have to
* match during the search.
*/
- public BooleanScorer2(Similarity similarity, int minNrShouldMatch) {
+ public BooleanScorer2(Similarity similarity, int minNrShouldMatch) throws IOException {
this(similarity, minNrShouldMatch, false);
}
@@ -114,11 +105,11 @@
* at least one of the optional scorers will have to match during the search.
* @param similarity The similarity to be used.
*/
- public BooleanScorer2(Similarity similarity) {
+ public BooleanScorer2(Similarity similarity) throws IOException {
this(similarity, 0, false);
}
- public void add(final Scorer scorer, boolean required, boolean prohibited) {
+ public void add(final Scorer scorer, boolean required, boolean prohibited) throws IOException {
if (!prohibited) {
coordinator.maxCoord++;
}
@@ -142,7 +133,7 @@
* When "sum" is used in a name it means score value summing
* over the matching scorers
*/
- private void initCountingSumScorer() throws IOException {
+ void initCountingSumScorer() throws IOException {
coordinator.init();
countingSumScorer = makeCountingSumScorer();
}
@@ -151,17 +142,24 @@
private class SingleMatchScorer extends Scorer {
private Scorer scorer;
private int lastScoredDoc = -1;
+ // Save the score of lastScoredDoc, so that we don't compute it more than
+ // once in score().
+ private float lastDocScore = Float.NaN;
SingleMatchScorer(Scorer scorer) {
super(scorer.getSimilarity());
this.scorer = scorer;
}
public float score() throws IOException {
- if (this.doc() >= lastScoredDoc) {
- lastScoredDoc = this.doc();
+ int doc = doc();
+ if (doc >= lastScoredDoc) {
+ if (doc > lastScoredDoc) {
+ lastDocScore = scorer.score();
+ lastScoredDoc = doc;
+ }
coordinator.nrMatchers++;
}
- return scorer.score();
+ return lastDocScore;
}
public int doc() {
return scorer.doc();
@@ -178,39 +176,51 @@
}
private Scorer countingDisjunctionSumScorer(final List scorers,
- int minNrShouldMatch) throws IOException
- // each scorer from the list counted as a single matcher
- {
+ int minNrShouldMatch) throws IOException {
+ // each scorer from the list counted as a single matcher
return new DisjunctionSumScorer(scorers, minNrShouldMatch) {
private int lastScoredDoc = -1;
+ // Save the score of lastScoredDoc, so that we don't compute it more than
+ // once in score().
+ private float lastDocScore = Float.NaN;
public float score() throws IOException {
- if (this.doc() >= lastScoredDoc) {
- lastScoredDoc = this.doc();
+ int doc = doc();
+ if (doc >= lastScoredDoc) {
+ if (doc > lastScoredDoc) {
+ lastDocScore = super.score();
+ lastScoredDoc = doc;
+ }
coordinator.nrMatchers += super.nrMatchers;
}
- return super.score();
+ return lastDocScore;
}
};
}
- private static Similarity defaultSimilarity = new DefaultSimilarity();
+ private static final Similarity defaultSimilarity = Similarity.getDefault();
private Scorer countingConjunctionSumScorer(List requiredScorers) throws IOException {
// each scorer from the list counted as a single matcher
final int requiredNrMatchers = requiredScorers.size();
return new ConjunctionScorer(defaultSimilarity, requiredScorers) {
private int lastScoredDoc = -1;
-
+ // Save the score of lastScoredDoc, so that we don't compute it more than
+ // once in score().
+ private float lastDocScore = Float.NaN;
public float score() throws IOException {
- if (this.doc() >= lastScoredDoc) {
- lastScoredDoc = this.doc();
+ int doc = doc();
+ if (doc >= lastScoredDoc) {
+ if (doc > lastScoredDoc) {
+ lastDocScore = super.score();
+ lastScoredDoc = doc;
+ }
coordinator.nrMatchers += requiredNrMatchers;
}
// All scorers match, so defaultSimilarity super.score() always has 1 as
// the coordination factor.
// Therefore the sum of the scores of the requiredScorers
// is used as score.
- return super.score();
+ return lastDocScore;
}
};
}
@@ -361,7 +371,7 @@
collector.setScorer(this);
while (docNr < max) {
collector.collect(docNr);
- if (! countingSumScorer.next()) {
+ if (!countingSumScorer.next()) {
return false;
}
docNr = countingSumScorer.doc();
@@ -379,9 +389,9 @@
}
public float score() throws IOException {
- coordinator.initDoc();
+ coordinator.nrMatchers = 0;
float sum = countingSumScorer.score();
- return sum * coordinator.coordFactor();
+ return sum * coordinator.coordFactors[coordinator.nrMatchers];
}
/** Skips to the first match beyond the current whose document number is
Index: src/java/org/apache/lucene/search/ConstantScoreQuery.java
===================================================================
--- src/java/org/apache/lucene/search/ConstantScoreQuery.java (revision 770123)
+++ src/java/org/apache/lucene/search/ConstantScoreQuery.java (working copy)
@@ -163,8 +163,9 @@
// Simple add is OK since no existing filter hashcode has a float component.
return filter.hashCode() + Float.floatToIntBits(getBoost());
}
+
+ public boolean scoresDocInOrder() {
+ return true;
+ }
}
-
-
-
Index: src/java/org/apache/lucene/search/DisjunctionMaxQuery.java
===================================================================
--- src/java/org/apache/lucene/search/DisjunctionMaxQuery.java (revision 770123)
+++ src/java/org/apache/lucene/search/DisjunctionMaxQuery.java (working copy)
@@ -253,4 +253,8 @@
+ disjuncts.hashCode();
}
+ public boolean scoresDocInOrder() {
+ return true;
+ }
+
}
Index: src/java/org/apache/lucene/search/FieldDoc.java
===================================================================
--- src/java/org/apache/lucene/search/FieldDoc.java (revision 770123)
+++ src/java/org/apache/lucene/search/FieldDoc.java (working copy)
@@ -17,7 +17,6 @@
* limitations under the License.
*/
-
/**
* Expert: A ScoreDoc which also contains information about
* how to sort the referenced document. In addition to the
@@ -38,8 +37,7 @@
* @see ScoreDoc
* @see TopFieldDocs
*/
-public class FieldDoc
-extends ScoreDoc {
+public class FieldDoc extends ScoreDoc {
/** Expert: The values which are used to sort the referenced document.
* The order of these will match the original sort criteria given by a
@@ -60,4 +58,19 @@
super (doc, score);
this.fields = fields;
}
+
+ // A convenience method for debugging.
+ public String toString() {
+ // super.toString returns the doc and score information, so just add the
+ // fields information
+ StringBuffer sb = new StringBuffer(super.toString());
+ sb.append("[");
+ for (int i = 0; i < fields.length; i++) {
+ sb.append(fields[i]).append(", ");
+ }
+ sb.setLength(sb.length() - 2); // discard last ", "
+ sb.append("]");
+ return super.toString();
+ }
+
}
\ No newline at end of file
Index: src/java/org/apache/lucene/search/FilteredQuery.java
===================================================================
--- src/java/org/apache/lucene/search/FilteredQuery.java (revision 770123)
+++ src/java/org/apache/lucene/search/FilteredQuery.java (working copy)
@@ -199,4 +199,9 @@
public int hashCode() {
return query.hashCode() ^ filter.hashCode() + Float.floatToRawIntBits(getBoost());
}
+
+ public boolean scoresDocInOrder() {
+ return query.scoresDocInOrder();
+ }
+
}
Index: src/java/org/apache/lucene/search/HitQueue.java
===================================================================
--- src/java/org/apache/lucene/search/HitQueue.java (revision 770123)
+++ src/java/org/apache/lucene/search/HitQueue.java (working copy)
@@ -20,10 +20,58 @@
import org.apache.lucene.util.PriorityQueue;
final class HitQueue extends PriorityQueue {
- HitQueue(int size) {
+
+ private boolean prePopulate;
+
+ /**
+ * Creates a new instance with size elements. If
+ * prePopulate is set to true, the queue will pre-populate itself
+ * with sentinel objects and set its {@link #size()} to size. In
+ * that case, you should not rely on {@link #size()} to get the number of
+ * actual elements that were added to the queue, but keep track yourself.
+ * NOTE: in case prePopulate is true, you should pop
+ * elements from the queue using the following code example:
+ *
+ *
+ * PriorityQueue pq = new HitQueue(10, true); // pre-populate.
+ * ScoreDoc top = pq.top();
+ *
+ * // Add/Update one element.
+ * top.score = 1.0f;
+ * top.doc = 0;
+ * top = (ScoreDoc) pq.updateTop();
+ * int totalHits = 1;
+ *
+ * // Now pop only the elements that were *truly* inserted.
+ * // First, pop all the sentinel elements (there are pq.size() - totalHits).
+ * for (int i = pq.size() - totalHits; i > 0; i--) pq.pop();
+ *
+ * // Now pop the truly added elements.
+ * ScoreDoc[] results = new ScoreDoc[totalHits];
+ * for (int i = totalHits - 1; i >= 0; i--) {
+ * results[i] = (ScoreDoc) pq.pop();
+ * }
+ *
+ *
+ * @param size
+ * the requested size of this queue.
+ * @param prePopulate
+ * specifies whether to pre-populate the queue with sentinel values.
+ * @see #getSentinelObject()
+ */
+ HitQueue(int size, boolean prePopulate) {
+ this.prePopulate = prePopulate;
initialize(size);
}
+ // Returns null if prePopulate is false.
+ protected Object getSentinelObject() {
+ // Always set the doc Id to MAX_VALUE so that it won't be favored by
+ // lessThan. This generally should not happen since if score is not NEG_INF,
+ // TopScoreDocCollector will always add the object to the queue.
+ return !prePopulate ? null : new ScoreDoc(Integer.MAX_VALUE, Float.NEGATIVE_INFINITY);
+ }
+
protected final boolean lessThan(Object a, Object b) {
ScoreDoc hitA = (ScoreDoc)a;
ScoreDoc hitB = (ScoreDoc)b;
Index: src/java/org/apache/lucene/search/IndexSearcher.java
===================================================================
--- src/java/org/apache/lucene/search/IndexSearcher.java (revision 770123)
+++ src/java/org/apache/lucene/search/IndexSearcher.java (working copy)
@@ -18,9 +18,9 @@
*/
import java.io.IOException;
-import java.util.List;
import java.util.ArrayList;
-import org.apache.lucene.util.SorterTemplate;
+import java.util.List;
+
import org.apache.lucene.document.Document;
import org.apache.lucene.document.FieldSelector;
import org.apache.lucene.index.CorruptIndexException;
@@ -40,7 +40,7 @@
public class IndexSearcher extends Searcher {
IndexReader reader;
private boolean closeReader;
- private IndexReader[] sortedSubReaders;
+ private IndexReader[] subReaders;
private int[] sortedStarts;
/** Creates a searcher searching the index in the named directory.
@@ -48,7 +48,7 @@
* @throws IOException if there is a low-level IO error
*/
public IndexSearcher(String path) throws CorruptIndexException, IOException {
- this(IndexReader.open(path), true, false);
+ this(IndexReader.open(path), true);
}
/** Creates a searcher searching the index in the provided directory.
@@ -56,28 +56,27 @@
* @throws IOException if there is a low-level IO error
*/
public IndexSearcher(Directory directory) throws CorruptIndexException, IOException {
- this(IndexReader.open(directory), true, false);
+ this(IndexReader.open(directory), true);
}
/** Creates a searcher searching the provided index. */
public IndexSearcher(IndexReader r) {
- this(r, false, false);
+ this(r, false);
}
- /** Expert: Creates a searcher searching the provided
- * index, specifying whether searches must visit the
- * documents in order. By default, segments are searched
- * in order of decreasing numDocs(); if you pass true for
- * docsInOrder, they will instead be searched in their
- * natural (unsorted) order.*/
- public IndexSearcher(IndexReader r, boolean docsInOrder) {
- this(r, false, docsInOrder);
- }
-
- private IndexSearcher(IndexReader r, boolean closeReader, boolean docsInOrder) {
+ private IndexSearcher(IndexReader r, boolean closeReader) {
reader = r;
this.closeReader = closeReader;
- sortSubReaders(docsInOrder);
+
+ List subReadersList = new ArrayList();
+ gatherSubReaders(subReadersList, reader);
+ subReaders = (IndexReader[]) subReadersList.toArray(new IndexReader[subReadersList.size()]);
+ sortedStarts = new int[subReaders.length];
+ int maxDoc = 0;
+ for (int i = 0; i < subReaders.length; i++) {
+ sortedStarts[i] = maxDoc;
+ maxDoc += subReaders[i].maxDoc();
+ }
}
protected void gatherSubReaders(List allSubReaders, IndexReader r) {
@@ -86,54 +85,12 @@
// Add the reader itself, and do not recurse
allSubReaders.add(r);
} else {
- for(int i=0;itrackDocScores 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.
+ * @see Query#scoresDocInOrder()
* @throws IOException
*/
public static TopFieldCollector create(Sort sort, int numHits,
- boolean fillFields, boolean trackDocScores, boolean trackMaxScore)
+ 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");
@@ -531,28 +851,47 @@
FieldValueHitQueue queue = FieldValueHitQueue.create(sort.fields, numHits);
if (queue.getComparators().length == 1) {
- if (trackMaxScore) {
- return new OneComparatorScoringMaxScoreCollector(queue, numHits, fillFields);
- } else if (trackDocScores) {
- return new OneComparatorScoringNoMaxScoreCollector(queue, numHits, fillFields);
+ 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 {
- return new OneComparatorNonScoringCollector(queue, numHits, fillFields);
+ 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 (trackMaxScore) {
- return new MultiComparatorScoringMaxScoreCollector(queue, numHits, fillFields);
- } else if (trackDocScores) {
- return new MultiComparatorScoringNoMaxScoreCollector(queue, numHits, fillFields);
+ 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 {
- return new MultiComparatorNonScoringCollector(queue, numHits, fillFields);
+ if (trackMaxScore) {
+ return new OutOfOrderMultiComparatorScoringMaxScoreCollector(queue, numHits, fillFields);
+ } else if (trackDocScores) {
+ return new OutOfOrderMultiComparatorScoringNoMaxScoreCollector(queue, numHits, fillFields);
+ } else {
+ return new OutOfOrderMultiComparatorNonScoringCollector(queue, numHits, fillFields);
+ }
}
}
final void add(int slot, int doc, float score) {
- pq.put(new FieldValueHitQueue.Entry(slot, docBase + doc, score));
- bottom = (FieldValueHitQueue.Entry) pq.top();
+ bottom = (Entry) pq.add(new Entry(slot, docBase + doc, score));
queueFull = totalHits == numHits;
}
@@ -562,14 +901,15 @@
*/
protected void populateResults(ScoreDoc[] results, int howMany) {
- FieldValueHitQueue queue = (FieldValueHitQueue) pq;
if (fillFields) {
- for (int i = queue.size() - 1; i >= 0; i--) {
- results[i] = queue.fillFields((FieldValueHitQueue.Entry) queue.pop());
+ // avoid casting if unnecessary.
+ FieldValueHitQueue queue = (FieldValueHitQueue) pq;
+ for (int i = howMany - 1; i >= 0; i--) {
+ results[i] = queue.fillFields((Entry) queue.pop());
}
} else {
- for (int i = queue.size() - 1; i >= 0; i--) {
- Entry entry = (FieldValueHitQueue.Entry) queue.pop();
+ for (int i = howMany - 1; i >= 0; i--) {
+ Entry entry = (Entry) pq.pop();
results[i] = new FieldDoc(entry.docID, entry.score);
}
}
Index: src/java/org/apache/lucene/search/TopScoreDocCollector.java
===================================================================
--- src/java/org/apache/lucene/search/TopScoreDocCollector.java (revision 770123)
+++ src/java/org/apache/lucene/search/TopScoreDocCollector.java (working copy)
@@ -22,25 +22,89 @@
import org.apache.lucene.index.IndexReader;
/**
- * A {@link Collector} implementation that collects the
- * top-scoring hits, returning them as a {@link
- * TopDocs}. This is used by {@link IndexSearcher} to
- * implement {@link TopDocs}-based search. Hits are sorted
- * by score descending and then (when the scores are tied)
- * docID ascending.
+ * A {@link Collector} implementation that collects the top-scoring hits,
+ * returning them as a {@link TopDocs}. This is used by {@link IndexSearcher} to
+ * implement {@link TopDocs}-based search. Hits are sorted by score descending
+ * and then (when the scores are tied) docID ascending. When you create an
+ * instance of this collector you should know in advance whether documents are
+ * going to be collected in doc Id order or not. That information is available
+ * on the {@link Query} instance and can be obtained by calling
+ * {@link Query#scoresDocInOrder()}.
+ *
+ * @see Query#scoresDocInOrder()
*/
-public final class TopScoreDocCollector extends TopDocsCollector {
+public abstract class TopScoreDocCollector extends TopDocsCollector {
- private ScoreDoc reusableSD;
- private int docBase = 0;
- private Scorer scorer;
+ // TODO: add a test case that tests out of orderness collection.
+
+ // Assumes docs are scored in order.
+ private static class InOrderTopScoreDocCollector extends TopScoreDocCollector {
+ private InOrderTopScoreDocCollector(int numHits) {
+ super(numHits);
+ }
- /** Construct to collect a given number of hits.
- * @param numHits the maximum number of hits to collect
+ public void collect(int doc) throws IOException {
+ float score = scorer.score();
+ totalHits++;
+ if (score <= pqTop.score) {
+ // Since docs are returned in-order (i.e., increasing doc Id), a document
+ // with equal score to pqTop.score cannot compete since HitQueue favors
+ // documents with lower doc Ids. Therefore reject those docs too.
+ return;
+ }
+ pqTop.doc = doc + docBase;
+ pqTop.score = score;
+ pqTop = (ScoreDoc) pq.updateTop();
+ }
+ }
+
+ // Assumes docs are scored out of order.
+ private static class OutOfOrderTopScoreDocCollector extends TopScoreDocCollector {
+ private OutOfOrderTopScoreDocCollector(int numHits) {
+ super(numHits);
+ }
+
+ public void collect(int doc) throws IOException {
+ float score = scorer.score();
+ totalHits++;
+ doc += docBase;
+ if (score < pqTop.score || (score == pqTop.score && doc > pqTop.doc)) {
+ return;
+ }
+ pqTop.doc = doc;
+ pqTop.score = score;
+ pqTop = (ScoreDoc) pq.updateTop();
+ }
+ }
+
+ /**
+ * Creates a new {@link TopScoreDocCollector} given the number of hits to
+ * collect and whether documents are scored in order by the input
+ * {@link Scorer} to {@link #setScorer(Scorer)}.
+ *
+ * @see Query#scoresDocInOrder()
*/
- public TopScoreDocCollector(int numHits) {
- super(new HitQueue(numHits));
+ public static TopScoreDocCollector create(int numHits, boolean docsScoredInOrder) {
+
+ if (docsScoredInOrder) {
+ return new InOrderTopScoreDocCollector(numHits);
+ } else {
+ return new OutOfOrderTopScoreDocCollector(numHits);
+ }
+
}
+
+ ScoreDoc pqTop;
+ int docBase = 0;
+ Scorer scorer;
+
+ // prevents instantiation
+ private TopScoreDocCollector(int numHits) {
+ super(new HitQueue(numHits, true));
+ // HitQueue implements getSentinelObject to return a ScoreDoc, so we know
+ // that at this point top() is already initialized.
+ pqTop = (ScoreDoc) pq.top();
+ }
protected TopDocs newTopDocs(ScoreDoc[] results, int start) {
if (results == null) {
@@ -62,24 +126,6 @@
return new TopDocs(totalHits, results, maxScore);
}
- // javadoc inherited
- public void collect(int doc) throws IOException {
- float score = scorer.score();
- totalHits++;
- if (reusableSD == null) {
- reusableSD = new ScoreDoc(doc + docBase, score);
- } else if (score >= reusableSD.score) {
- // reusableSD holds the last "rejected" entry, so, if
- // this new score is not better than that, there's no
- // need to try inserting it
- reusableSD.doc = doc + docBase;
- reusableSD.score = score;
- } else {
- return;
- }
- reusableSD = (ScoreDoc) pq.insertWithOverflow(reusableSD);
- }
-
public void setNextReader(IndexReader reader, int base) {
docBase = base;
}
Index: src/java/org/apache/lucene/search/function/CustomScoreQuery.java
===================================================================
--- src/java/org/apache/lucene/search/function/CustomScoreQuery.java (revision 770123)
+++ src/java/org/apache/lucene/search/function/CustomScoreQuery.java (working copy)
@@ -457,4 +457,17 @@
return "custom";
}
+ public boolean scoresDocInOrder() {
+ // Return true only if all queries score docs in order
+ if (!subQuery.scoresDocInOrder()) {
+ return false;
+ }
+ for (int i = 0; i < valSrcQueries.length; i++) {
+ if (!valSrcQueries[i].scoresDocInOrder()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
}
Index: src/java/org/apache/lucene/search/function/ValueSourceQuery.java
===================================================================
--- src/java/org/apache/lucene/search/function/ValueSourceQuery.java (revision 770123)
+++ src/java/org/apache/lucene/search/function/ValueSourceQuery.java (working copy)
@@ -186,4 +186,8 @@
return (getClass().hashCode() + valSrc.hashCode()) ^ Float.floatToIntBits(getBoost());
}
+ public boolean scoresDocInOrder() {
+ return true;
+ }
+
}
Index: src/java/org/apache/lucene/search/spans/SpanNearQuery.java
===================================================================
--- src/java/org/apache/lucene/search/spans/SpanNearQuery.java (revision 770123)
+++ src/java/org/apache/lucene/search/spans/SpanNearQuery.java (working copy)
@@ -178,4 +178,16 @@
result ^= (inOrder ? 0x99AFD3BD : 0);
return result;
}
+
+ public boolean scoresDocInOrder() {
+ // Only if all clauses score docs in order return true.
+ for (Iterator iter = clauses.iterator(); iter.hasNext();) {
+ SpanQuery q = (SpanQuery) iter.next();
+ if (!q.scoresDocInOrder()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
}
Index: src/java/org/apache/lucene/search/spans/SpanNotQuery.java
===================================================================
--- src/java/org/apache/lucene/search/spans/SpanNotQuery.java (revision 770123)
+++ src/java/org/apache/lucene/search/spans/SpanNotQuery.java (working copy)
@@ -197,4 +197,8 @@
return h;
}
+ public boolean scoresDocInOrder() {
+ return include.scoresDocInOrder() && exclude.scoresDocInOrder();
+ }
+
}
Index: src/java/org/apache/lucene/search/spans/SpanOrQuery.java
===================================================================
--- src/java/org/apache/lucene/search/spans/SpanOrQuery.java (revision 770123)
+++ src/java/org/apache/lucene/search/spans/SpanOrQuery.java (working copy)
@@ -243,4 +243,15 @@
};
}
+ public boolean scoresDocInOrder() {
+ // return true only if all clauses score docs in order.
+ for (Iterator iter = clauses.iterator(); iter.hasNext();) {
+ SpanQuery q = (SpanQuery) iter.next();
+ if (!q.scoresDocInOrder()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
}
Index: src/java/org/apache/lucene/search/spans/SpanQuery.java
===================================================================
--- src/java/org/apache/lucene/search/spans/SpanQuery.java (revision 770123)
+++ src/java/org/apache/lucene/search/spans/SpanQuery.java (working copy)
@@ -17,15 +17,14 @@
* limitations under the License.
*/
+import java.io.IOException;
+import java.util.Collection;
+
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.Searcher;
import org.apache.lucene.search.Weight;
-import java.io.IOException;
-import java.util.Collection;
-import java.util.Set;
-
/** Base class for span-based queries. */
public abstract class SpanQuery extends Query {
/** Expert: Returns the matches for this query in an index. Used internally
@@ -46,7 +45,7 @@
*/
public PayloadSpans getPayloadSpans(IndexReader reader) throws IOException{
return null;
- };
+ }
/** Returns the name of the field matched by this query.*/
public abstract String getField();
@@ -61,5 +60,8 @@
return new SpanWeight(this, searcher);
}
+ public boolean scoresDocInOrder() {
+ return true;
+ }
+
}
-
Index: src/java/org/apache/lucene/util/PriorityQueue.java
===================================================================
--- src/java/org/apache/lucene/util/PriorityQueue.java (revision 770123)
+++ src/java/org/apache/lucene/util/PriorityQueue.java (working copy)
@@ -29,6 +29,49 @@
must define this one method. */
protected abstract boolean lessThan(Object a, Object b);
+ /**
+ * This method can be overridden by extending classes to return a sentinel
+ * object which will be used by {@link #initialize(int)} to fill the queue, so
+ * that the code which uses that queue can always assume it's full and only
+ * change the top without attempting to insert any new object.+ * // extends getSentinelObject() to return a non-null value. + * PriorityQueue pq = new MyQueue(numHits); + * // save the 'top' element, which is guaranteed to not be null. + * MyObject pqTop = (MyObject) pq.top(); + * <...> + * // now in order to add a new element, which is 'better' than top (after + * // you've verified it is better), it is as simple as: + * pqTop.change(). + * pqTop = pq.updateTop(); + *+ * + * NOTE: if this method returns a non-null value, it will be called by + * {@link #initialize(int)} {@link #size()} times, relying on a new object to + * be returned and will not check if it's null again. Therefore you should + * ensure any call to this method creates a new instance and behaves + * consistently, e.g., it cannot return null if it previously returned + * non-null. + * + * @return the sentinel object to use to pre-populate the queue, or null if + * sentinel objects are not supported. + */ + protected Object getSentinelObject() { + return null; + } + /** Subclass constructors must call this. */ protected final void initialize(int maxSize) { size = 0; @@ -40,12 +83,25 @@ heapSize = maxSize + 1; heap = new Object[heapSize]; this.maxSize = maxSize; + + // If sentinel objects are supported, populate the queue with them + Object sentinel = getSentinelObject(); + if (sentinel != null) { + heap[1] = sentinel; + for (int i = 2; i < heap.length; i++) { + heap[i] = getSentinelObject(); + } + size = maxSize; + } } /** - * Adds an Object to a PriorityQueue in log(size) time. - * If one tries to add more objects than maxSize from initialize - * a RuntimeException (ArrayIndexOutOfBound) is thrown. + * Adds an Object to a PriorityQueue in log(size) time. If one tries to add + * more objects than maxSize from initialize a RuntimeException + * (ArrayIndexOutOfBound) is thrown. + * + * @deprecated use {@link #add(Object)} which returns the new top object, + * saving an additional call to {@link #top()}. */ public final void put(Object element) { size++; @@ -54,10 +110,27 @@ } /** - * Adds element to the PriorityQueue in log(size) time if either - * the PriorityQueue is not full, or not lessThan(element, top()). + * Adds an Object to a PriorityQueue in log(size) time. If one tries to add + * more objects than maxSize from initialize an + * {@link ArrayIndexOutOfBoundsException} is thrown. + * + * @return the new 'top' element in the queue. + */ + public final Object add(Object element) { + size++; + heap[size] = element; + upHeap(); + return heap[1]; + } + + /** + * Adds element to the PriorityQueue in log(size) time if either the + * PriorityQueue is not full, or not lessThan(element, top()). + * * @param element * @return true if element is added, false otherwise. + * @deprecated use {@link #insertWithOverflow(Object)} instead, which + * encourages objects reuse. */ public boolean insert(Object element) { return insertWithOverflow(element) != element; @@ -109,16 +182,53 @@ return null; } - /** Should be called when the Object at top changes values. Still log(n) - * worst case, but it's at least twice as fast to
- * { pq.top().change(); pq.adjustTop(); }
- * instead of
- * { o = pq.pop(); o.change(); pq.push(o); }
+ /**
+ * Should be called when the Object at top changes values. Still log(n) worst
+ * case, but it's at least twice as fast to
+ *
+ *
+ * pq.top().change();
+ * pq.adjustTop();
*
+ *
+ * instead of
+ *
+ *
+ * o = pq.pop();
+ * o.change();
+ * pq.push(o);
+ *
+ *
+ * @deprecated use {@link #updateTop()} which returns the new top element and
+ * saves an additional call to {@link #top()}.
*/
public final void adjustTop() {
downHeap();
}
+
+ /**
+ * Should be called when the Object at top changes values. Still log(n) worst
+ * case, but it's at least twice as fast to
+ *
+ *
+ * pq.top().change();
+ * pq.updateTop();
+ *
+ *
+ * instead of
+ *
+ *
+ * o = pq.pop();
+ * o.change();
+ * pq.push(o);
+ *
+ *
+ * @return the new 'top' element.
+ */
+ public final Object updateTop() {
+ downHeap();
+ return heap[1];
+ }
/** Returns the number of elements currently stored in the PriorityQueue. */
public final int size() {
@@ -127,8 +237,9 @@
/** Removes all entries from the PriorityQueue. */
public final void clear() {
- for (int i = 0; i <= size; i++)
+ for (int i = 0; i <= size; i++) {
heap[i] = null;
+ }
size = 0;
}
Index: src/test/org/apache/lucene/index/TestIndexReader.java
===================================================================
--- src/test/org/apache/lucene/index/TestIndexReader.java (revision 770123)
+++ src/test/org/apache/lucene/index/TestIndexReader.java (working copy)
@@ -39,20 +39,17 @@
import org.apache.lucene.document.Fieldable;
import org.apache.lucene.document.SetBasedFieldSelector;
import org.apache.lucene.index.IndexReader.FieldOption;
-import org.apache.lucene.search.Collector;
+import org.apache.lucene.search.FieldCache;
import org.apache.lucene.search.IndexSearcher;
-import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
-import org.apache.lucene.search.Scorer;
import org.apache.lucene.search.TermQuery;
-import org.apache.lucene.search.FieldCache;
import org.apache.lucene.store.AlreadyClosedException;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.store.LockObtainFailedException;
import org.apache.lucene.store.MockRAMDirectory;
-import org.apache.lucene.store.RAMDirectory;
import org.apache.lucene.store.NoSuchDirectoryException;
+import org.apache.lucene.store.RAMDirectory;
import org.apache.lucene.util.LuceneTestCase;
import org.apache.lucene.util._TestUtil;
@@ -1644,59 +1641,6 @@
dir.close();
}
- // LUCENE-1483
- public void testDocsInOrderSearch() throws Throwable {
- Directory dir = new MockRAMDirectory();
-
- IndexWriter writer = new IndexWriter(dir, new StandardAnalyzer(),
- IndexWriter.MaxFieldLength.LIMITED);
- writer.addDocument(createDocument("a"));
- writer.commit();
- writer.addDocument(createDocument("a"));
- writer.addDocument(createDocument("a"));
- writer.close();
-
- Query q = new TermQuery(new Term("id", "a"));
-
- IndexSearcher s = new IndexSearcher(dir);
- s.search(q, new Collector() {
- int lastDocBase = -1;
- public void setNextReader(IndexReader reader, int docBase) {
- if (lastDocBase == -1) {
- assertEquals(1, docBase);
- } else if (lastDocBase == 1) {
- assertEquals(0, docBase);
- } else {
- fail();
- }
- lastDocBase = docBase;
- }
- public void collect(int doc) {}
- public void setScorer(Scorer scorer) {}
- });
- s.close();
-
- IndexReader r = IndexReader.open(dir);
- s = new IndexSearcher(r, true);
- s.search(q, new Collector() {
- int lastDocBase = -1;
- public void setNextReader(IndexReader reader, int docBase) {
- if (lastDocBase == -1) {
- assertEquals(0, docBase);
- } else if (lastDocBase == 0) {
- assertEquals(1, docBase);
- } else {
- fail();
- }
- lastDocBase = docBase;
- }
- public void collect(int doc) {}
- public void setScorer(Scorer scorer) {}
- });
- s.close();
- r.close();
- }
-
// LUCENE-1579: Ensure that on a cloned reader, segments
// reuse the doc values arrays in FieldCache
public void testFieldCacheReuseAfterClone() throws Exception {
Index: src/test/org/apache/lucene/search/TestPositiveScoresOnlyCollector.java
===================================================================
--- src/test/org/apache/lucene/search/TestPositiveScoresOnlyCollector.java (revision 770123)
+++ src/test/org/apache/lucene/search/TestPositiveScoresOnlyCollector.java (working copy)
@@ -68,7 +68,7 @@
}
Scorer s = new SimpleScorer();
- TopDocsCollector tdc = new TopScoreDocCollector(scores.length);
+ TopDocsCollector tdc = TopScoreDocCollector.create(scores.length, true);
Collector c = new PositiveScoresOnlyCollector(tdc);
c.setScorer(s);
while (!s.next()) {
Index: src/test/org/apache/lucene/search/TestSort.java
===================================================================
--- src/test/org/apache/lucene/search/TestSort.java (revision 770123)
+++ src/test/org/apache/lucene/search/TestSort.java (working copy)
@@ -17,9 +17,20 @@
* limitations under the License.
*/
+import java.io.IOException;
+import java.io.Serializable;
+import java.rmi.Naming;
+import java.rmi.registry.LocateRegistry;
+import java.util.BitSet;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Random;
+
import junit.framework.Test;
import junit.framework.TestSuite;
import junit.textui.TestRunner;
+
import org.apache.lucene.analysis.SimpleAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
@@ -28,22 +39,12 @@
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.Term;
import org.apache.lucene.queryParser.ParseException;
+import org.apache.lucene.search.BooleanClause.Occur;
import org.apache.lucene.store.LockObtainFailedException;
import org.apache.lucene.store.RAMDirectory;
import org.apache.lucene.util.DocIdBitSet;
import org.apache.lucene.util.LuceneTestCase;
-import java.io.IOException;
-import java.io.Serializable;
-import java.rmi.Naming;
-import java.rmi.registry.LocateRegistry;
-import java.util.BitSet;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Locale;
-import java.util.Random;
-import java.util.regex.Pattern;
-
/**
* Unit tests for sorting code.
*
@@ -53,9 +54,7 @@
* @version $Id$
*/
-public class TestSort
-extends LuceneTestCase
-implements Serializable {
+public class TestSort extends LuceneTestCase implements Serializable {
private static final int NUM_STRINGS = 6000;
private Searcher full;
@@ -118,7 +117,7 @@
{ "Y", "g", "1", "0.2", null, null, null, null, null, null, null, null},
{ "Z", "f g", null, null, null, null, null, null, null, null, null, null}
};
-
+
// create an index of all the documents, or just the x, or just the y documents
private Searcher getIndex (boolean even, boolean odd)
throws IOException {
@@ -343,28 +342,28 @@
public void testCustomFieldParserSort() throws Exception {
sort.setSort (new SortField[] { new SortField ("parser", new FieldCache.IntParser(){
public final int parseInt(final String val) {
- return (int) (val.charAt(0)-'A') * 123456;
+ return (val.charAt(0)-'A') * 123456;
}
}), SortField.FIELD_DOC });
assertMatches (full, queryA, sort, "JIHGFEDCBA");
sort.setSort (new SortField[] { new SortField ("parser", new FieldCache.FloatParser(){
public final float parseFloat(final String val) {
- return (float) Math.sqrt( (double) val.charAt(0) );
+ return (float) Math.sqrt( val.charAt(0) );
}
}), SortField.FIELD_DOC });
assertMatches (full, queryA, sort, "JIHGFEDCBA");
sort.setSort (new SortField[] { new SortField ("parser", new ExtendedFieldCache.LongParser(){
public final long parseLong(final String val) {
- return (long) (val.charAt(0)-'A') * 1234567890L;
+ return (val.charAt(0)-'A') * 1234567890L;
}
}), SortField.FIELD_DOC });
assertMatches (full, queryA, sort, "JIHGFEDCBA");
sort.setSort (new SortField[] { new SortField ("parser", new ExtendedFieldCache.DoubleParser(){
public final double parseDouble(final String val) {
- return Math.pow( (double) val.charAt(0), (double) (val.charAt(0)-'A') );
+ return Math.pow( val.charAt(0), (val.charAt(0)-'A') );
}
}), SortField.FIELD_DOC });
assertMatches (full, queryA, sort, "JIHGFEDCBA");
@@ -432,7 +431,7 @@
public void setNextReader(IndexReader reader, int docBase, int numSlotsFull) throws IOException {
docValues = FieldCache.DEFAULT.getInts(reader, "parser", new FieldCache.IntParser() {
public final int parseInt(final String val) {
- return (int) (val.charAt(0)-'A') * 123456;
+ return (val.charAt(0)-'A') * 123456;
}
});
}
@@ -642,20 +641,20 @@
// test a variety of sorts using more than one searcher
public void testMultiSort() throws Exception {
MultiSearcher searcher = new MultiSearcher (new Searchable[] { searchX, searchY });
- runMultiSorts (searcher);
+ runMultiSorts(searcher, false);
}
// test a variety of sorts using a parallel multisearcher
public void testParallelMultiSort() throws Exception {
Searcher searcher = new ParallelMultiSearcher (new Searchable[] { searchX, searchY });
- runMultiSorts (searcher);
+ runMultiSorts(searcher, false);
}
// test a variety of sorts using a remote searcher
public void testRemoteSort() throws Exception {
Searchable searcher = getRemote();
MultiSearcher multi = new MultiSearcher (new Searchable[] { searcher });
- runMultiSorts (multi);
+ runMultiSorts(multi, true); // this runs on the full index
}
// test custom search when remote
@@ -813,9 +812,11 @@
// fillFields to true.
Sort[] sort = new Sort[] { new Sort(SortField.FIELD_DOC), new Sort() };
for (int i = 0; i < sort.length; i++) {
- TopDocsCollector tdc = TopFieldCollector.create(sort[i], 10, false, false, false);
+ Query q = new MatchAllDocsQuery();
+ TopDocsCollector tdc = TopFieldCollector.create(sort[i], 10, false,
+ false, false, q.scoresDocInOrder());
- full.search(new MatchAllDocsQuery(), tdc);
+ full.search(q, tdc);
ScoreDoc[] sd = tdc.topDocs().scoreDocs;
for (int j = 1; j < sd.length; j++) {
@@ -830,9 +831,11 @@
// Two Sort criteria to instantiate the multi/single comparators.
Sort[] sort = new Sort[] {new Sort(SortField.FIELD_DOC), new Sort() };
for (int i = 0; i < sort.length; i++) {
- TopDocsCollector tdc = TopFieldCollector.create(sort[i], 10, true, false, false);
+ Query q = new MatchAllDocsQuery();
+ TopDocsCollector tdc = TopFieldCollector.create(sort[i], 10, true, false,
+ false, q.scoresDocInOrder());
- full.search(new MatchAllDocsQuery(), tdc);
+ full.search(q, tdc);
TopDocs td = tdc.topDocs();
ScoreDoc[] sd = td.scoreDocs;
@@ -848,9 +851,11 @@
// Two Sort criteria to instantiate the multi/single comparators.
Sort[] sort = new Sort[] {new Sort(SortField.FIELD_DOC), new Sort() };
for (int i = 0; i < sort.length; i++) {
- TopDocsCollector tdc = TopFieldCollector.create(sort[i], 10, true, true, false);
+ Query q = new MatchAllDocsQuery();
+ TopDocsCollector tdc = TopFieldCollector.create(sort[i], 10, true, true,
+ false, q.scoresDocInOrder());
- full.search(new MatchAllDocsQuery(), tdc);
+ full.search(q, tdc);
TopDocs td = tdc.topDocs();
ScoreDoc[] sd = td.scoreDocs;
@@ -866,9 +871,11 @@
// Two Sort criteria to instantiate the multi/single comparators.
Sort[] sort = new Sort[] {new Sort(SortField.FIELD_DOC), new Sort() };
for (int i = 0; i < sort.length; i++) {
- TopDocsCollector tdc = TopFieldCollector.create(sort[i], 10, true, true, true);
+ Query q = new MatchAllDocsQuery();
+ TopDocsCollector tdc = TopFieldCollector.create(sort[i], 10, true, true,
+ true, q.scoresDocInOrder());
- full.search(new MatchAllDocsQuery(), tdc);
+ full.search(q, tdc);
TopDocs td = tdc.topDocs();
ScoreDoc[] sd = td.scoreDocs;
@@ -879,12 +886,74 @@
}
}
+ public void testOutOfOrderDocsScoringSort() throws Exception {
+
+ // Two Sort criteria to instantiate the multi/single comparators.
+ Sort[] sort = new Sort[] {new Sort(SortField.FIELD_DOC), new Sort() };
+ boolean[][] tfcOptions = new boolean[][] {
+ new boolean[] { false, false, false },
+ new boolean[] { false, false, true },
+ new boolean[] { false, true, false },
+ new boolean[] { false, true, true },
+ new boolean[] { true, false, false },
+ new boolean[] { true, false, true },
+ new boolean[] { true, true, false },
+ new boolean[] { true, true, true },
+ };
+ String[] actualTFCClasses = new String[] {
+ "OutOfOrderOneComparatorNonScoringCollector",
+ "OutOfOrderOneComparatorScoringMaxScoreCollector",
+ "OutOfOrderOneComparatorScoringNoMaxScoreCollector",
+ "OutOfOrderOneComparatorScoringMaxScoreCollector",
+ "OutOfOrderOneComparatorNonScoringCollector",
+ "OutOfOrderOneComparatorScoringMaxScoreCollector",
+ "OutOfOrderOneComparatorScoringNoMaxScoreCollector",
+ "OutOfOrderOneComparatorScoringMaxScoreCollector"
+ };
+
+ // Save the original value to set later.
+ boolean origVal = BooleanQuery.getAllowDocsOutOfOrder();
+
+ BooleanQuery.setAllowDocsOutOfOrder(true);
+
+ BooleanQuery bq = new BooleanQuery();
+ // Add a Query with SHOULD, since bw.scorer() returns BooleanScorer2
+ // which delegates to BS if there are no mandatory clauses.
+ bq.add(new MatchAllDocsQuery(), Occur.SHOULD);
+ // Set minNrShouldMatch to 1 so that BQ will not optimize rewrite to return
+ // the clause instead of BQ.
+ bq.setMinimumNumberShouldMatch(1);
+ try {
+ for (int i = 0; i < sort.length; i++) {
+ for (int j = 0; j < tfcOptions.length; j++) {
+ TopDocsCollector tdc = TopFieldCollector.create(sort[i], 10,
+ tfcOptions[j][0], tfcOptions[j][1], tfcOptions[j][2], bq
+ .scoresDocInOrder());
+
+ assertEquals(actualTFCClasses[j], tdc.getClass().getSimpleName());
+
+ full.search(bq, tdc);
+
+ TopDocs td = tdc.topDocs();
+ ScoreDoc[] sd = td.scoreDocs;
+ assertEquals(10, sd.length);
+ }
+ }
+ } finally {
+ // Whatever happens, reset BooleanQuery.allowDocsOutOfOrder to the
+ // original value. Don't set it to false in case the implementation in BQ
+ // will change some day.
+ BooleanQuery.setAllowDocsOutOfOrder(origVal);
+ }
+
+ }
+
public void testSortWithScoreAndMaxScoreTrackingNoResults() throws Exception {
// Two Sort criteria to instantiate the multi/single comparators.
Sort[] sort = new Sort[] {new Sort(SortField.FIELD_DOC), new Sort() };
for (int i = 0; i < sort.length; i++) {
- TopDocsCollector tdc = TopFieldCollector.create(sort[i], 10, true, true, true);
+ TopDocsCollector tdc = TopFieldCollector.create(sort[i], 10, true, true, true, true);
TopDocs td = tdc.topDocs();
assertEquals(0, td.totalHits);
assertTrue(Float.isNaN(td.getMaxScore()));
@@ -892,68 +961,73 @@
}
// runs a variety of sorts useful for multisearchers
- private void runMultiSorts (Searcher multi) throws Exception {
- sort.setSort (SortField.FIELD_DOC);
- assertMatchesPattern (multi, queryA, sort, "[AB]{2}[CD]{2}[EF]{2}[GH]{2}[IJ]{2}");
+ private void runMultiSorts(Searcher multi, boolean isFull) throws Exception {
+ sort.setSort(SortField.FIELD_DOC);
+ String expected = isFull ? "ABCDEFGHIJ" : "ACEGIBDFHJ";
+ assertMatches(multi, queryA, sort, expected);
- sort.setSort (new SortField ("int", SortField.INT));
- assertMatchesPattern (multi, queryA, sort, "IDHFGJ[ABE]{3}C");
+ sort.setSort(new SortField ("int", SortField.INT));
+ expected = isFull ? "IDHFGJABEC" : "IDHFGJAEBC";
+ assertMatches(multi, queryA, sort, expected);
- sort.setSort (new SortField[] {new SortField ("int", SortField.INT), SortField.FIELD_DOC});
- assertMatchesPattern (multi, queryA, sort, "IDHFGJ[AB]{2}EC");
+ sort.setSort(new SortField[] {new SortField ("int", SortField.INT), SortField.FIELD_DOC});
+ expected = isFull ? "IDHFGJABEC" : "IDHFGJAEBC";
+ assertMatches(multi, queryA, sort, expected);
- sort.setSort ("int");
- assertMatchesPattern (multi, queryA, sort, "IDHFGJ[AB]{2}EC");
+ sort.setSort("int");
+ expected = isFull ? "IDHFGJABEC" : "IDHFGJAEBC";
+ assertMatches(multi, queryA, sort, expected);
- sort.setSort (new SortField[] {new SortField ("float", SortField.FLOAT), SortField.FIELD_DOC});
- assertMatchesPattern (multi, queryA, sort, "GDHJ[CI]{2}EFAB");
+ sort.setSort(new SortField[] {new SortField ("float", SortField.FLOAT), SortField.FIELD_DOC});
+ assertMatches(multi, queryA, sort, "GDHJCIEFAB");
- sort.setSort ("float");
- assertMatchesPattern (multi, queryA, sort, "GDHJ[CI]{2}EFAB");
+ sort.setSort("float");
+ assertMatches(multi, queryA, sort, "GDHJCIEFAB");
- sort.setSort ("string");
- assertMatches (multi, queryA, sort, "DJAIHGFEBC");
+ sort.setSort("string");
+ assertMatches(multi, queryA, sort, "DJAIHGFEBC");
- sort.setSort ("int", true);
- assertMatchesPattern (multi, queryA, sort, "C[AB]{2}EJGFHDI");
+ sort.setSort("int", true);
+ expected = isFull ? "CABEJGFHDI" : "CAEBJGFHDI";
+ assertMatches(multi, queryA, sort, expected);
- sort.setSort ("float", true);
- assertMatchesPattern (multi, queryA, sort, "BAFE[IC]{2}JHDG");
+ sort.setSort("float", true);
+ assertMatches(multi, queryA, sort, "BAFECIJHDG");
- sort.setSort ("string", true);
- assertMatches (multi, queryA, sort, "CBEFGHIAJD");
+ sort.setSort("string", true);
+ assertMatches(multi, queryA, sort, "CBEFGHIAJD");
- sort.setSort (new SortField[] { new SortField ("string", Locale.US) });
- assertMatches (multi, queryA, sort, "DJAIHGFEBC");
+ sort.setSort(new SortField[] { new SortField ("string", Locale.US) });
+ assertMatches(multi, queryA, sort, "DJAIHGFEBC");
- sort.setSort (new SortField[] { new SortField ("string", Locale.US, true) });
- assertMatches (multi, queryA, sort, "CBEFGHIAJD");
+ sort.setSort(new SortField[] { new SortField ("string", Locale.US, true) });
+ assertMatches(multi, queryA, sort, "CBEFGHIAJD");
- sort.setSort (new String[] {"int","float"});
- assertMatches (multi, queryA, sort, "IDHFGJEABC");
+ sort.setSort(new String[] {"int","float"});
+ assertMatches(multi, queryA, sort, "IDHFGJEABC");
- sort.setSort (new String[] {"float","string"});
- assertMatches (multi, queryA, sort, "GDHJICEFAB");
+ sort.setSort(new String[] {"float","string"});
+ assertMatches(multi, queryA, sort, "GDHJICEFAB");
- sort.setSort ("int");
- assertMatches (multi, queryF, sort, "IZJ");
+ sort.setSort("int");
+ assertMatches(multi, queryF, sort, "IZJ");
- sort.setSort ("int", true);
- assertMatches (multi, queryF, sort, "JZI");
+ sort.setSort("int", true);
+ assertMatches(multi, queryF, sort, "JZI");
- sort.setSort ("float");
- assertMatches (multi, queryF, sort, "ZJI");
+ sort.setSort("float");
+ assertMatches(multi, queryF, sort, "ZJI");
- sort.setSort ("string");
- assertMatches (multi, queryF, sort, "ZJI");
+ sort.setSort("string");
+ assertMatches(multi, queryF, sort, "ZJI");
- sort.setSort ("string", true);
- assertMatches (multi, queryF, sort, "IJZ");
+ sort.setSort("string", true);
+ assertMatches(multi, queryF, sort, "IJZ");
}
// make sure the documents returned by the search match the expected list
- private void assertMatches (Searcher searcher, Query query, Sort sort, String expectedResult)
- throws IOException {
+ private void assertMatches(Searcher searcher, Query query, Sort sort,
+ String expectedResult) throws IOException {
//ScoreDoc[] result = searcher.search (query, null, 1000, sort).scoreDocs;
TopDocs hits = searcher.search (query, null, expectedResult.length(), sort);
ScoreDoc[] result = hits.scoreDocs;
@@ -970,23 +1044,6 @@
assertEquals (expectedResult, buff.toString());
}
- // make sure the documents returned by the search match the expected list pattern
- private void assertMatchesPattern (Searcher searcher, Query query, Sort sort, String pattern)
- throws IOException {
- ScoreDoc[] result = searcher.search (query, null, 1000, sort).scoreDocs;
- StringBuffer buff = new StringBuffer(10);
- int n = result.length;
- for (int i=0; i