You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by ds...@apache.org on 2016/02/01 04:26:28 UTC

lucene-solr git commit: SOLR-7968: Make QueryComponent extensible

Repository: lucene-solr
Updated Branches:
  refs/heads/master 14a2c16ca -> 4cdce3db7


SOLR-7968: Make QueryComponent extensible


Project: http://git-wip-us.apache.org/repos/asf/lucene-solr/repo
Commit: http://git-wip-us.apache.org/repos/asf/lucene-solr/commit/4cdce3db
Tree: http://git-wip-us.apache.org/repos/asf/lucene-solr/tree/4cdce3db
Diff: http://git-wip-us.apache.org/repos/asf/lucene-solr/diff/4cdce3db

Branch: refs/heads/master
Commit: 4cdce3db77cde2506ac77dc9ced8c1f91ed9800f
Parents: 14a2c16ca
Author: David Smiley <ds...@apache.org>
Authored: Sun Jan 31 22:25:37 2016 -0500
Committer: David Smiley <ds...@apache.org>
Committed: Sun Jan 31 22:25:37 2016 -0500

----------------------------------------------------------------------
 solr/CHANGES.txt                                |   2 +
 .../solr/handler/component/QueryComponent.java  |  48 ++---
 .../solr/handler/component/ResponseBuilder.java |   4 +
 .../apache/solr/handler/component/ShardDoc.java | 166 +----------------
 .../component/ShardFieldSortedHitQueue.java     | 179 +++++++++++++++++++
 5 files changed, 211 insertions(+), 188 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4cdce3db/solr/CHANGES.txt
----------------------------------------------------------------------
diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index 4c0722e..adfc8d7 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -583,6 +583,8 @@ Other Changes
 
 * SOLR-8597: add default, no-op QParserPlugin.init(NamedList) method (Christine Poerschke)
 
+* SOLR-7968: Make QueryComponent more extensible. (Markus Jelsma via David Smiley)
+
 ==================  5.4.1 ==================
 
 Bug Fixes

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4cdce3db/solr/core/src/java/org/apache/solr/handler/component/QueryComponent.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/component/QueryComponent.java b/solr/core/src/java/org/apache/solr/handler/component/QueryComponent.java
index 2e99a2b..05fe28d 100644
--- a/solr/core/src/java/org/apache/solr/handler/component/QueryComponent.java
+++ b/solr/core/src/java/org/apache/solr/handler/component/QueryComponent.java
@@ -235,7 +235,7 @@ public class QueryComponent extends SearchComponent
     }
   }
 
-  private void prepareGrouping(ResponseBuilder rb) throws IOException {
+  protected void prepareGrouping(ResponseBuilder rb) throws IOException {
 
     SolrQueryRequest req = rb.req;
     SolrParams params = req.getParams();
@@ -671,7 +671,7 @@ public class QueryComponent extends SearchComponent
     }
   }
 
