Index: lucene/core/src/java/org/apache/lucene/search/ScoreCachingWrappingScorer.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- lucene/core/src/java/org/apache/lucene/search/ScoreCachingWrappingScorer.java (revision 1413821) +++ lucene/core/src/java/org/apache/lucene/search/ScoreCachingWrappingScorer.java (revision ) @@ -37,7 +37,16 @@ private final Scorer scorer; private int curDoc = -1; private float curScore; - + + /** Wraps the provided scorer in ScoreCachingWrappingScorer if needed. */ + public static ScoreCachingWrappingScorer wrap(Scorer scorer) { + if (scorer instanceof ScoreCachingWrappingScorer) { + return (ScoreCachingWrappingScorer) scorer; + } else { + return new ScoreCachingWrappingScorer(scorer); + } + } + /** Creates a new instance by wrapping the given scorer. */ public ScoreCachingWrappingScorer(Scorer scorer) { super(scorer.weight); Index: lucene/core/src/java/org/apache/lucene/search/FieldComparator.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- lucene/core/src/java/org/apache/lucene/search/FieldComparator.java (revision 1413821) +++ lucene/core/src/java/org/apache/lucene/search/FieldComparator.java (revision ) @@ -17,10 +17,7 @@ * limitations under the License. */ -import java.io.IOException; -import java.util.Comparator; - -import org.apache.lucene.index.AtomicReader; // javadocs +import org.apache.lucene.index.AtomicReader; import org.apache.lucene.index.AtomicReaderContext; import org.apache.lucene.index.DocValues; import org.apache.lucene.search.FieldCache.ByteParser; @@ -35,6 +32,9 @@ import org.apache.lucene.util.BytesRef; import org.apache.lucene.util.packed.PackedInts; +import java.io.IOException; +import java.util.Comparator; + /** * Expert: a FieldComparator compares hits so as to determine their * sort order when collecting the top results with {@link @@ -967,11 +967,7 @@ // wrap with a ScoreCachingWrappingScorer so that successive calls to // score() will not incur score computation over and // over again. - if (!(scorer instanceof ScoreCachingWrappingScorer)) { - this.scorer = new ScoreCachingWrappingScorer(scorer); - } else { - this.scorer = scorer; - } + scorer = ScoreCachingWrappingScorer.wrap(scorer); } @Override Index: lucene/queries/src/test/org/apache/lucene/queries/function/TestFunctionQuerySort.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- lucene/queries/src/test/org/apache/lucene/queries/function/TestFunctionQuerySort.java (revision 1413821) +++ lucene/queries/src/test/org/apache/lucene/queries/function/TestFunctionQuerySort.java (revision ) @@ -17,14 +17,16 @@ * limitations under the License. */ -import java.io.IOException; import org.apache.lucene.document.Document; import org.apache.lucene.document.Field; import org.apache.lucene.document.StringField; +import org.apache.lucene.index.AtomicReaderContext; import org.apache.lucene.index.IndexReader; import org.apache.lucene.index.IndexWriterConfig; import org.apache.lucene.index.RandomIndexWriter; +import org.apache.lucene.queries.function.docvalues.IntDocValues; import org.apache.lucene.queries.function.valuesource.IntFieldSource; +import org.apache.lucene.queries.function.valuesource.SingleFunction; import org.apache.lucene.search.FieldDoc; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.MatchAllDocsQuery; @@ -33,12 +35,19 @@ import org.apache.lucene.search.Sort; import org.apache.lucene.search.SortField; import org.apache.lucene.search.TopDocs; +import org.apache.lucene.search.TopFieldCollector; import org.apache.lucene.store.Directory; import org.apache.lucene.util.LuceneTestCase; +import java.io.IOException; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + /** Test that functionquery's getSortField() actually works */ public class TestFunctionQuerySort extends LuceneTestCase { + public static final int NUM_VALS = 5; + public void testSearchAfterWhenSortingByFunctionValues() throws IOException { Directory dir = newDirectory(); IndexWriterConfig iwc = newIndexWriterConfig(TEST_VERSION_CURRENT, null); @@ -50,7 +59,6 @@ doc.add(field); // Save docs unsorted (decreasing value n, n-1, ...) - final int NUM_VALS = 5; for (int val = NUM_VALS; val > 0; val--) { field.setStringValue(Integer.toString(val)); writer.addDocument(doc); @@ -61,8 +69,23 @@ writer.close(); IndexSearcher searcher = new IndexSearcher(reader); + // Lets do a quick search test using FunctionQuery sort score desc + { + MonitorValueSource src = new MonitorValueSource(new IntFieldSource("value")); + Query q = new FunctionQuery(src); + Sort orderBy = new Sort(); + SortField sf = new SortField(null, SortField.Type.SCORE, true); + orderBy.setSort(sf); + TopFieldCollector collector = TopFieldCollector.create( + orderBy.rewrite(searcher), reader.maxDoc(), false, true, true, true + ); + searcher.search(q, collector); + verifySortHits(reader, src, collector.topDocs()); + } + //good; continue testing... + // Get ValueSource from FieldCache - IntFieldSource src = new IntFieldSource("value"); + MonitorValueSource src = new MonitorValueSource(new IntFieldSource("value")); // ...and make it a sort criterion SortField sf = src.getSortField(false).rewrite(searcher); Sort orderBy = new Sort(sf); @@ -70,13 +93,7 @@ // Get hits sorted by our FunctionValues (ascending values) Query q = new MatchAllDocsQuery(); TopDocs hits = searcher.search(q, Integer.MAX_VALUE, orderBy); - assertEquals(NUM_VALS, hits.scoreDocs.length); - // Verify that sorting works in general - int i = 0; - for (ScoreDoc hit : hits.scoreDocs) { - int valueFromDoc = Integer.parseInt(reader.document(hit.doc).get("value")); - assertEquals(++i, valueFromDoc); - } + verifySortHits(reader, src, hits); // Now get hits after hit #2 using IS.searchAfter() int afterIdx = 1; @@ -95,5 +112,49 @@ } reader.close(); dir.close(); + } + + private void verifySortHits(IndexReader reader, MonitorValueSource src, TopDocs hits) throws IOException { + assertEquals(NUM_VALS, hits.scoreDocs.length); + assertEquals(NUM_VALS, src.callCount.get()); + // Verify that sorting works in general + int i = 0; + for (ScoreDoc hit : hits.scoreDocs) { + int valueFromDoc = Integer.parseInt(reader.document(hit.doc).get("value")); + assertEquals(++i, valueFromDoc); + } + } + + /** Wraps a ValueSource to increment a counter each time a value is retrieved. */ + static class MonitorValueSource extends SingleFunction { + + final AtomicInteger callCount = new AtomicInteger(); + + public MonitorValueSource(ValueSource source) { + super(source); + } + + @Override + protected String name() { + return "monitor"; + } + + @Override + public FunctionValues getValues(Map context, AtomicReaderContext readerContext) throws IOException { + final FunctionValues vals = source.getValues(context, readerContext); + return new IntDocValues(this) { + + @Override + public int intVal(int doc) { + callCount.incrementAndGet(); + return vals.intVal(doc); + } + + @Override + public String toString(int doc) { + return name() + '(' + vals.toString(doc) + ')'; + } + }; + } } } Index: lucene/core/src/java/org/apache/lucene/search/TopFieldCollector.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- lucene/core/src/java/org/apache/lucene/search/TopFieldCollector.java (revision 1413821) +++ lucene/core/src/java/org/apache/lucene/search/TopFieldCollector.java (revision ) @@ -17,12 +17,12 @@ * limitations under the License. */ -import java.io.IOException; - import org.apache.lucene.index.AtomicReaderContext; import org.apache.lucene.search.FieldValueHitQueue.Entry; import org.apache.lucene.util.PriorityQueue; +import java.io.IOException; + /** * A {@link Collector} that sorts by {@link SortField} using * {@link FieldComparator}s. @@ -206,6 +206,7 @@ @Override public void setScorer(Scorer scorer) throws IOException { + scorer = ScoreCachingWrappingScorer.wrap(scorer); this.scorer = scorer; comparator.setScorer(scorer); } @@ -320,6 +321,7 @@ @Override public void setScorer(Scorer scorer) throws IOException { + scorer = ScoreCachingWrappingScorer.wrap(scorer); this.scorer = scorer; super.setScorer(scorer); }