Index: src/java/org/apache/lucene/search/HitQueue.java =================================================================== --- src/java/org/apache/lucene/search/HitQueue.java (revision 911727) +++ src/java/org/apache/lucene/search/HitQueue.java (working copy) @@ -78,7 +78,7 @@ @Override protected final boolean lessThan(ScoreDoc hitA, ScoreDoc hitB) { - if (hitA.score == hitB.score) + if (hitA.score == hitB.score || hitA.doc == Integer.MAX_VALUE || hitB.doc == Integer.MAX_VALUE) return hitA.doc > hitB.doc; else return hitA.score < hitB.score; Index: src/java/org/apache/lucene/search/TopScoreDocCollector.java =================================================================== --- src/java/org/apache/lucene/search/TopScoreDocCollector.java (revision 911727) +++ src/java/org/apache/lucene/search/TopScoreDocCollector.java (working copy) @@ -28,11 +28,6 @@ * 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. - * - *

NOTE: The values Float.Nan, - * Float.NEGATIVE_INFINITY and Float.POSITIVE_INFINITY are - * not valid scores. This collector will not properly - * collect hits with such scores. */ public abstract class TopScoreDocCollector extends TopDocsCollector { @@ -46,7 +41,7 @@ public void collect(int doc) throws IOException { float score = scorer.score(); totalHits++; - if (score <= pqTop.score) { + if (score <= pqTop.score && pqTop.doc != Integer.MAX_VALUE) { // 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. Index: src/test/org/apache/lucene/search/TestTopScoreDocCollector.java =================================================================== --- src/test/org/apache/lucene/search/TestTopScoreDocCollector.java (revision 911727) +++ src/test/org/apache/lucene/search/TestTopScoreDocCollector.java (working copy) @@ -24,6 +24,7 @@ import org.apache.lucene.store.Directory; import org.apache.lucene.store.RAMDirectory; import org.apache.lucene.util.LuceneTestCase; +import org.apache.lucene.search.function.CustomScoreQuery; public class TestTopScoreDocCollector extends LuceneTestCase { @@ -35,7 +36,6 @@ } public void testOutOfOrderCollection() throws Exception { - Directory dir = new RAMDirectory(); IndexWriter writer = new IndexWriter(dir, null, MaxFieldLength.UNLIMITED); for (int i = 0; i < 10; i++) { @@ -50,13 +50,6 @@ "InOrderTopScoreDocCollector" }; - 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); IndexSearcher searcher = new IndexSearcher(dir, true); for (int i = 0; i < inOrder.length; i++) { TopDocsCollector tdc = TopScoreDocCollector.create(3, inOrder[i]); @@ -72,4 +65,64 @@ } } + public void testSpecialValues() throws Exception { + Directory dir = new RAMDirectory(); + IndexWriter writer = new IndexWriter(dir, null, MaxFieldLength.UNLIMITED); + for (int i = 0; i < 50; i++) { + writer.addDocument(new Document()); + } + writer.commit(); + writer.close(); + + boolean[] inOrder = new boolean[] { false, true }; + + IndexSearcher searcher = new IndexSearcher(dir, true); + for (int i = 0; i < inOrder.length; i++) { + // use a larger numDocs value to still have sentinels after query in queue!!! + + // fist check all values for validity, incl NaN + TopDocsCollector tdc = TopScoreDocCollector.create(100, inOrder[i]); + searcher.search(new CustomScoreQuery(new MatchAllDocsQuery()) { + @Override + public float customScore(int doc, float subQueryScore, float valSrcScore) { + switch (doc % 4) { + case 1: return Float.NEGATIVE_INFINITY; + case 2: return Float.POSITIVE_INFINITY; + case 3: return Float.NaN; + default: return 1.0f; + } + } + }, tdc); + + ScoreDoc[] sd = tdc.topDocs().scoreDocs; + assertEquals(50, sd.length); + for (int j = 0; j < sd.length; j++) { + assertTrue("Invalid docid="+sd[j].doc+" for score="+sd[j].score, sd[j].doc >=0 && sd[j].doc != Integer.MAX_VALUE); + } + + // then only test infinity values and compare the scored docs (if decreasing) + tdc = TopScoreDocCollector.create(100, inOrder[i]); + searcher.search(new CustomScoreQuery(new MatchAllDocsQuery()) { + @Override + public float customScore(int doc, float subQueryScore, float valSrcScore) { + switch (doc % 3) { + case 1: return Float.NEGATIVE_INFINITY; + case 2: return Float.POSITIVE_INFINITY; + default: return (float) (doc - 25); + } + } + }, tdc); + + sd = tdc.topDocs().scoreDocs; + assertEquals(50, sd.length); + for (int j = 1; j < sd.length; j++) { + //System.out.println("inOrder="+inOrder[i]+" doc="+sd[j].doc+" score="+sd[j].score); + assertTrue("Score must be decreasing", sd[j-1].score >= sd[j].score); + if (sd[j-1].score == sd[j].score) { + assertTrue("docid must be increasing when score unchanged", sd[j-1].doc < sd[j].doc); + } + } + } + } + }