-  private int groupedDistributedProcess(ResponseBuilder rb) {
+  protected int groupedDistributedProcess(ResponseBuilder rb) {
     int nextStage = ResponseBuilder.STAGE_DONE;
     ShardRequestFactory shardRequestFactory = null;
 
@@ -705,7 +705,7 @@ public class QueryComponent extends SearchComponent
     return nextStage;
   }
 
-  private int regularDistributedProcess(ResponseBuilder rb) {
+  protected int regularDistributedProcess(ResponseBuilder rb) {
     if (rb.stage < ResponseBuilder.STAGE_PARSE_QUERY)
       return ResponseBuilder.STAGE_PARSE_QUERY;
     if (rb.stage == ResponseBuilder.STAGE_PARSE_QUERY) {
@@ -734,7 +734,7 @@ public class QueryComponent extends SearchComponent
     }
   }
 
-  private void handleGroupedResponses(ResponseBuilder rb, ShardRequest sreq) {
+  protected void handleGroupedResponses(ResponseBuilder rb, ShardRequest sreq) {
     ShardResponseProcessor responseProcessor = null;
     if ((sreq.purpose & ShardRequest.PURPOSE_GET_TOP_GROUPS) != 0) {
       responseProcessor = new SearchGroupShardResponseProcessor();
@@ -749,7 +749,7 @@ public class QueryComponent extends SearchComponent
     }
   }
 
-  private void handleRegularResponses(ResponseBuilder rb, ShardRequest sreq) {
+  protected void handleRegularResponses(ResponseBuilder rb, ShardRequest sreq) {
     if ((sreq.purpose & ShardRequest.PURPOSE_GET_TOP_IDS) != 0) {
       mergeIds(rb, sreq);
     }
@@ -775,11 +775,11 @@ public class QueryComponent extends SearchComponent
     }
   }
 
-  private static final EndResultTransformer MAIN_END_RESULT_TRANSFORMER = new MainEndResultTransformer();
-  private static final EndResultTransformer SIMPLE_END_RESULT_TRANSFORMER = new SimpleEndResultTransformer();
+  protected static final EndResultTransformer MAIN_END_RESULT_TRANSFORMER = new MainEndResultTransformer();
+  protected static final EndResultTransformer SIMPLE_END_RESULT_TRANSFORMER = new SimpleEndResultTransformer();
 
   @SuppressWarnings("unchecked")
-  private void groupedFinishStage(final ResponseBuilder rb) {
+  protected void groupedFinishStage(final ResponseBuilder rb) {
     // To have same response as non-distributed request.
     GroupingSpecification groupSpec = rb.getGroupingSpec();
     if (rb.mergedTopGroups.isEmpty()) {
@@ -814,24 +814,24 @@ public class QueryComponent extends SearchComponent
     endResultTransformer.transform(combinedMap, rb, solrDocumentSource);
   }
 
-  private void regularFinishStage(ResponseBuilder rb) {
+  protected void regularFinishStage(ResponseBuilder rb) {
     // We may not have been able to retrieve all the docs due to an
     // index change.  Remove any null documents.
-    for (Iterator<SolrDocument> iter = rb._responseDocs.iterator(); iter.hasNext();) {
+    for (Iterator<SolrDocument> iter = rb.getResponseDocs().iterator(); iter.hasNext();) {
       if (iter.next() == null) {
         iter.remove();
-        rb._responseDocs.setNumFound(rb._responseDocs.getNumFound()-1);
+        rb.getResponseDocs().setNumFound(rb.getResponseDocs().getNumFound()-1);
       }
     }
 
-    rb.rsp.addResponse(rb._responseDocs);
+    rb.rsp.addResponse(rb.getResponseDocs());
     if (null != rb.getNextCursorMark()) {
       rb.rsp.add(CursorMarkParams.CURSOR_MARK_NEXT,
                  rb.getNextCursorMark().getSerializedTotem());
     }
   }
 
-  private void createDistributedStats(ResponseBuilder rb) {
+  protected void createDistributedStats(ResponseBuilder rb) {
     StatsCache cache = rb.req.getCore().getStatsCache();
     if ( (rb.getFieldFlags() & SolrIndexSearcher.GET_SCORES)!=0 || rb.getSortSpec().includesScore()) {
       ShardRequest sreq = cache.retrieveStatsRequest(rb);
@@ -841,12 +841,12 @@ public class QueryComponent extends SearchComponent
     }
   }
 
-  private void updateStats(ResponseBuilder rb, ShardRequest sreq) {
+  protected void updateStats(ResponseBuilder rb, ShardRequest sreq) {
     StatsCache cache = rb.req.getCore().getStatsCache();
     cache.mergeToGlobalStats(rb.req, sreq.responses);
   }
 
-  private void createMainQuery(ResponseBuilder rb) {
+  protected void createMainQuery(ResponseBuilder rb) {
     ShardRequest sreq = new ShardRequest();
     sreq.purpose = ShardRequest.PURPOSE_GET_TOP_IDS;
 
@@ -931,13 +931,13 @@ public class QueryComponent extends SearchComponent
     rb.addRequest(this, sreq);
   }
   
-  private boolean addFL(StringBuilder fl, String field, boolean additionalAdded) {
+  protected boolean addFL(StringBuilder fl, String field, boolean additionalAdded) {
     if (additionalAdded) fl.append(",");
     fl.append(field);
     return true;
   }
 
-  private void mergeIds(ResponseBuilder rb, ShardRequest sreq) {
+  protected void mergeIds(ResponseBuilder rb, ShardRequest sreq) {
       List<MergeStrategy> mergeStrategies = rb.getMergeStrategies();
       if(mergeStrategies != null) {
         Collections.sort(mergeStrategies, MergeStrategy.MERGE_COMP);
@@ -1110,7 +1110,7 @@ public class QueryComponent extends SearchComponent
       // again when retrieving stored fields.
       // TODO: use ResponseBuilder (w/ comments) or the request context?
       rb.resultIds = resultIds;
-      rb._responseDocs = responseDocs;
+      rb.setResponseDocs(responseDocs);
 
       populateNextCursorMarkFromMergedShards(rb);
 
@@ -1130,7 +1130,7 @@ public class QueryComponent extends SearchComponent
    *           <code>ShardDocs</code> in <code>resultIds</code>, may or may not be 
    *           part of a Cursor based request (method will NOOP if not needed)
    */
-  private void populateNextCursorMarkFromMergedShards(ResponseBuilder rb) {
+  protected void populateNextCursorMarkFromMergedShards(ResponseBuilder rb) {
 
     final CursorMark lastCursorMark = rb.getCursorMark();
     if (null == lastCursorMark) {
@@ -1172,7 +1172,7 @@ public class QueryComponent extends SearchComponent
     rb.setNextCursorMark(nextCursorMark);
   }
 
-  private NamedList unmarshalSortValues(SortSpec sortSpec, 
+  protected NamedList unmarshalSortValues(SortSpec sortSpec, 
                                         NamedList sortFieldValues, 
                                         IndexSchema schema) {
     NamedList unmarshalledSortValsPerField = new NamedList();
@@ -1213,7 +1213,7 @@ public class QueryComponent extends SearchComponent
     return unmarshalledSortValsPerField;
   }
 
-  private void createRetrieveDocs(ResponseBuilder rb) {
+  protected void createRetrieveDocs(ResponseBuilder rb) {
 
     // TODO: in a system with nTiers > 2, we could be passed "ids" here
     // unless those requests always go to the final destination shard
@@ -1267,7 +1267,7 @@ public class QueryComponent extends SearchComponent
   }
 
 
-  private void returnFields(ResponseBuilder rb, ShardRequest sreq) {
+  protected void returnFields(ResponseBuilder rb, ShardRequest sreq) {
     // Keep in mind that this could also be a shard in a multi-tiered system.
     // TODO: if a multi-tiered system, it seems like some requests
     // could/should bypass middlemen (like retrieving stored fields)
@@ -1318,7 +1318,7 @@ public class QueryComponent extends SearchComponent
             if (removeKeyField) {
               doc.removeFields(keyFieldName);
             }
-            rb._responseDocs.set(sdoc.positionInResponse, doc);
+            rb.getResponseDocs().set(sdoc.positionInResponse, doc);
           }
         }
       }
@@ -1344,7 +1344,7 @@ public class QueryComponent extends SearchComponent
    *
    * TODO: when SOLR-5595 is fixed, this wont be needed, as we dont need to recompute sort values here from the comparator
    */
-  private static class FakeScorer extends Scorer {
+  protected static class FakeScorer extends Scorer {
     final int docid;
     final float score;
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4cdce3db/solr/core/src/java/org/apache/solr/handler/component/ResponseBuilder.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/component/ResponseBuilder.java b/solr/core/src/java/org/apache/solr/handler/component/ResponseBuilder.java
index c1fb21a..8f20dbf 100644
--- a/solr/core/src/java/org/apache/solr/handler/component/ResponseBuilder.java
+++ b/solr/core/src/java/org/apache/solr/handler/component/ResponseBuilder.java
@@ -263,6 +263,10 @@ public class ResponseBuilder
   public void setResponseDocs(SolrDocumentList _responseDocs) {
     this._responseDocs = _responseDocs;
   }
+  
+  public SolrDocumentList getResponseDocs() {
+    return this._responseDocs;
+  }
 
   public boolean isDebugTrack() {
     return debugTrack;

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4cdce3db/solr/core/src/java/org/apache/solr/handler/component/ShardDoc.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/component/ShardDoc.java b/solr/core/src/java/org/apache/solr/handler/component/ShardDoc.java
index 97b831b..2935aa1 100644
--- a/solr/core/src/java/org/apache/solr/handler/component/ShardDoc.java
+++ b/solr/core/src/java/org/apache/solr/handler/component/ShardDoc.java
@@ -16,21 +16,9 @@
  */
 package org.apache.solr.handler.component;
 
-import org.apache.lucene.search.FieldComparator;
 import org.apache.lucene.search.FieldDoc;
-import org.apache.lucene.search.IndexSearcher;
-import org.apache.lucene.search.SortField;
-import org.apache.lucene.util.PriorityQueue;
-import org.apache.solr.common.SolrException;
 import org.apache.solr.common.util.NamedList;
 
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Comparator;
-import java.util.List;
-
-import static org.apache.solr.common.SolrException.ErrorCode.SERVER_ERROR;
-
 public class ShardDoc extends FieldDoc {
   public String shard;
   public String shardAddress;  // TODO
@@ -44,7 +32,7 @@ public class ShardDoc extends FieldDoc {
     // this is currently the uniqueKeyField but
     // may be replaced with internal docid in a future release.
 
-  NamedList sortFieldValues;
+  public NamedList sortFieldValues;
   // sort field values for *all* docs in a particular shard.
   // this doc's values are in position orderInShard
 
@@ -93,154 +81,4 @@ public class ShardDoc extends FieldDoc {
             +" ,positionInResponse="+positionInResponse
             +" ,sortFieldValues="+sortFieldValues;
   }
-}
-
-
-
-// used by distributed search to merge results.
-class ShardFieldSortedHitQueue extends PriorityQueue<ShardDoc> {
-
-  /** Stores a comparator corresponding to each field being sorted by */
-  protected Comparator<ShardDoc>[] comparators;
-
-  /** Stores the sort criteria being used. */
-  protected SortField[] fields;
-
-  /** The order of these fieldNames should correspond to the order of sort field values retrieved from the shard */
-  protected List<String> fieldNames = new ArrayList<>();
-
-  public ShardFieldSortedHitQueue(SortField[] fields, int size, IndexSearcher searcher) {
-    super(size);
-    final int n = fields.length;
-    //noinspection unchecked
-    comparators = new Comparator[n];
-    this.fields = new SortField[n];
-    for (int i = 0; i < n; ++i) {
-
-      // keep track of the named fields
-      SortField.Type type = fields[i].getType();
-      if (type!=SortField.Type.SCORE && type!=SortField.Type.DOC) {
-        fieldNames.add(fields[i].getField());
-      }
-
-      String fieldname = fields[i].getField();
-      comparators[i] = getCachedComparator(fields[i], searcher);
-
-     if (fields[i].getType() == SortField.Type.STRING) {
-        this.fields[i] = new SortField(fieldname, SortField.Type.STRING,
-            fields[i].getReverse());
-      } else {
-        this.fields[i] = new SortField(fieldname, fields[i].getType(),
-            fields[i].getReverse());
-      }
-
-      //System.out.println("%%%%%%%%%%%%%%%%%% got "+fields[i].getType() +"   for "+ fieldname +"  fields[i].getReverse(): "+fields[i].getReverse());
-    }
-  }
-
-  @Override
-  protected boolean lessThan(ShardDoc docA, ShardDoc docB) {
-    // If these docs are from the same shard, then the relative order
-    // is how they appeared in the response from that shard.    
-    if (docA.shard == docB.shard) {
-      // if docA has a smaller position, it should be "larger" so it
-      // comes before docB.
-      // This will handle sorting by docid within the same shard
-
-      // comment this out to test comparators.
-      return !(docA.orderInShard < docB.orderInShard);
-    }
-
-
-    // run comparators
-    final int n = comparators.length;
-    int c = 0;
-    for (int i = 0; i < n && c == 0; i++) {
-      c = (fields[i].getReverse()) ? comparators[i].compare(docB, docA)
-          : comparators[i].compare(docA, docB);
-    }
-
-    // solve tiebreaks by comparing shards (similar to using docid)
-    // smaller docid's beat larger ids, so reverse the natural ordering
-    if (c == 0) {
-      c = -docA.shard.compareTo(docB.shard);
-    }
-
-    return c < 0;
-  }
-
-  Comparator<ShardDoc> getCachedComparator(SortField sortField, IndexSearcher searcher) {
-    SortField.Type type = sortField.getType();
-    if (type == SortField.Type.SCORE) {
-      return comparatorScore();
-    } else if (type == SortField.Type.REWRITEABLE) {
-      try {
-        sortField = sortField.rewrite(searcher);
-      } catch (IOException e) {
-        throw new SolrException(SERVER_ERROR, "Exception rewriting sort field " + sortField, e);
-      }
-    }
-    return comparatorFieldComparator(sortField);
-  }
-
-  abstract class ShardComparator implements Comparator<ShardDoc> {
-    final SortField sortField;
-    final String fieldName;
-    final int fieldNum;
-
-    public ShardComparator(SortField sortField) {
-      this.sortField = sortField;
-      this.fieldName = sortField.getField();
-      int fieldNum = 0;
-      for (int i=0; i<fieldNames.size(); i++) {
-        if (fieldNames.get(i).equals(fieldName)) {
-          fieldNum = i;
-          break;
-        }
-      }
-      this.fieldNum = fieldNum;
-    }
-
-    Object sortVal(ShardDoc shardDoc) {
-      assert(shardDoc.sortFieldValues.getName(fieldNum).equals(fieldName));
-      List lst = (List)shardDoc.sortFieldValues.getVal(fieldNum);
-      return lst.get(shardDoc.orderInShard);
-    }
-  }
-
-  static Comparator<ShardDoc> comparatorScore() {
-    return new Comparator<ShardDoc>() {
-      @Override
-      public final int compare(final ShardDoc o1, final ShardDoc o2) {
-        final float f1 = o1.score;
-        final float f2 = o2.score;
-        if (f1 < f2)
-          return -1;
-        if (f1 > f2)
-          return 1;
-        return 0;
-      }
-    };
-  }
-
-  Comparator<ShardDoc> comparatorFieldComparator(SortField sortField) {
-    final FieldComparator fieldComparator;
-    try {
-      fieldComparator = sortField.getComparator(0, 0);
-    } catch (IOException e) {
-      throw new RuntimeException("Unable to get FieldComparator for sortField " + sortField);
-    }
-
-    return new ShardComparator(sortField) {
-      // Since the PriorityQueue keeps the biggest elements by default,
-      // we need to reverse the field compare ordering so that the
-      // smallest elements are kept instead of the largest... hence
-      // the negative sign.
-      @Override
-      public int compare(final ShardDoc o1, final ShardDoc o2) {
-        //noinspection unchecked
-        return -fieldComparator.compareValues(sortVal(o1), sortVal(o2));
-      }
-    };
-  }
-}
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4cdce3db/solr/core/src/java/org/apache/solr/handler/component/ShardFieldSortedHitQueue.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/component/ShardFieldSortedHitQueue.java b/solr/core/src/java/org/apache/solr/handler/component/ShardFieldSortedHitQueue.java
new file mode 100644
index 0000000..fd0603d
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/handler/component/ShardFieldSortedHitQueue.java
@@ -0,0 +1,179 @@
+package org.apache.solr.handler.component;
+
+/*
+ * 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.Comparator;
+import java.util.List;
+
+import org.apache.lucene.search.FieldComparator;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.SortField;
+import org.apache.lucene.util.PriorityQueue;
+import org.apache.solr.common.SolrException;
+
+import static org.apache.solr.common.SolrException.ErrorCode.SERVER_ERROR;
+
+// used by distributed search to merge results.
+public class ShardFieldSortedHitQueue extends PriorityQueue<ShardDoc> {
+
+  /** Stores a comparator corresponding to each field being sorted by */
+  protected Comparator<ShardDoc>[] comparators;
+
+  /** Stores the sort criteria being used. */
+  protected SortField[] fields;
+
+  /** The order of these fieldNames should correspond to the order of sort field values retrieved from the shard */
+  protected List<String> fieldNames = new ArrayList<>();
+
+  public ShardFieldSortedHitQueue(SortField[] fields, int size, IndexSearcher searcher) {
+    super(size);
+    final int n = fields.length;
+    //noinspection unchecked
+    comparators = new Comparator[n];
+    this.fields = new SortField[n];
+    for (int i = 0; i < n; ++i) {
+
+      // keep track of the named fields
+      SortField.Type type = fields[i].getType();
+      if (type!=SortField.Type.SCORE && type!=SortField.Type.DOC) {
+        fieldNames.add(fields[i].getField());
+      }
+
+      String fieldname = fields[i].getField();
+      comparators[i] = getCachedComparator(fields[i], searcher);
+
+     if (fields[i].getType() == SortField.Type.STRING) {
+        this.fields[i] = new SortField(fieldname, SortField.Type.STRING,
+            fields[i].getReverse());
+      } else {
+        this.fields[i] = new SortField(fieldname, fields[i].getType(),
+            fields[i].getReverse());
+      }
+
+      //System.out.println("%%%%%%%%%%%%%%%%%% got "+fields[i].getType() +"   for "+ fieldname +"  fields[i].getReverse(): "+fields[i].getReverse());
+    }
+  }
+
+  @Override
+  protected boolean lessThan(ShardDoc docA, ShardDoc docB) {
+    // If these docs are from the same shard, then the relative order
+    // is how they appeared in the response from that shard.    
+    if (docA.shard == docB.shard) {
+      // if docA has a smaller position, it should be "larger" so it
+      // comes before docB.
+      // This will handle sorting by docid within the same shard
+
+      // comment this out to test comparators.
+      return !(docA.orderInShard < docB.orderInShard);
+    }
+
+
+    // run comparators
+    final int n = comparators.length;
+    int c = 0;
+    for (int i = 0; i < n && c == 0; i++) {
+      c = (fields[i].getReverse()) ? comparators[i].compare(docB, docA)
+          : comparators[i].compare(docA, docB);
+    }
+
+    // solve tiebreaks by comparing shards (similar to using docid)
+    // smaller docid's beat larger ids, so reverse the natural ordering
+    if (c == 0) {
+      c = -docA.shard.compareTo(docB.shard);
+    }
+
+    return c < 0;
+  }
+
+  Comparator<ShardDoc> getCachedComparator(SortField sortField, IndexSearcher searcher) {
+    SortField.Type type = sortField.getType();
+    if (type == SortField.Type.SCORE) {
+      return comparatorScore();
+    } else if (type == SortField.Type.REWRITEABLE) {
+      try {
+        sortField = sortField.rewrite(searcher);
+      } catch (IOException e) {
+        throw new SolrException(SERVER_ERROR, "Exception rewriting sort field " + sortField, e);
+      }
+    }
+    return comparatorFieldComparator(sortField);
+  }
+
+  abstract class ShardComparator implements Comparator<ShardDoc> {
+    final SortField sortField;
+    final String fieldName;
+    final int fieldNum;
+
+    public ShardComparator(SortField sortField) {
+      this.sortField = sortField;
+      this.fieldName = sortField.getField();
+      int fieldNum = 0;
+      for (int i=0; i<fieldNames.size(); i++) {
+        if (fieldNames.get(i).equals(fieldName)) {
+          fieldNum = i;
+          break;
+        }
+      }
+      this.fieldNum = fieldNum;
+    }
+
+    Object sortVal(ShardDoc shardDoc) {
+      assert(shardDoc.sortFieldValues.getName(fieldNum).equals(fieldName));
+      List lst = (List)shardDoc.sortFieldValues.getVal(fieldNum);
+      return lst.get(shardDoc.orderInShard);
+    }
+  }
+
+  static Comparator<ShardDoc> comparatorScore() {
+    return new Comparator<ShardDoc>() {
+      @Override
+      public final int compare(final ShardDoc o1, final ShardDoc o2) {
+        final float f1 = o1.score;
+        final float f2 = o2.score;
+        if (f1 < f2)
+          return -1;
+        if (f1 > f2)
+          return 1;
+        return 0;
+      }
+    };
+  }
+
+  Comparator<ShardDoc> comparatorFieldComparator(SortField sortField) {
+    final FieldComparator fieldComparator;
+    try {
+      fieldComparator = sortField.getComparator(0, 0);
+    } catch (IOException e) {
+      throw new RuntimeException("Unable to get FieldComparator for sortField " + sortField);
+    }
+
+    return new ShardComparator(sortField) {
+      // Since the PriorityQueue keeps the biggest elements by default,
+      // we need to reverse the field compare ordering so that the
+      // smallest elements are kept instead of the largest... hence
+      // the negative sign.
+      @Override
+      public int compare(final ShardDoc o1, final ShardDoc o2) {
+        //noinspection unchecked
+        return -fieldComparator.compareValues(sortVal(o1), sortVal(o2));
+      }
+    };
+  }
+}
\ No newline at end of file