second.shardIndex) {
+ //System.out.println(" return tb false");
+ return false;
+ } else {
+ // Tie break in same shard: resolve however the
+ // shard had resolved it:
+ //System.out.println(" return tb " + (first.hitIndex < second.hitIndex));
+ assert first.hitIndex != second.hitIndex;
+ return first.hitIndex < second.hitIndex;
+ }
+ }
+ }
+
+ /** Returned from {@link #merge}, to include the merged
+ * TopDocs as well as the reference to which original
+ * TopDocs shard each hit came from.
+ *
+ * @lucene.experimental */
+ public static class TopDocsAndShards extends TopDocs {
+
+ /** Parallel array matching hits.scoreDocs */
+ public final int[] shardIndex;
+
+ public TopDocsAndShards(int totalHits, ScoreDoc[] scoreDocs, float maxScore, int[] shardIndex) {
+ super(totalHits, scoreDocs, maxScore);
+ this.shardIndex = shardIndex;
+ }
+ }
+
+ /** Returns a new TopDocs, containing topN results across
+ * the provided TopDocs, sorting by the specified {@link
+ * Sort}. Each of the TopDocs must have been sorted by
+ * the same Sort, and sort field values must have been
+ * filled (ie, fillFields=true must be
+ * passed to {@link
+ * TopFieldCollector#create}.
+ *
+ * Pass sort=null to merge sort by score descending.
+ *
+ * @lucene.experimental */
+ public static TopDocsAndShards merge(Sort sort, int topN, TopDocs[] shardHits) throws IOException {
+
+ final PriorityQueue queue;
+ if (sort == null) {
+ queue = new ScoreMergeSortQueue(shardHits);
+ } else {
+ queue = new MergeSortQueue(sort, shardHits);
+ }
+
+ int totalHitCount = 0;
+ float maxScore = Float.MIN_VALUE;
+ for(int shardIDX=0;shardIDX 0) {
+ totalHitCount += shard.totalHits;
+ queue.add(new ShardRef(shardIDX));
+ maxScore = Math.max(maxScore, shard.getMaxScore());
+ //System.out.println(" maxScore now " + maxScore + " vs " + shard.getMaxScore());
+ }
+ }
+
+ final ScoreDoc[] hits = new ScoreDoc[Math.min(topN, totalHitCount)];
+ final int[] shardIndex = new int[hits.length];
+
+ int hitUpto = 0;
+ while(hitUpto < hits.length) {
+ assert queue.size() > 0;
+ ShardRef ref = queue.pop();
+ hits[hitUpto] = shardHits[ref.shardIndex].scoreDocs[ref.hitIndex++];
+ shardIndex[hitUpto] = ref.shardIndex;
+
+ //System.out.println(" hitUpto=" + hitUpto);
+ //System.out.println(" doc=" + hits[hitUpto].doc + " score=" + hits[hitUpto].score);
+
+ hitUpto++;
+
+ if (ref.hitIndex < shardHits[ref.shardIndex].scoreDocs.length) {
+ // Not done with this these TopDocs yet:
+ queue.add(ref);
+ }
+ }
+
+ return new TopDocsAndShards(totalHitCount, hits, maxScore, shardIndex);
+ }
}
Index: lucene/src/test-framework/org/apache/lucene/index/RandomIndexWriter.java
--- lucene/src/test-framework/org/apache/lucene/index/RandomIndexWriter.java Sun Jun 12 13:27:05 2011 -0400
+++ lucene/src/test-framework/org/apache/lucene/index/RandomIndexWriter.java Sun Jun 12 13:30:26 2011 -0400
@@ -308,16 +308,24 @@
return getReader(true);
}
+ private boolean doRandomOptimize = true;
+
+ public void setDoRandomOptimize(boolean v) {
+ doRandomOptimize = v;
+ }
+
private void doRandomOptimize() throws IOException {
- final int segCount = w.getSegmentCount();
- if (r.nextBoolean() || segCount == 0) {
- // full optimize
- w.optimize();
- } else {
- // partial optimize
- final int limit = _TestUtil.nextInt(r, 1, segCount);
- w.optimize(limit);
- assert w.getSegmentCount() <= limit: "limit=" + limit + " actual=" + w.getSegmentCount();
+ if (doRandomOptimize) {
+ final int segCount = w.getSegmentCount();
+ if (r.nextBoolean() || segCount == 0) {
+ // full optimize
+ w.optimize();
+ } else {
+ // partial optimize
+ final int limit = _TestUtil.nextInt(r, 1, segCount);
+ w.optimize(limit);
+ assert w.getSegmentCount() <= limit: "limit=" + limit + " actual=" + w.getSegmentCount();
+ }
}
switchDoDocValues();
}
Index: lucene/src/test-framework/org/apache/lucene/util/LuceneTestCase.java
--- lucene/src/test-framework/org/apache/lucene/util/LuceneTestCase.java Sun Jun 12 13:27:05 2011 -0400
+++ lucene/src/test-framework/org/apache/lucene/util/LuceneTestCase.java Sun Jun 12 13:30:26 2011 -0400
@@ -1484,4 +1484,5 @@
@Ignore("just a hack")
public final void alwaysIgnoredTestMethod() {}
+
}
Index: lucene/src/test-framework/org/apache/lucene/util/_TestUtil.java
--- lucene/src/test-framework/org/apache/lucene/util/_TestUtil.java Sun Jun 12 13:27:05 2011 -0400
+++ lucene/src/test-framework/org/apache/lucene/util/_TestUtil.java Sun Jun 12 13:30:26 2011 -0400
@@ -27,10 +27,10 @@
import java.io.PrintStream;
import java.lang.reflect.Method;
import java.util.Enumeration;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.Random;
-import java.util.Map;
-import java.util.HashMap;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
@@ -46,6 +46,9 @@
import org.apache.lucene.index.TieredMergePolicy;
import org.apache.lucene.index.codecs.Codec;
import org.apache.lucene.index.codecs.CodecProvider;
+import org.apache.lucene.search.FieldDoc;
+import org.apache.lucene.search.ScoreDoc;
+import org.apache.lucene.search.TopDocs;
import org.apache.lucene.store.Directory;
import org.junit.Assert;
@@ -467,4 +470,24 @@
newName.append(suffix);
return new File(directory, newName.toString());
}
+
+ public static void assertEquals(TopDocs expected, TopDocs actual) {
+ Assert.assertEquals("wrong total hits", expected.totalHits, actual.totalHits);
+ Assert.assertEquals("wrong maxScore", expected.getMaxScore(), actual.getMaxScore(), 0.0);
+ Assert.assertEquals("wrong hit count", expected.scoreDocs.length, actual.scoreDocs.length);
+ for(int hitIDX=0;hitIDX {
@Override
public int compare(int slot1, int slot2) {
@@ -132,10 +132,10 @@
}
@Override
- public Comparable> value(int slot) {
+ public Object value(int slot) {
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
}
-
+
}
static final class JustCompileFieldComparatorSource extends FieldComparatorSource {
Index: lucene/src/test/org/apache/lucene/search/TestElevationComparator.java
--- lucene/src/test/org/apache/lucene/search/TestElevationComparator.java Sun Jun 12 13:27:05 2011 -0400
+++ lucene/src/test/org/apache/lucene/search/TestElevationComparator.java Sun Jun 12 13:30:26 2011 -0400
@@ -139,7 +139,7 @@
@Override
public FieldComparator newComparator(final String fieldname, final int numHits, int sortPos, boolean reversed) throws IOException {
- return new FieldComparator() {
+ return new FieldComparator() {
FieldCache.DocTermsIndex idIndex;
private final int[] values = new int[numHits];
@@ -184,7 +184,7 @@
}
@Override
- public Comparable> value(int slot) {
+ public Integer value(int slot) {
return Integer.valueOf(values[slot]);
}
};
Index: lucene/src/test/org/apache/lucene/search/TestSort.java
--- lucene/src/test/org/apache/lucene/search/TestSort.java Sun Jun 12 13:27:05 2011 -0400
+++ lucene/src/test/org/apache/lucene/search/TestSort.java Sun Jun 12 13:30:26 2011 -0400
@@ -511,7 +511,7 @@
assertMatches (empty, queryX, sort, "");
}
- static class MyFieldComparator extends FieldComparator {
+ static class MyFieldComparator extends FieldComparator {
int[] docValues;
int[] slotValues;
int bottomValue;
@@ -527,6 +527,7 @@
@Override
public int compare(int slot1, int slot2) {
+ // values are small enough that overflow won't happen
return slotValues[slot1] - slotValues[slot2];
}
@@ -553,7 +554,7 @@
}
@Override
- public Comparable> value(int slot) {
+ public Integer value(int slot) {
return Integer.valueOf(slotValues[slot]);
}
}
Index: lucene/src/test/org/apache/lucene/search/TestTopDocsMerge.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ lucene/src/test/org/apache/lucene/search/TestTopDocsMerge.java Sun Jun 12 13:30:26 2011 -0400
@@ -0,0 +1,244 @@
+package org.apache.lucene.search;
+
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.Field;
+import org.apache.lucene.document.NumericField;
+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.LuceneTestCase;
+import org.apache.lucene.util.ReaderUtil;
+import org.apache.lucene.util._TestUtil;
+
+public class TestTopDocsMerge extends LuceneTestCase {
+
+ private static class ShardSearcher extends IndexSearcher {
+ private final IndexReader.AtomicReaderContext[] ctx;
+
+ public ShardSearcher(IndexReader.AtomicReaderContext ctx, IndexReader.ReaderContext parent) {
+ super(parent);
+ this.ctx = new IndexReader.AtomicReaderContext[] {ctx};
+ }
+
+ public void search(Weight weight, Collector collector) throws IOException {
+ search(ctx, weight, null, collector);
+ }
+
+ public TopDocs search(Weight weight, int topN) throws IOException {
+ return search(ctx, weight, null, topN);
+ }
+
+ @Override
+ public String toString() {
+ return "ShardSearcher(" + ctx[0] + ")";
+ }
+ }
+
+ public void testSort() throws Exception {
+
+ IndexReader reader = null;
+ Directory dir = null;
+
+ final int numDocs = atLeast(1000);
+ //final int numDocs = atLeast(50);
+
+ final String[] tokens = new String[] {"a", "b", "c", "d", "e"};
+
+ if (VERBOSE) {
+ System.out.println("TEST: make index");
+ }
+
+ {
+ dir = newDirectory();
+ final RandomIndexWriter w = new RandomIndexWriter(random, dir);
+ // w.setDoRandomOptimize(false);
+
+ // w.w.getConfig().setMaxBufferedDocs(atLeast(100));
+
+ final String[] content = new String[atLeast(20)];
+
+ for(int contentIDX=0;contentIDX searchGroup = new SearchGroup();
searchGroup.groupValue = group.groupValue;
if (fillFields) {
- searchGroup.sortValues = new Comparable[sortFieldCount];
+ searchGroup.sortValues = new Object[sortFieldCount];
for(int sortFieldIDX=0;sortFieldIDXnull. */
- public Comparable[] sortValues;
+ /** The sort values used during sorting. These are the
+ * groupSort field values of the highest rank document
+ * (by the groupSort) within the group. Can be
+ * null if fillFields=false had
+ * been passed to {@link AbstractFirstPassGroupingCollector#getTopGroups} */
+ public Object[] sortValues;
+
+ @Override
+ public String toString() {
+ return("SearchGroup(groupValue=" + groupValue + " sortValues=" + Arrays.toString(sortValues) + ")");
+ }
+
+ private static class ShardIter {
+ public final Iterator> iter;
+ public final int shardIndex;
+
+ public ShardIter(Collection> shard, int shardIndex) {
+ this.shardIndex = shardIndex;
+ iter = shard.iterator();
+ assert iter.hasNext();
+ }
+
+ public SearchGroup next() {
+ assert iter.hasNext();
+ final SearchGroup group = iter.next();
+ if (group.sortValues == null) {
+ throw new IllegalArgumentException("group.sortValues is null; you must pass fillFields=true to the first pass collector");
+ }
+ return group;
+ }
+
+ @Override
+ public String toString() {
+ return "ShardIter(shard=" + shardIndex + ")";
+ }
+ }
+
+ // Holds all shards currently on the same group
+ private static class MergedGroup {
+
+ // groupValue may be null!
+ public final T groupValue;
+
+ public Object[] topValues;
+ public final List> shards = new ArrayList>();
+ public int minShardIndex;
+ public boolean processed;
+ public boolean inQueue;
+
+ public MergedGroup(T groupValue) {
+ this.groupValue = groupValue;
+ }
+
+ // Only for assert
+ private boolean neverEquals(Object _other) {
+ if (_other instanceof MergedGroup) {
+ MergedGroup other = (MergedGroup) _other;
+ if (groupValue == null) {
+ assert other.groupValue != null;
+ } else {
+ assert !groupValue.equals(other.groupValue);
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public boolean equals(Object _other) {
+ // We never have another MergedGroup instance with
+ // same groupValue
+ assert neverEquals(_other);
+
+ if (_other instanceof MergedGroup) {
+ MergedGroup other = (MergedGroup) _other;
+ if (groupValue == null) {
+ return other == null;
+ } else {
+ return groupValue.equals(other);
+ }
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ if (groupValue == null) {
+ return 0;
+ } else {
+ return groupValue.hashCode();
+ }
+ }
+ }
+
+ private static class GroupComparator implements Comparator> {
+
+ public final FieldComparator[] comparators;
+ public final int[] reversed;
+
+ public GroupComparator(Sort groupSort) throws IOException {
+ final SortField[] sortFields = groupSort.getSort();
+ comparators = new FieldComparator[sortFields.length];
+ reversed = new int[sortFields.length];
+ for (int compIDX = 0; compIDX < sortFields.length; compIDX++) {
+ final SortField sortField = sortFields[compIDX];
+ comparators[compIDX] = sortField.getComparator(1, compIDX);
+ reversed[compIDX] = sortField.getReverse() ? -1 : 1;
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public int compare(MergedGroup group, MergedGroup other) {
+ if (group == other) {
+ return 0;
+ }
+ //System.out.println("compare group=" + group + " other=" + other);
+ final Object[] groupValues = group.topValues;
+ final Object[] otherValues = other.topValues;
+ //System.out.println(" groupValues=" + groupValues + " otherValues=" + otherValues);
+ for (int compIDX = 0;compIDX < comparators.length; compIDX++) {
+ final int c = reversed[compIDX] * comparators[compIDX].compareValues(groupValues[compIDX],
+ otherValues[compIDX]);
+ if (c != 0) {
+ return c;
+ }
+ }
+
+ // Tie break by min shard index:
+ assert group.minShardIndex != other.minShardIndex;
+ return group.minShardIndex - other.minShardIndex;
+ }
+ }
+
+ private static class GroupMerger {
+
+ private final GroupComparator groupComp;
+ private final SortedSet> queue;
+ private final Map> groupsSeen;
+
+ public GroupMerger(Sort groupSort) throws IOException {
+ groupComp = new GroupComparator(groupSort);
+ queue = new TreeSet>(groupComp);
+ groupsSeen = new HashMap>();
+ }
+
+ @SuppressWarnings("unchecked")
+ private void updateNextGroup(int topN, ShardIter shard) {
+ while(shard.iter.hasNext()) {
+ final SearchGroup group = shard.next();
+ MergedGroup mergedGroup = groupsSeen.get(group.groupValue);
+ final boolean isNew = mergedGroup == null;
+ //System.out.println(" next group=" + (group.groupValue == null ? "null" : ((BytesRef) group.groupValue).utf8ToString()) + " sort=" + Arrays.toString(group.sortValues));
+
+ if (isNew) {
+ // Start a new group:
+ //System.out.println(" new");
+ mergedGroup = new MergedGroup(group.groupValue);
+ mergedGroup.minShardIndex = shard.shardIndex;
+ assert group.sortValues != null;
+ mergedGroup.topValues = group.sortValues;
+ groupsSeen.put(group.groupValue, mergedGroup);
+ mergedGroup.inQueue = true;
+ queue.add(mergedGroup);
+ } else if (mergedGroup.processed) {
+ // This shard produced a group that we already
+ // processed; move on to next group...
+ continue;
+ } else {
+ //System.out.println(" old");
+ boolean competes = false;
+ for(int compIDX=0;compIDX 0) {
+ // Definitely does not compete
+ break;
+ } else if (compIDX == groupComp.comparators.length-1) {
+ if (shard.shardIndex < mergedGroup.minShardIndex) {
+ competes = true;
+ }
+ }
+ }
+
+ //System.out.println(" competes=" + competes);
+
+ if (competes) {
+ // Group's sort changed -- remove & re-insert
+ if (mergedGroup.inQueue) {
+ queue.remove(mergedGroup);
+ }
+ mergedGroup.topValues = group.sortValues;
+ mergedGroup.minShardIndex = shard.shardIndex;
+ queue.add(mergedGroup);
+ mergedGroup.inQueue = true;
+ }
+ }
+
+ mergedGroup.shards.add(shard);
+ break;
+ }
+
+ // Prune un-competitive groups:
+ while(queue.size() > topN) {
+ // TODO java 1.6: .pollLast
+ final MergedGroup group = queue.last();
+ //System.out.println("PRUNE: " + group);
+ queue.remove(group);
+ group.inQueue = false;
+ }
+ }
+
+ public Collection> merge(List>> shards, int offset, int topN) {
+
+ final int maxQueueSize = offset + topN;
+
+ //System.out.println("merge");
+ // Init queue:
+ for(int shardIDX=0;shardIDX> shard = shards.get(shardIDX);
+ if (!shard.isEmpty()) {
+ //System.out.println(" insert shard=" + shardIDX);
+ updateNextGroup(maxQueueSize, new ShardIter(shard, shardIDX));
+ }
+ }
+
+ // Pull merged topN groups:
+ final List> newTopGroups = new ArrayList>();
+
+ int count = 0;
+
+ while(queue.size() != 0) {
+ // TODO Java 1.6: pollFirst()
+ final MergedGroup group = queue.first();
+ queue.remove(group);
+ group.processed = true;
+ //System.out.println(" pop: shards=" + group.shards + " group=" + (group.groupValue == null ? "null" : (((BytesRef) group.groupValue).utf8ToString())) + " sortValues=" + Arrays.toString(group.topValues));
+ if (count++ >= offset) {
+ final SearchGroup newGroup = new SearchGroup();
+ newGroup.groupValue = group.groupValue;
+ newGroup.sortValues = group.topValues;
+ newTopGroups.add(newGroup);
+ if (newTopGroups.size() == topN) {
+ break;
+ }
+ //} else {
+ // System.out.println(" skip < offset");
+ }
+
+ // Advance all iters in this group:
+ for(ShardIter shardIter : group.shards) {
+ updateNextGroup(maxQueueSize, shardIter);
+ }
+ }
+
+ if (newTopGroups.size() == 0) {
+ return null;
+ } else {
+ return newTopGroups;
+ }
+ }
+ }
+
+ /** Merges multiple collections of top groups, for example
+ * obtained from separate index shards. The provided
+ * groupSort must match how the groups were sorted, and
+ * the provided SearchGroups must have been computed
+ * with fillFields=true passed to {@link
+ * AbstractFirstPassGroupingCollector#getTopGroups}.
+ *
+ * NOTE: this returns null if the topGroups is empty.
+ */
+ public static Collection> merge(List>> topGroups, int offset, int topN, Sort groupSort)
+ throws IOException {
+ if (topGroups.size() == 0) {
+ return null;
+ } else {
+ return new GroupMerger(groupSort).merge(topGroups, offset, topN);
+ }
+ }
}
Index: modules/grouping/src/java/org/apache/lucene/search/grouping/TopGroups.java
--- modules/grouping/src/java/org/apache/lucene/search/grouping/TopGroups.java Sun Jun 12 13:27:05 2011 -0400
+++ modules/grouping/src/java/org/apache/lucene/search/grouping/TopGroups.java Sun Jun 12 13:30:26 2011 -0400
@@ -1,7 +1,5 @@
package org.apache.lucene.search.grouping;
-import org.apache.lucene.search.SortField;
-
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
@@ -19,6 +17,14 @@
* limitations under the License.
*/
+import java.io.IOException;
+import java.util.Arrays;
+
+import org.apache.lucene.search.ScoreDoc;
+import org.apache.lucene.search.Sort;
+import org.apache.lucene.search.SortField;
+import org.apache.lucene.search.TopDocs;
+
/** Represents result returned by a grouping search.
*
* @lucene.experimental */
@@ -58,4 +64,113 @@
this.groups = oldTopGroups.groups;
this.totalGroupCount = totalGroupCount;
}
+
+ /** Merges an array of TopGroups, for example obtained
+ * from the second-pass collector across multiple
+ * shards. Each TopGroups must have been sorted by the
+ * same groupSort and docSort, and the top groups passed
+ * to all second-pass collectors must be the same.
+ *
+ * NOTE: this cannot merge totalGroupCount; ie the
+ * returned TopGroups will have null totalGroupCount.
+ *
+ * NOTE: the topDocs in each GroupDocs is actually
+ * an instance of TopDocsAndShards
+ */
+ public static TopGroups merge(TopGroups[] shardGroups, Sort groupSort, Sort docSort, int docOffset, int docTopN)
+ throws IOException {
+
+ //System.out.println("TopGroups.merge");
+
+ if (shardGroups.length == 0) {
+ return null;
+ }
+
+ int totalHitCount = 0;
+ int totalGroupedHitCount = 0;
+
+ final int numGroups = shardGroups[0].groups.length;
+ for(TopGroups shard : shardGroups) {
+ if (numGroups != shard.groups.length) {
+ throw new IllegalArgumentException("number of groups differs across shards; you must pass same top groups to all shards' second-pass collector");
+ }
+ totalHitCount += shard.totalHitCount;
+ totalGroupedHitCount += shard.totalGroupedHitCount;
+ }
+
+ @SuppressWarnings("unchecked")
+ final GroupDocs[] mergedGroupDocs = new GroupDocs[numGroups];
+
+ final TopDocs[] shardTopDocs = new TopDocs[shardGroups.length];
+
+ for(int groupIDX=0;groupIDX slowGrouping(GroupDoc[] groupDocs,
String searchTerm,
@@ -418,6 +417,38 @@
return r;
}
+ private static class ShardState {
+
+ public final ShardSearcher[] subSearchers;
+ public final int[] docStarts;
+
+ public ShardState(IndexSearcher s) {
+ IndexReader[] subReaders = s.getIndexReader().getSequentialSubReaders();
+ if (subReaders == null) {
+ subReaders = new IndexReader[] {s.getIndexReader()};
+ }
+ subSearchers = new ShardSearcher[subReaders.length];
+ final IndexReader.ReaderContext ctx = s.getTopReaderContext();
+ if (ctx instanceof IndexReader.AtomicReaderContext) {
+ assert subSearchers.length == 1;
+ subSearchers[0] = new ShardSearcher((IndexReader.AtomicReaderContext) ctx, ctx);
+ } else {
+ final IndexReader.CompositeReaderContext compCTX = (IndexReader.CompositeReaderContext) ctx;
+ for(int searcherIDX=0;searcherIDX> topGroups = c1.getTopGroups(groupOffset, fillFields);
final TopGroups groupsResult;
+ if (VERBOSE) {
+ System.out.println("TEST: topGroups:");
+ if (topGroups == null) {
+ System.out.println(" null");
+ } else {
+ for(SearchGroup groupx : topGroups) {
+ System.out.println(" " + groupToString(groupx.groupValue) + " sort=" + Arrays.toString(groupx.sortValues));
+ }
+ }
+ }
+
+ final TopGroups topGroupsShards = searchShards(s, shards.subSearchers, q, groupSort, docSort, groupOffset, topNGroups, docOffset, docsPerGroup, getScores, getMaxScores);
if (topGroups != null) {
@@ -734,7 +781,13 @@
}
}
}
- assertEquals(docIDToID, expectedGroups, groupsResult, true, getScores);
+ assertEquals(docIDToID, expectedGroups, groupsResult, true, true, true, getScores);
+
+ // Confirm merged shards match:
+ assertEquals(docIDToID, expectedGroups, topGroupsShards, true, false, fillFields, getScores);
+ if (topGroupsShards != null) {
+ verifyShards(shards.docStarts, topGroupsShards);
+ }
final boolean needsScores = getScores || getMaxScores || docSort == null;
final BlockGroupingCollector c3 = new BlockGroupingCollector(groupSort, groupOffset+topNGroups, needsScores, lastDocInBlock);
@@ -758,6 +811,8 @@
groupsResult2 = tempTopGroups2;
}
+ final TopGroups topGroupsBlockShards = searchShards(s2, shards2.subSearchers, q, groupSort, docSort, groupOffset, topNGroups, docOffset, docsPerGroup, getScores, getMaxScores);
+
if (expectedGroups != null) {
// Fixup scores for reader2
for (GroupDocs groupDocsHits : expectedGroups.groups) {
@@ -799,8 +854,11 @@
}
}
- assertEquals(docIDToID2, expectedGroups, groupsResult2, false, getScores);
+ assertEquals(docIDToID2, expectedGroups, groupsResult2, false, true, true, getScores);
+ assertEquals(docIDToID2, expectedGroups, topGroupsBlockShards, false, false, fillFields, getScores);
}
+ s.close();
+ s2.close();
} finally {
FieldCache.DEFAULT.purge(r);
if (r2 != null) {
@@ -816,7 +874,95 @@
}
}
- private void assertEquals(int[] docIDtoID, TopGroups expected, TopGroups actual, boolean verifyGroupValues, boolean testScores) {
+ private void verifyShards(int[] docStarts, TopGroups topGroups) {
+ for(GroupDocs group : topGroups.groups) {
+ assertTrue(group instanceof GroupDocsAndShards);
+ GroupDocsAndShards withShards = (GroupDocsAndShards) group;
+ for(int hitIDX=0;hitIDX> groups1, Collection> groups2, boolean doSortValues) {
+ assertEquals(groups1.size(), groups2.size());
+ final Iterator> iter1 = groups1.iterator();
+ final Iterator> iter2 = groups2.iterator();
+
+ while(iter1.hasNext()) {
+ assertTrue(iter2.hasNext());
+
+ SearchGroup group1 = iter1.next();
+ SearchGroup group2 = iter2.next();
+
+ assertEquals(group1.groupValue, group2.groupValue);
+ if (doSortValues) {
+ assertEquals(group1.sortValues, group2.sortValues);
+ }
+ }
+ assertFalse(iter2.hasNext());
+ }
+
+ private TopGroups searchShards(IndexSearcher topSearcher, ShardSearcher[] subSearchers, Query query, Sort groupSort, Sort docSort, int groupOffset, int topNGroups, int docOffset,
+ int topNDocs, boolean getScores, boolean getMaxScores) throws Exception {
+
+ // TODO: swap in caching, all groups collector here
+ // too...
+ if (VERBOSE) {
+ System.out.println("TEST: " + subSearchers.length + " shards: " + Arrays.toString(subSearchers));
+ }
+ // Run 1st pass collector to get top groups per shard
+ final Weight w = query.weight(topSearcher);
+ final List>> shardGroups = new ArrayList>>();
+ for(int shardIDX=0;shardIDX> topGroups = c.getTopGroups(0, true);
+ if (topGroups != null) {
+ if (VERBOSE) {
+ System.out.println(" shard " + shardIDX + " s=" + subSearchers[shardIDX] + " " + topGroups.size() + " groups:");
+ for(SearchGroup group : topGroups) {
+ System.out.println(" " + groupToString(group.groupValue) + " sort=" + Arrays.toString(group.sortValues));
+ }
+ }
+ shardGroups.add(topGroups);
+ }
+ }
+
+ final Collection> mergedTopGroups = SearchGroup.merge(shardGroups, groupOffset, topNGroups, groupSort);
+ if (VERBOSE) {
+ System.out.println(" merged:");
+ if (mergedTopGroups == null) {
+ System.out.println(" null");
+ } else {
+ for(SearchGroup group : mergedTopGroups) {
+ System.out.println(" " + groupToString(group.groupValue) + " sort=" + Arrays.toString(group.sortValues));
+ }
+ }
+ }
+
+ if (mergedTopGroups != null) {
+
+ // Now 2nd pass:
+ @SuppressWarnings("unchecked")
+ final TopGroups[] shardTopGroups = new TopGroups[subSearchers.length];
+ for(int shardIDX=0;shardIDX newComparator(final String fieldname, final int numHits, int sortPos, boolean reversed) throws IOException {
+ return new FieldComparator() {
FieldCache.DocTermsIndex idIndex;
private final int[] values = new int[numHits];
@@ -517,7 +517,7 @@
}
@Override
- public Comparable value(int slot) {
+ public Integer value(int slot) {
return values[slot];
}
};
Index: solr/src/java/org/apache/solr/schema/RandomSortField.java
--- solr/src/java/org/apache/solr/schema/RandomSortField.java Sun Jun 12 13:27:05 2011 -0400
+++ solr/src/java/org/apache/solr/schema/RandomSortField.java Sun Jun 12 13:30:26 2011 -0400
@@ -102,8 +102,8 @@
private static FieldComparatorSource randomComparatorSource = new FieldComparatorSource() {
@Override
- public FieldComparator newComparator(final String fieldname, final int numHits, int sortPos, boolean reversed) throws IOException {
- return new FieldComparator() {
+ public FieldComparator newComparator(final String fieldname, final int numHits, int sortPos, boolean reversed) throws IOException {
+ return new FieldComparator() {
int seed;
private final int[] values = new int[numHits];
int bottomVal;
@@ -135,7 +135,7 @@
}
@Override
- public Comparable value(int slot) {
+ public Integer value(int slot) {
return values[slot];
}
};
Index: solr/src/java/org/apache/solr/search/MissingStringLastComparatorSource.java
--- solr/src/java/org/apache/solr/search/MissingStringLastComparatorSource.java Sun Jun 12 13:27:05 2011 -0400
+++ solr/src/java/org/apache/solr/search/MissingStringLastComparatorSource.java Sun Jun 12 13:30:26 2011 -0400
@@ -56,7 +56,7 @@
// Copied from Lucene's TermOrdValComparator and modified since the Lucene version couldn't
// be extended.
-class TermOrdValComparator_SML extends FieldComparator {
+class TermOrdValComparator_SML extends FieldComparator {
private static final int NULL_ORD = Integer.MAX_VALUE;
private final int[] ords;
@@ -98,7 +98,7 @@
}
@Override
- public Comparable> value(int slot) {
+ public BytesRef value(int slot) {
throw new UnsupportedOperationException();
}
@@ -111,7 +111,7 @@
// ords) per-segment comparator. NOTE: this is messy;
// we do this only because hotspot can't reliably inline
// the underlying array access when looking up doc->ord
- private static abstract class PerSegmentComparator extends FieldComparator {
+ private static abstract class PerSegmentComparator extends FieldComparator {
protected TermOrdValComparator_SML parent;
protected final int[] ords;
protected final BytesRef[] values;
@@ -199,7 +199,7 @@
}
@Override
- public Comparable> value(int slot) {
+ public BytesRef value(int slot) {
return values==null ? parent.NULL_VAL : values[slot];
}
}
Index: solr/src/java/org/apache/solr/search/function/ValueSource.java
--- solr/src/java/org/apache/solr/search/function/ValueSource.java Sun Jun 12 13:27:05 2011 -0400
+++ solr/src/java/org/apache/solr/search/function/ValueSource.java Sun Jun 12 13:30:26 2011 -0400
@@ -141,7 +141,7 @@
* off of the {@link org.apache.solr.search.function.DocValues} for a ValueSource
* instead of the normal Lucene FieldComparator that works off of a FieldCache.
*/
- class ValueSourceComparator extends FieldComparator {
+ class ValueSourceComparator extends FieldComparator {
private final double[] values;
private DocValues docVals;
private double bottom;
@@ -195,7 +195,7 @@
}
@Override
- public Comparable value(int slot) {
+ public Double value(int slot) {
return values[slot];
}
}