diff --git a/lucene/CHANGES.txt b/lucene/CHANGES.txt index c3127c169aa..29d5a6b5ab2 100644 --- a/lucene/CHANGES.txt +++ b/lucene/CHANGES.txt @@ -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 diff --git a/lucene/core/src/java/org/apache/lucene/search/FieldComparator.java b/lucene/core/src/java/org/apache/lucene/search/FieldComparator.java index 5013f2f67d6..e6d96b15d0e 100644 --- a/lucene/core/src/java/org/apache/lucene/search/FieldComparator.java +++ b/lucene/core/src/java/org/apache/lucene/search/FieldComparator.java @@ -190,6 +190,10 @@ public abstract class FieldComparator { } } + /** 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 extends FieldComparator { protected final T missingValue; protected final String field; @@ -274,9 +278,19 @@ public abstract class FieldComparator { 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 */ public static final class DoubleComparator extends NumericComparator { @@ -351,6 +365,24 @@ public abstract class FieldComparator { 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 { 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 { 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 { 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 { 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 { 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 { 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 { @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 { // 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 { 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 { 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 { } 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 { 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 { 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 { } 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 { 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) { diff --git a/lucene/core/src/java/org/apache/lucene/search/IndexSearcher.java b/lucene/core/src/java/org/apache/lucene/search/IndexSearcher.java index 9ed8a9f5b5e..3d79dbe16aa 100644 --- a/lucene/core/src/java/org/apache/lucene/search/IndexSearcher.java +++ b/lucene/core/src/java/org/apache/lucene/search/IndexSearcher.java @@ -304,14 +304,51 @@ public class IndexSearcher { * sort. * *

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 n hits for query, applying + * filter if non-null, and sorting the hits by the criteria in + * sort. If doDocScores is true + * then the score of each hit will be computed and + * returned. If doMaxScore is + * true 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 n + * hits for query, applying filter if non-null, + * where all results are after a previous result (after). + *

+ * By passing the bottom result from a previous page as after, + * 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 n + * hits for query where all results are after a previous + * result (after). + *

+ * By passing the bottom result from a previous page as after, + * 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 n + * hits for query where all results are after a previous + * result (after), allowing control over + * whether hit scores and max score should be computed. + *

+ * By passing the bottom result from a previous page as after, + * this method can be used for efficient 'deep-paging' across potentially + * large result sets. If doDocScores is true + * then the score of each hit will be computed and + * returned. If doMaxScore is + * true 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 n @@ -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 n hits for query and sorting the hits * by the criteria in sort. * @@ -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)}.

*/ - 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 runner = new ExecutionHelper(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. - * - *

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)}.

*/ - 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(); } diff --git a/lucene/core/src/java/org/apache/lucene/search/TopDocs.java b/lucene/core/src/java/org/apache/lucene/search/TopDocs.java index 0e5bc0fb96e..e8af0f8d215 100644 --- a/lucene/core/src/java/org/apache/lucene/search/TopDocs.java +++ b/lucene/core/src/java/org/apache/lucene/search/TopDocs.java @@ -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. */ diff --git a/lucene/core/src/java/org/apache/lucene/search/TopFieldCollector.java b/lucene/core/src/java/org/apache/lucene/search/TopFieldCollector.java index cc01ce8ea05..f14cabda865 100644 --- a/lucene/core/src/java/org/apache/lucene/search/TopFieldCollector.java +++ b/lucene/core/src/java/org/apache/lucene/search/TopFieldCollector.java @@ -843,6 +843,166 @@ public abstract class TopFieldCollector extends TopDocsCollector { } + /* + * 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 queue; + final boolean trackDocScores; + final boolean trackMaxScore; + final FieldDoc after; + int afterDoc; + + public PagingFieldCollector( + FieldValueHitQueue 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 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 { 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. + * + *

NOTE: The instances returned by this method + * pre-allocate a full array of length + * numHits. + * + * @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 + * trackDocScores 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 { } FieldValueHitQueue 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); } } diff --git a/lucene/core/src/java/org/apache/lucene/search/TopFieldDocs.java b/lucene/core/src/java/org/apache/lucene/search/TopFieldDocs.java index fc4c2331c3b..aa17421b19e 100644 --- a/lucene/core/src/java/org/apache/lucene/search/TopFieldDocs.java +++ b/lucene/core/src/java/org/apache/lucene/search/TopFieldDocs.java @@ -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; + } } \ No newline at end of file diff --git a/lucene/core/src/test/org/apache/lucene/search/JustCompileSearch.java b/lucene/core/src/test/org/apache/lucene/search/JustCompileSearch.java index 7774da63fc1..e3c284564ae 100644 --- a/lucene/core/src/test/org/apache/lucene/search/JustCompileSearch.java +++ b/lucene/core/src/test/org/apache/lucene/search/JustCompileSearch.java @@ -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 { diff --git a/lucene/core/src/test/org/apache/lucene/search/TestElevationComparator.java b/lucene/core/src/test/org/apache/lucene/search/TestElevationComparator.java index fa04a5b52c4..688bc921f58 100644 --- a/lucene/core/src/test/org/apache/lucene/search/TestElevationComparator.java +++ b/lucene/core/src/test/org/apache/lucene/search/TestElevationComparator.java @@ -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; + } }; } } diff --git a/lucene/core/src/test/org/apache/lucene/search/TestSearchAfter.java b/lucene/core/src/test/org/apache/lucene/search/TestSearchAfter.java index 0fa6f22dbeb..ed8b793f901 100644 --- a/lucene/core/src/test/org/apache/lucene/search/TestSearchAfter.java +++ b/lucene/core/src/test/org/apache/lucene/search/TestSearchAfter.java @@ -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); + } } } } diff --git a/lucene/core/src/test/org/apache/lucene/search/TestSort.java b/lucene/core/src/test/org/apache/lucene/search/TestSort.java index 759ad15af78..a63af54d49a 100644 --- a/lucene/core/src/test/org/apache/lucene/search/TestSort.java +++ b/lucene/core/src/test/org/apache/lucene/search/TestSort.java @@ -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 value) { + return -1; + } else { + return 0; + } + } } } diff --git a/lucene/queries/src/test/org/apache/lucene/queries/function/TestValueSources.java b/lucene/queries/src/test/org/apache/lucene/queries/function/TestValueSources.java index a8e208479c9..de8a33ffd2b 100644 --- a/lucene/queries/src/test/org/apache/lucene/queries/function/TestValueSources.java +++ b/lucene/queries/src/test/org/apache/lucene/queries/function/TestValueSources.java @@ -132,7 +132,6 @@ public class TestValueSources extends LuceneTestCase { reader = iw.getReader(); searcher = newSearcher(reader); - searcher.setDefaultFieldSortScoring(true, true); iw.close(); } diff --git a/lucene/sandbox/src/java/org/apache/lucene/sandbox/queries/SlowCollatedStringComparator.java b/lucene/sandbox/src/java/org/apache/lucene/sandbox/queries/SlowCollatedStringComparator.java index 4170bdd1dfd..4bc543b6956 100644 --- a/lucene/sandbox/src/java/org/apache/lucene/sandbox/queries/SlowCollatedStringComparator.java +++ b/lucene/sandbox/src/java/org/apache/lucene/sandbox/queries/SlowCollatedStringComparator.java @@ -118,4 +118,16 @@ public final class SlowCollatedStringComparator extends FieldComparator 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); + } } diff --git a/solr/core/src/java/org/apache/solr/handler/component/QueryElevationComponent.java b/solr/core/src/java/org/apache/solr/handler/component/QueryElevationComponent.java index 2104cb722c9..1da847d8da8 100644 --- a/solr/core/src/java/org/apache/solr/handler/component/QueryElevationComponent.java +++ b/solr/core/src/java/org/apache/solr/handler/component/QueryElevationComponent.java @@ -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 + } }; } + } } -} - - diff --git a/solr/core/src/java/org/apache/solr/schema/RandomSortField.java b/solr/core/src/java/org/apache/solr/schema/RandomSortField.java index 69ca740bd02..9640f947338 100644 --- a/solr/core/src/java/org/apache/solr/schema/RandomSortField.java +++ b/solr/core/src/java/org/apache/solr/schema/RandomSortField.java @@ -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(); + } }; } }; diff --git a/solr/core/src/java/org/apache/solr/search/MissingStringLastComparatorSource.java b/solr/core/src/java/org/apache/solr/search/MissingStringLastComparatorSource.java index ec1363abf1b..65b7ad596ed 100644 --- a/solr/core/src/java/org/apache/solr/search/MissingStringLastComparatorSource.java +++ b/solr/core/src/java/org/apache/solr/search/MissingStringLastComparatorSource.java @@ -121,6 +121,11 @@ class TermOrdValComparator_SML extends FieldComparator { 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 { 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: