You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by cp...@apache.org on 2018/12/31 11:37:13 UTC

lucene-solr:master: SOLR-13096: rename TestRankQueryPlugin to RankQueryTestPlugin

Repository: lucene-solr
Updated Branches:
  refs/heads/master 345a655f2 -> 6a2de771b


SOLR-13096: rename TestRankQueryPlugin to RankQueryTestPlugin


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

Branch: refs/heads/master
Commit: 6a2de771bfc8a40738e1ce0645f8b2c8ae8f9e09
Parents: 345a655
Author: Christine Poerschke <cp...@apache.org>
Authored: Mon Dec 31 11:05:31 2018 +0000
Committer: Christine Poerschke <cp...@apache.org>
Committed: Mon Dec 31 11:05:31 2018 +0000

----------------------------------------------------------------------
 .../conf/solrconfig-plugcollector.xml           |   2 +-
 .../apache/solr/search/RankQueryTestPlugin.java | 781 ++++++++++++++++++
 .../apache/solr/search/TestRankQueryPlugin.java | 783 -------------------
 3 files changed, 782 insertions(+), 784 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/6a2de771/solr/core/src/test-files/solr/collection1/conf/solrconfig-plugcollector.xml
----------------------------------------------------------------------
diff --git a/solr/core/src/test-files/solr/collection1/conf/solrconfig-plugcollector.xml b/solr/core/src/test-files/solr/collection1/conf/solrconfig-plugcollector.xml
index 56f8a01..b60bd6a 100644
--- a/solr/core/src/test-files/solr/collection1/conf/solrconfig-plugcollector.xml
+++ b/solr/core/src/test-files/solr/collection1/conf/solrconfig-plugcollector.xml
@@ -457,7 +457,7 @@ based HashBitset. -->
   <propTest attr1="${solr.test.sys.prop1}-$${literal}"
             attr2="${non.existent.sys.prop:default-from-config}">prefix-${solr.test.sys.prop2}-suffix</propTest>
 
-  <queryParser name="rank" class="org.apache.solr.search.TestRankQueryPlugin"/>
+  <queryParser name="rank" class="org.apache.solr.search.RankQueryTestPlugin"/>
 
   <updateRequestProcessorChain name="dedupe">
     <processor class="org.apache.solr.update.processor.SignatureUpdateProcessorFactory">

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/6a2de771/solr/core/src/test/org/apache/solr/search/RankQueryTestPlugin.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/search/RankQueryTestPlugin.java b/solr/core/src/test/org/apache/solr/search/RankQueryTestPlugin.java
new file mode 100644
index 0000000..bc38397
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/search/RankQueryTestPlugin.java
@@ -0,0 +1,781 @@
+/*
+ * 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.
+ */
+package org.apache.solr.search;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.lucene.index.DocValues;
+import org.apache.lucene.index.IndexReaderContext;
+import org.apache.lucene.index.LeafReaderContext;
+import org.apache.lucene.index.NumericDocValues;
+import org.apache.lucene.index.ReaderUtil;
+import org.apache.lucene.search.FieldComparator;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.LeafCollector;
+import org.apache.lucene.search.LeafFieldComparator;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.Scorable;
+import org.apache.lucene.search.ScoreDoc;
+import org.apache.lucene.search.ScoreMode;
+import org.apache.lucene.search.Sort;
+import org.apache.lucene.search.SortField;
+import org.apache.lucene.search.TopDocs;
+import org.apache.lucene.search.TopDocsCollector;
+import org.apache.lucene.search.TotalHits;
+import org.apache.lucene.search.Weight;
+import org.apache.lucene.util.InPlaceMergeSorter;
+import org.apache.lucene.util.PriorityQueue;
+import org.apache.solr.client.solrj.SolrServerException;
+import org.apache.solr.common.SolrDocument;
+import org.apache.solr.common.SolrDocumentList;
+import org.apache.solr.common.params.ShardParams;
+import org.apache.solr.common.params.SolrParams;
+import org.apache.solr.common.util.NamedList;
+import org.apache.solr.common.util.SimpleOrderedMap;
+import org.apache.solr.handler.component.MergeStrategy;
+import org.apache.solr.handler.component.ResponseBuilder;
+import org.apache.solr.handler.component.ShardDoc;
+import org.apache.solr.handler.component.ShardRequest;
+import org.apache.solr.handler.component.ShardResponse;
+import org.apache.solr.request.SolrQueryRequest;
+import org.apache.solr.response.SolrQueryResponse;
+import org.apache.solr.schema.FieldType;
+import org.apache.solr.schema.IndexSchema;
+import org.apache.solr.schema.SchemaField;
+
+
+public class RankQueryTestPlugin extends QParserPlugin {
+
+
+  public QParser createParser(String query, SolrParams localParams, SolrParams params, SolrQueryRequest req) {
+    return new TestRankQueryParser(query, localParams, params, req);
+  }
+
+  static class TestRankQueryParser extends QParser {
+
+    public TestRankQueryParser(String query, SolrParams localParams, SolrParams params, SolrQueryRequest req) {
+      super(query, localParams, params, req);
+    }
+
+    public Query parse() throws SyntaxError {
+
+      int mergeStrategy = localParams.getInt("mergeStrategy", 0);
+      int collector = localParams.getInt("collector", 0);
+      return new TestRankQuery(collector, mergeStrategy);
+    }
+  }
+
+  static class TestRankQuery extends RankQuery {
+
+    private int mergeStrategy;
+    private int collector;
+    private Query q;
+
+    public int hashCode() {
+      return collector+q.hashCode();
+    }
+
+    public boolean equals(Object o) {
+      if(o instanceof TestRankQuery) {
+        TestRankQuery trq = (TestRankQuery)o;
+
+        return (trq.q.equals(q) && trq.collector == collector) ;
+      }
+
+      return false;
+    }
+
+    public Weight createWeight(IndexSearcher indexSearcher, ScoreMode scoreMode, float boost) throws IOException{
+      return q.createWeight(indexSearcher, scoreMode, boost);
+    }
+
+    @Override
+    public String toString(String field) {
+      return q.toString(field);
+    }
+
+    public RankQuery wrap(Query q) {
+      this.q = q;
+      return this;
+    }
+
+    public TestRankQuery(int collector, int mergeStrategy) {
+      this.collector = collector;
+      this.mergeStrategy = mergeStrategy;
+    }
+
+    public TopDocsCollector getTopDocsCollector(int len, QueryCommand cmd, IndexSearcher searcher) {
+      if(collector == 0)
+        return new TestCollector(null);
+      else
+        return new TestCollector1(null);
+    }
+
+    public MergeStrategy getMergeStrategy() {
+      if(mergeStrategy == 0)
+        return new TestMergeStrategy();
+      else
+        return new TestMergeStrategy1();
+    }
+  }
+
+  static class TestMergeStrategy implements MergeStrategy {
+
+    public int getCost() {
+      return 1;
+    }
+
+    public boolean mergesIds() {
+      return true;
+    }
+
+    public boolean handlesMergeFields() {
+      return false;
+    }
+
+    public void handleMergeFields(ResponseBuilder rb, SolrIndexSearcher searcher) {
+
+    }
+
+    public void merge(ResponseBuilder rb, ShardRequest sreq) {
+
+      // id to shard mapping, to eliminate any accidental dups
+      HashMap<Object,String> uniqueDoc = new HashMap<>();
+
+
+      NamedList<Object> shardInfo = null;
+      if(rb.req.getParams().getBool(ShardParams.SHARDS_INFO, false)) {
+        shardInfo = new SimpleOrderedMap<>();
+        rb.rsp.getValues().add(ShardParams.SHARDS_INFO,shardInfo);
+      }
+
+      IndexSchema schema = rb.req.getSchema();
+      SchemaField uniqueKeyField = schema.getUniqueKeyField();
+
+      long numFound = 0;
+      Float maxScore=null;
+      boolean partialResults = false;
+      List<ShardDoc> shardDocs = new ArrayList();
+
+      for (ShardResponse srsp : sreq.responses) {
+        SolrDocumentList docs = null;
+
+        if(shardInfo!=null) {
+          SimpleOrderedMap<Object> nl = new SimpleOrderedMap<>();
+
+          if (srsp.getException() != null) {
+            Throwable t = srsp.getException();
+            if(t instanceof SolrServerException) {
+              t = ((SolrServerException)t).getCause();
+            }
+            nl.add("error", t.toString() );
+            StringWriter trace = new StringWriter();
+            t.printStackTrace(new PrintWriter(trace));
+            nl.add("trace", trace.toString() );
+            if (srsp.getShardAddress() != null) {
+              nl.add("shardAddress", srsp.getShardAddress());
+            }
+          }
+          else {
+            docs = (SolrDocumentList)srsp.getSolrResponse().getResponse().get("response");
+            nl.add("numFound", docs.getNumFound());
+            nl.add("maxScore", docs.getMaxScore());
+            nl.add("shardAddress", srsp.getShardAddress());
+          }
+          if(srsp.getSolrResponse()!=null) {
+            nl.add("time", srsp.getSolrResponse().getElapsedTime());
+          }
+
+          shardInfo.add(srsp.getShard(), nl);
+        }
+        // now that we've added the shard info, let's only proceed if we have no error.
+        if (srsp.getException() != null) {
+          partialResults = true;
+          continue;
+        }
+
+        if (docs == null) { // could have been initialized in the shards info block above
+          docs = (SolrDocumentList)srsp.getSolrResponse().getResponse().get("response");
+        }
+
+        NamedList<?> responseHeader = (NamedList<?>)srsp.getSolrResponse().getResponse().get("responseHeader");
+        if (responseHeader != null && Boolean.TRUE.equals(responseHeader.get(SolrQueryResponse.RESPONSE_HEADER_PARTIAL_RESULTS_KEY))) {
+          partialResults = true;
+        }
+
+        // calculate global maxScore and numDocsFound
+        if (docs.getMaxScore() != null) {
+          maxScore = maxScore==null ? docs.getMaxScore() : Math.max(maxScore, docs.getMaxScore());
+        }
+        numFound += docs.getNumFound();
+
+
+        for (int i=0; i<docs.size(); i++) {
+          SolrDocument doc = docs.get(i);
+          Object id = doc.getFieldValue(uniqueKeyField.getName());
+
+          String prevShard = uniqueDoc.put(id, srsp.getShard());
+          if (prevShard != null) {
+            // duplicate detected
+            numFound--;
+
+            // For now, just always use the first encountered since we can't currently
+            // remove the previous one added to the priority queue.  If we switched
+            // to the Java5 PriorityQueue, this would be easier.
+            continue;
+            // make which duplicate is used deterministic based on shard
+            // if (prevShard.compareTo(srsp.shard) >= 0) {
+            //  TODO: remove previous from priority queue
+            //  continue;
+            // }
+          }
+
+          ShardDoc shardDoc = new ShardDoc();
+          shardDoc.id = id;
+          shardDoc.shard = srsp.getShard();
+          shardDoc.orderInShard = i;
+          Object scoreObj = doc.getFieldValue("score");
+          if (scoreObj != null) {
+            if (scoreObj instanceof String) {
+              shardDoc.score = Float.parseFloat((String)scoreObj);
+            } else {
+              shardDoc.score = (Float)scoreObj;
+            }
+          }
+          shardDocs.add(shardDoc);
+        } // end for-each-doc-in-response
+      } // end for-each-response
+
+      Collections.sort(shardDocs, (o1, o2) -> {
+        if (o1.score < o2.score) {
+          return 1;
+        } else if (o1.score > o2.score) {
+          return -1;
+        } else {
+          return 0;  //To change body of implemented methods use File | Settings | File Templates.
+        }
+      });
+
+      int resultSize = shardDocs.size();
+
+      Map<Object,ShardDoc> resultIds = new HashMap<>();
+      for (int i=0; i<shardDocs.size(); i++) {
+        ShardDoc shardDoc = shardDocs.get(i);
+        shardDoc.positionInResponse = i;
+        // Need the toString() for correlation with other lists that must
+        // be strings (like keys in highlighting, explain, etc)
+        resultIds.put(shardDoc.id.toString(), shardDoc);
+      }
+
+      // Add hits for distributed requests
+      // https://issues.apache.org/jira/browse/SOLR-3518
+      rb.rsp.addToLog("hits", numFound);
+
+      SolrDocumentList responseDocs = new SolrDocumentList();
+      if (maxScore!=null) responseDocs.setMaxScore(maxScore);
+      responseDocs.setNumFound(numFound);
+      responseDocs.setStart(0);
+      // size appropriately
+      for (int i=0; i<resultSize; i++) responseDocs.add(null);
+
+      // save these results in a private area so we can access them
+      // again when retrieving stored fields.
+      // TODO: use ResponseBuilder (w/ comments) or the request context?
+      rb.resultIds = resultIds;
+      rb.setResponseDocs(responseDocs);
+
+      if (partialResults) {
+        rb.rsp.getResponseHeader().add(SolrQueryResponse.RESPONSE_HEADER_PARTIAL_RESULTS_KEY, Boolean.TRUE );
+      }
+    }
+  }
+
+  static class TestMergeStrategy1 implements MergeStrategy {
+
+    public int getCost() {
+      return 1;
+    }
+
+    public boolean mergesIds() {
+      return true;
+    }
+
+    public boolean handlesMergeFields() {
+      return true;
+    }
+
+    public void handleMergeFields(ResponseBuilder rb, SolrIndexSearcher searcher) throws IOException {
+      SolrQueryRequest req = rb.req;
+      SolrQueryResponse rsp = rb.rsp;
+      // The query cache doesn't currently store sort field values, and SolrIndexSearcher doesn't
+      // currently have an option to return sort field values.  Because of this, we
+      // take the documents given and re-derive the sort values.
+      //
+      // TODO: See SOLR-5595
+      boolean fsv = req.getParams().getBool(ResponseBuilder.FIELD_SORT_VALUES,false);
+      if(fsv){
+        NamedList<Object[]> sortVals = new NamedList<>(); // order is important for the sort fields
+        IndexReaderContext topReaderContext = searcher.getTopReaderContext();
+        List<LeafReaderContext> leaves = topReaderContext.leaves();
+        LeafReaderContext currentLeaf = null;
+        if (leaves.size()==1) {
+          // if there is a single segment, use that subReader and avoid looking up each time
+          currentLeaf = leaves.get(0);
+          leaves=null;
+        }
+
+        DocList docList = rb.getResults().docList;
+
+        // sort ids from lowest to highest so we can access them in order
+        int nDocs = docList.size();
+        final long[] sortedIds = new long[nDocs];
+        final float[] scores = new float[nDocs]; // doc scores, parallel to sortedIds
+        DocList docs = rb.getResults().docList;
+        DocIterator it = docs.iterator();
+        for (int i=0; i<nDocs; i++) {
+          sortedIds[i] = (((long)it.nextDoc()) << 32) | i;
+          scores[i] = docs.hasScores() ? it.score() : Float.NaN;
+        }
+
+        // sort ids and scores together
+        new InPlaceMergeSorter() {
+          @Override
+          protected void swap(int i, int j) {
+            long tmpId = sortedIds[i];
+            float tmpScore = scores[i];
+            sortedIds[i] = sortedIds[j];
+            scores[i] = scores[j];
+            sortedIds[j] = tmpId;
+            scores[j] = tmpScore;
+          }
+
+          @Override
+          protected int compare(int i, int j) {
+            return Long.compare(sortedIds[i], sortedIds[j]);
+          }
+        }.sort(0, sortedIds.length);
+
+        SortSpec sortSpec = rb.getSortSpec();
+        Sort sort = searcher.weightSort(sortSpec.getSort());
+        SortField[] sortFields = sort==null ? new SortField[]{SortField.FIELD_SCORE} : sort.getSort();
+        List<SchemaField> schemaFields = sortSpec.getSchemaFields();
+
+        for (int fld = 0; fld < schemaFields.size(); fld++) {
+          SchemaField schemaField = schemaFields.get(fld);
+          FieldType ft = null == schemaField? null : schemaField.getType();
+          SortField sortField = sortFields[fld];
+
+          SortField.Type type = sortField.getType();
+          // :TODO: would be simpler to always serialize every position of SortField[]
+          if (type==SortField.Type.SCORE || type==SortField.Type.DOC) continue;
+
+          FieldComparator<?> comparator = null;
+          LeafFieldComparator leafComparator = null;
+          Object[] vals = new Object[nDocs];
+
+          int lastIdx = -1;
+          int idx = 0;
+
+          for (int i = 0; i < sortedIds.length; ++i) {
+            long idAndPos = sortedIds[i];
+            float score = scores[i];
+            int doc = (int)(idAndPos >>> 32);
+            int position = (int)idAndPos;
+
+            if (leaves != null) {
+              idx = ReaderUtil.subIndex(doc, leaves);
+              currentLeaf = leaves.get(idx);
+              if (idx != lastIdx) {
+                // we switched segments.  invalidate comparator.
+                comparator = null;
+              }
+            }
+
+            if (comparator == null) {
+              comparator = sortField.getComparator(1,0);
+              leafComparator = comparator.getLeafComparator(currentLeaf);
+            }
+
+            doc -= currentLeaf.docBase;  // adjust for what segment this is in
+            leafComparator.setScorer(new ScoreAndDoc(doc, score));
+            leafComparator.copy(0, doc);
+            Object val = comparator.value(0);
+            if (null != ft) val = ft.marshalSortValue(val);
+            vals[position] = val;
+          }
+
+          sortVals.add(sortField.getField(), vals);
+        }
+
+        rsp.add("merge_values", sortVals);
+      }
+    }
+
+    private static class ScoreAndDoc extends Scorable {
+
+      final int docid;
+      final float score;
+
+      ScoreAndDoc(int docid, float score) {
+        this.docid = docid;
+        this.score = score;
+      }
+
+      @Override
+      public int docID() {
+        return docid;
+      }
+
+      @Override
+      public float score() {
+        return score;
+      }
+    }
+
+    public void merge(ResponseBuilder rb, ShardRequest sreq) {
+
+      // id to shard mapping, to eliminate any accidental dups
+      HashMap<Object,String> uniqueDoc = new HashMap<>();
+
+
+      NamedList<Object> shardInfo = null;
+      if(rb.req.getParams().getBool(ShardParams.SHARDS_INFO, false)) {
+        shardInfo = new SimpleOrderedMap<>();
+        rb.rsp.getValues().add(ShardParams.SHARDS_INFO,shardInfo);
+      }
+
+      IndexSchema schema = rb.req.getSchema();
+      SchemaField uniqueKeyField = schema.getUniqueKeyField();
+
+      long numFound = 0;
+      Float maxScore=null;
+      boolean partialResults = false;
+      List<ShardDoc> shardDocs = new ArrayList();
+
+      for (ShardResponse srsp : sreq.responses) {
+        SolrDocumentList docs = null;
+
+        if(shardInfo!=null) {
+          SimpleOrderedMap<Object> nl = new SimpleOrderedMap<>();
+
+          if (srsp.getException() != null) {
+            Throwable t = srsp.getException();
+            if(t instanceof SolrServerException) {
+              t = ((SolrServerException)t).getCause();
+            }
+            nl.add("error", t.toString() );
+            StringWriter trace = new StringWriter();
+            t.printStackTrace(new PrintWriter(trace));
+            nl.add("trace", trace.toString() );
+            if (srsp.getShardAddress() != null) {
+              nl.add("shardAddress", srsp.getShardAddress());
+            }
+          }
+          else {
+            docs = (SolrDocumentList)srsp.getSolrResponse().getResponse().get("response");
+            nl.add("numFound", docs.getNumFound());
+            nl.add("maxScore", docs.getMaxScore());
+            nl.add("shardAddress", srsp.getShardAddress());
+          }
+          if(srsp.getSolrResponse()!=null) {
+            nl.add("time", srsp.getSolrResponse().getElapsedTime());
+          }
+
+          shardInfo.add(srsp.getShard(), nl);
+        }
+        // now that we've added the shard info, let's only proceed if we have no error.
+        if (srsp.getException() != null) {
+          partialResults = true;
+          continue;
+        }
+
+        if (docs == null) { // could have been initialized in the shards info block above
+          docs = (SolrDocumentList)srsp.getSolrResponse().getResponse().get("response");
+        }
+
+        NamedList<?> responseHeader = (NamedList<?>)srsp.getSolrResponse().getResponse().get("responseHeader");
+        if (responseHeader != null && Boolean.TRUE.equals(responseHeader.get(SolrQueryResponse.RESPONSE_HEADER_PARTIAL_RESULTS_KEY))) {
+          partialResults = true;
+        }
+
+        // calculate global maxScore and numDocsFound
+        if (docs.getMaxScore() != null) {
+          maxScore = maxScore==null ? docs.getMaxScore() : Math.max(maxScore, docs.getMaxScore());
+        }
+        numFound += docs.getNumFound();
+
+        SortSpec ss = rb.getSortSpec();
+        Sort sort = ss.getSort();
+
+        NamedList sortFieldValues = (NamedList)(srsp.getSolrResponse().getResponse().get("merge_values"));
+        NamedList unmarshalledSortFieldValues = unmarshalSortValues(ss, sortFieldValues, schema);
+        List lst = (List)unmarshalledSortFieldValues.getVal(0);
+
+        for (int i=0; i<docs.size(); i++) {
+          SolrDocument doc = docs.get(i);
+          Object id = doc.getFieldValue(uniqueKeyField.getName());
+
+          String prevShard = uniqueDoc.put(id, srsp.getShard());
+          if (prevShard != null) {
+            // duplicate detected
+            numFound--;
+
+            // For now, just always use the first encountered since we can't currently
+            // remove the previous one added to the priority queue.  If we switched
+            // to the Java5 PriorityQueue, this would be easier.
+            continue;
+            // make which duplicate is used deterministic based on shard
+            // if (prevShard.compareTo(srsp.shard) >= 0) {
+            //  TODO: remove previous from priority queue
+            //  continue;
+            // }
+          }
+
+          ShardDoc shardDoc = new ShardDoc();
+          shardDoc.id = id;
+          shardDoc.shard = srsp.getShard();
+          shardDoc.orderInShard = i;
+          Object scoreObj = lst.get(i);
+          if (scoreObj != null) {
+            shardDoc.score = ((Integer)scoreObj).floatValue();
+          }
+          shardDocs.add(shardDoc);
+        } // end for-each-doc-in-response
+      } // end for-each-response
+
+      Collections.sort(shardDocs, (o1, o2) -> {
+        if (o1.score < o2.score) {
+          return 1;
+        } else if (o1.score > o2.score) {
+          return -1;
+        } else {
+          return 0;  //To change body of implemented methods use File | Settings | File Templates.
+        }
+      });
+
+      int resultSize = shardDocs.size();
+
+      Map<Object,ShardDoc> resultIds = new HashMap<>();
+      for (int i=0; i<shardDocs.size(); i++) {
+        ShardDoc shardDoc = shardDocs.get(i);
+        shardDoc.positionInResponse = i;
+        // Need the toString() for correlation with other lists that must
+        // be strings (like keys in highlighting, explain, etc)
+        resultIds.put(shardDoc.id.toString(), shardDoc);
+      }
+
+      // Add hits for distributed requests
+      // https://issues.apache.org/jira/browse/SOLR-3518
+      rb.rsp.addToLog("hits", numFound);
+
+      SolrDocumentList responseDocs = new SolrDocumentList();
+      if (maxScore!=null) responseDocs.setMaxScore(maxScore);
+      responseDocs.setNumFound(numFound);
+      responseDocs.setStart(0);
+      // size appropriately
+      for (int i=0; i<resultSize; i++) responseDocs.add(null);
+
+      // save these results in a private area so we can access them
+      // again when retrieving stored fields.
+      // TODO: use ResponseBuilder (w/ comments) or the request context?
+      rb.resultIds = resultIds;
+      rb.setResponseDocs(responseDocs);
+
+      if (partialResults) {
+        rb.rsp.getResponseHeader().add(SolrQueryResponse.RESPONSE_HEADER_PARTIAL_RESULTS_KEY, Boolean.TRUE );
+      }
+    }
+
+    private NamedList unmarshalSortValues(SortSpec sortSpec,
+                                          NamedList sortFieldValues,
+                                          IndexSchema schema) {
+      NamedList unmarshalledSortValsPerField = new NamedList();
+
+      if (0 == sortFieldValues.size()) return unmarshalledSortValsPerField;
+
+      List<SchemaField> schemaFields = sortSpec.getSchemaFields();
+      SortField[] sortFields = sortSpec.getSort().getSort();
+
+      int marshalledFieldNum = 0;
+      for (int sortFieldNum = 0; sortFieldNum < sortFields.length; sortFieldNum++) {
+        final SortField sortField = sortFields[sortFieldNum];
+        final SortField.Type type = sortField.getType();
+
+        // :TODO: would be simpler to always serialize every position of SortField[]
+        if (type==SortField.Type.SCORE || type==SortField.Type.DOC) continue;
+
+        final String sortFieldName = sortField.getField();
+        final String valueFieldName = sortFieldValues.getName(marshalledFieldNum);
+        assert sortFieldName.equals(valueFieldName)
+            : "sortFieldValues name key does not match expected SortField.getField";
+
+        List sortVals = (List)sortFieldValues.getVal(marshalledFieldNum);
+
+        final SchemaField schemaField = schemaFields.get(sortFieldNum);
+        if (null == schemaField) {
+          unmarshalledSortValsPerField.add(sortField.getField(), sortVals);
+        } else {
+          FieldType fieldType = schemaField.getType();
+          List unmarshalledSortVals = new ArrayList();
+          for (Object sortVal : sortVals) {
+            unmarshalledSortVals.add(fieldType.unmarshalSortValue(sortVal));
+          }
+          unmarshalledSortValsPerField.add(sortField.getField(), unmarshalledSortVals);
+        }
+        marshalledFieldNum++;
+      }
+      return unmarshalledSortValsPerField;
+    }
+  }
+
+
+  static class TestCollector extends TopDocsCollector {
+
+    private List<ScoreDoc> list = new ArrayList();
+
+    public TestCollector(PriorityQueue pq) {
+      super(pq);
+    }
+
+    @Override
+    public LeafCollector getLeafCollector(LeafReaderContext context) throws IOException {
+      final int base = context.docBase;
+      final NumericDocValues values = DocValues.getNumeric(context.reader(), "sort_i");
+      return new LeafCollector() {
+        
+        @Override
+        public void setScorer(Scorable scorer) throws IOException {}
+        
+        public void collect(int doc) throws IOException {
+          long value;
+          if (values.advanceExact(doc)) {
+            value = values.longValue();
+          } else {
+            value = 0;
+          }
+          list.add(new ScoreDoc(doc+base, (float) value));
+        }
+      };
+    }
+
+    public int topDocsSize() {
+      return list.size();
+    }
+
+    public TopDocs topDocs() {
+      Collections.sort(list, new Comparator() {
+        public int compare(Object o1, Object o2) {
+          ScoreDoc s1 = (ScoreDoc) o1;
+          ScoreDoc s2 = (ScoreDoc) o2;
+          if (s1.score == s2.score) {
+            return 0;
+          } else if (s1.score < s2.score) {
+            return 1;
+          } else {
+            return -1;
+          }
+        }
+      });
+      ScoreDoc[] scoreDocs = list.toArray(new ScoreDoc[list.size()]);
+      return new TopDocs(new TotalHits(list.size(), TotalHits.Relation.EQUAL_TO), scoreDocs);
+    }
+
+    public TopDocs topDocs(int start, int len) {
+      return topDocs();
+    }
+
+    public int getTotalHits() {
+      return list.size();
+    }
+    
+    @Override
+    public ScoreMode scoreMode() {
+      return ScoreMode.COMPLETE;
+    }
+  }
+
+  static class TestCollector1 extends TopDocsCollector {
+
+    private List<ScoreDoc> list = new ArrayList();
+
+    public TestCollector1(PriorityQueue pq) {
+      super(pq);
+    }
+
+    @Override
+    public LeafCollector getLeafCollector(LeafReaderContext context) throws IOException {
+      final int base = context.docBase;
+      return new LeafCollector() {
+        
+        Scorable scorer;
+        
+        @Override
+        public void setScorer(Scorable scorer) throws IOException {
+          this.scorer = scorer;
+        }
+        
+        public void collect(int doc) throws IOException {
+          list.add(new ScoreDoc(doc+base, scorer.score()));
+        }
+      };
+    }
+
+    public int topDocsSize() {
+      return list.size();
+    }
+
+    public TopDocs topDocs() {
+      Collections.sort(list, new Comparator() {
+        public int compare(Object o1, Object o2) {
+          ScoreDoc s1 = (ScoreDoc) o1;
+          ScoreDoc s2 = (ScoreDoc) o2;
+          if (s1.score == s2.score) {
+            return 0;
+          } else if (s1.score > s2.score) {
+            return 1;
+          } else {
+            return -1;
+          }
+        }
+      });
+      ScoreDoc[] scoreDocs = list.toArray(new ScoreDoc[list.size()]);
+      return new TopDocs(new TotalHits(list.size(), TotalHits.Relation.EQUAL_TO), scoreDocs);
+    }
+
+    public TopDocs topDocs(int start, int len) {
+      return topDocs();
+    }
+
+    public int getTotalHits() {
+      return list.size();
+    }
+    
+    @Override
+    public ScoreMode scoreMode() {
+      return ScoreMode.COMPLETE;
+    }
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/6a2de771/solr/core/src/test/org/apache/solr/search/TestRankQueryPlugin.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/search/TestRankQueryPlugin.java b/solr/core/src/test/org/apache/solr/search/TestRankQueryPlugin.java
deleted file mode 100644
index a678110..0000000
--- a/solr/core/src/test/org/apache/solr/search/TestRankQueryPlugin.java
+++ /dev/null
@@ -1,783 +0,0 @@
-/*
- * 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.
- */
-package org.apache.solr.search;
-
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.io.StringWriter;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import org.apache.lucene.index.DocValues;
-import org.apache.lucene.index.IndexReaderContext;
-import org.apache.lucene.index.LeafReaderContext;
-import org.apache.lucene.index.NumericDocValues;
-import org.apache.lucene.index.ReaderUtil;
-import org.apache.lucene.search.FieldComparator;
-import org.apache.lucene.search.IndexSearcher;
-import org.apache.lucene.search.LeafCollector;
-import org.apache.lucene.search.LeafFieldComparator;
-import org.apache.lucene.search.Query;
-import org.apache.lucene.search.Scorable;
-import org.apache.lucene.search.ScoreDoc;
-import org.apache.lucene.search.ScoreMode;
-import org.apache.lucene.search.Sort;
-import org.apache.lucene.search.SortField;
-import org.apache.lucene.search.TopDocs;
-import org.apache.lucene.search.TopDocsCollector;
-import org.apache.lucene.search.TotalHits;
-import org.apache.lucene.search.Weight;
-import org.apache.lucene.util.InPlaceMergeSorter;
-import org.apache.lucene.util.PriorityQueue;
-import org.apache.solr.client.solrj.SolrServerException;
-import org.apache.solr.common.SolrDocument;
-import org.apache.solr.common.SolrDocumentList;
-import org.apache.solr.common.params.ShardParams;
-import org.apache.solr.common.params.SolrParams;
-import org.apache.solr.common.util.NamedList;
-import org.apache.solr.common.util.SimpleOrderedMap;
-import org.apache.solr.handler.component.MergeStrategy;
-import org.apache.solr.handler.component.ResponseBuilder;
-import org.apache.solr.handler.component.ShardDoc;
-import org.apache.solr.handler.component.ShardRequest;
-import org.apache.solr.handler.component.ShardResponse;
-import org.apache.solr.request.SolrQueryRequest;
-import org.apache.solr.response.SolrQueryResponse;
-import org.apache.solr.schema.FieldType;
-import org.apache.solr.schema.IndexSchema;
-import org.apache.solr.schema.SchemaField;
-import org.junit.Ignore;
-
-
-@Ignore
-public class TestRankQueryPlugin extends QParserPlugin {
-
-
-  public QParser createParser(String query, SolrParams localParams, SolrParams params, SolrQueryRequest req) {
-    return new TestRankQueryParser(query, localParams, params, req);
-  }
-
-  static class TestRankQueryParser extends QParser {
-
-    public TestRankQueryParser(String query, SolrParams localParams, SolrParams params, SolrQueryRequest req) {
-      super(query, localParams, params, req);
-    }
-
-    public Query parse() throws SyntaxError {
-
-      int mergeStrategy = localParams.getInt("mergeStrategy", 0);
-      int collector = localParams.getInt("collector", 0);
-      return new TestRankQuery(collector, mergeStrategy);
-    }
-  }
-
-  static class TestRankQuery extends RankQuery {
-
-    private int mergeStrategy;
-    private int collector;
-    private Query q;
-
-    public int hashCode() {
-      return collector+q.hashCode();
-    }
-
-    public boolean equals(Object o) {
-      if(o instanceof TestRankQuery) {
-        TestRankQuery trq = (TestRankQuery)o;
-
-        return (trq.q.equals(q) && trq.collector == collector) ;
-      }
-
-      return false;
-    }
-
-    public Weight createWeight(IndexSearcher indexSearcher, ScoreMode scoreMode, float boost) throws IOException{
-      return q.createWeight(indexSearcher, scoreMode, boost);
-    }
-
-    @Override
-    public String toString(String field) {
-      return q.toString(field);
-    }
-
-    public RankQuery wrap(Query q) {
-      this.q = q;
-      return this;
-    }
-
-    public TestRankQuery(int collector, int mergeStrategy) {
-      this.collector = collector;
-      this.mergeStrategy = mergeStrategy;
-    }
-
-    public TopDocsCollector getTopDocsCollector(int len, QueryCommand cmd, IndexSearcher searcher) {
-      if(collector == 0)
-        return new TestCollector(null);
-      else
-        return new TestCollector1(null);
-    }
-
-    public MergeStrategy getMergeStrategy() {
-      if(mergeStrategy == 0)
-        return new TestMergeStrategy();
-      else
-        return new TestMergeStrategy1();
-    }
-  }
-
-  static class TestMergeStrategy implements MergeStrategy {
-
-    public int getCost() {
-      return 1;
-    }
-
-    public boolean mergesIds() {
-      return true;
-    }
-
-    public boolean handlesMergeFields() {
-      return false;
-    }
-
-    public void handleMergeFields(ResponseBuilder rb, SolrIndexSearcher searcher) {
-
-    }
-
-    public void merge(ResponseBuilder rb, ShardRequest sreq) {
-
-      // id to shard mapping, to eliminate any accidental dups
-      HashMap<Object,String> uniqueDoc = new HashMap<>();
-
-
-      NamedList<Object> shardInfo = null;
-      if(rb.req.getParams().getBool(ShardParams.SHARDS_INFO, false)) {
-        shardInfo = new SimpleOrderedMap<>();
-        rb.rsp.getValues().add(ShardParams.SHARDS_INFO,shardInfo);
-      }
-
-      IndexSchema schema = rb.req.getSchema();
-      SchemaField uniqueKeyField = schema.getUniqueKeyField();
-
-      long numFound = 0;
-      Float maxScore=null;
-      boolean partialResults = false;
-      List<ShardDoc> shardDocs = new ArrayList();
-
-      for (ShardResponse srsp : sreq.responses) {
-        SolrDocumentList docs = null;
-
-        if(shardInfo!=null) {
-          SimpleOrderedMap<Object> nl = new SimpleOrderedMap<>();
-
-          if (srsp.getException() != null) {
-            Throwable t = srsp.getException();
-            if(t instanceof SolrServerException) {
-              t = ((SolrServerException)t).getCause();
-            }
-            nl.add("error", t.toString() );
-            StringWriter trace = new StringWriter();
-            t.printStackTrace(new PrintWriter(trace));
-            nl.add("trace", trace.toString() );
-            if (srsp.getShardAddress() != null) {
-              nl.add("shardAddress", srsp.getShardAddress());
-            }
-          }
-          else {
-            docs = (SolrDocumentList)srsp.getSolrResponse().getResponse().get("response");
-            nl.add("numFound", docs.getNumFound());
-            nl.add("maxScore", docs.getMaxScore());
-            nl.add("shardAddress", srsp.getShardAddress());
-          }
-          if(srsp.getSolrResponse()!=null) {
-            nl.add("time", srsp.getSolrResponse().getElapsedTime());
-          }
-
-          shardInfo.add(srsp.getShard(), nl);
-        }
-        // now that we've added the shard info, let's only proceed if we have no error.
-        if (srsp.getException() != null) {
-          partialResults = true;
-          continue;
-        }
-
-        if (docs == null) { // could have been initialized in the shards info block above
-          docs = (SolrDocumentList)srsp.getSolrResponse().getResponse().get("response");
-        }
-
-        NamedList<?> responseHeader = (NamedList<?>)srsp.getSolrResponse().getResponse().get("responseHeader");
-        if (responseHeader != null && Boolean.TRUE.equals(responseHeader.get(SolrQueryResponse.RESPONSE_HEADER_PARTIAL_RESULTS_KEY))) {
-          partialResults = true;
-        }
-
-        // calculate global maxScore and numDocsFound
-        if (docs.getMaxScore() != null) {
-          maxScore = maxScore==null ? docs.getMaxScore() : Math.max(maxScore, docs.getMaxScore());
-        }
-        numFound += docs.getNumFound();
-
-
-        for (int i=0; i<docs.size(); i++) {
-          SolrDocument doc = docs.get(i);
-          Object id = doc.getFieldValue(uniqueKeyField.getName());
-
-          String prevShard = uniqueDoc.put(id, srsp.getShard());
-          if (prevShard != null) {
-            // duplicate detected
-            numFound--;
-
-            // For now, just always use the first encountered since we can't currently
-            // remove the previous one added to the priority queue.  If we switched
-            // to the Java5 PriorityQueue, this would be easier.
-            continue;
-            // make which duplicate is used deterministic based on shard
-            // if (prevShard.compareTo(srsp.shard) >= 0) {
-            //  TODO: remove previous from priority queue
-            //  continue;
-            // }
-          }
-
-          ShardDoc shardDoc = new ShardDoc();
-          shardDoc.id = id;
-          shardDoc.shard = srsp.getShard();
-          shardDoc.orderInShard = i;
-          Object scoreObj = doc.getFieldValue("score");
-          if (scoreObj != null) {
-            if (scoreObj instanceof String) {
-              shardDoc.score = Float.parseFloat((String)scoreObj);
-            } else {
-              shardDoc.score = (Float)scoreObj;
-            }
-          }
-          shardDocs.add(shardDoc);
-        } // end for-each-doc-in-response
-      } // end for-each-response
-
-      Collections.sort(shardDocs, (o1, o2) -> {
-        if (o1.score < o2.score) {
-          return 1;
-        } else if (o1.score > o2.score) {
-          return -1;
-        } else {
-          return 0;  //To change body of implemented methods use File | Settings | File Templates.
-        }
-      });
-
-      int resultSize = shardDocs.size();
-
-      Map<Object,ShardDoc> resultIds = new HashMap<>();
-      for (int i=0; i<shardDocs.size(); i++) {
-        ShardDoc shardDoc = shardDocs.get(i);
-        shardDoc.positionInResponse = i;
-        // Need the toString() for correlation with other lists that must
-        // be strings (like keys in highlighting, explain, etc)
-        resultIds.put(shardDoc.id.toString(), shardDoc);
-      }
-
-      // Add hits for distributed requests
-      // https://issues.apache.org/jira/browse/SOLR-3518
-      rb.rsp.addToLog("hits", numFound);
-
-      SolrDocumentList responseDocs = new SolrDocumentList();
-      if (maxScore!=null) responseDocs.setMaxScore(maxScore);
-      responseDocs.setNumFound(numFound);
-      responseDocs.setStart(0);
-      // size appropriately
-      for (int i=0; i<resultSize; i++) responseDocs.add(null);
-
-      // save these results in a private area so we can access them
-      // again when retrieving stored fields.
-      // TODO: use ResponseBuilder (w/ comments) or the request context?
-      rb.resultIds = resultIds;
-      rb.setResponseDocs(responseDocs);
-
-      if (partialResults) {
-        rb.rsp.getResponseHeader().add(SolrQueryResponse.RESPONSE_HEADER_PARTIAL_RESULTS_KEY, Boolean.TRUE );
-      }
-    }
-  }
-
-  static class TestMergeStrategy1 implements MergeStrategy {
-
-    public int getCost() {
-      return 1;
-    }
-
-    public boolean mergesIds() {
-      return true;
-    }
-
-    public boolean handlesMergeFields() {
-      return true;
-    }
-
-    public void handleMergeFields(ResponseBuilder rb, SolrIndexSearcher searcher) throws IOException {
-      SolrQueryRequest req = rb.req;
-      SolrQueryResponse rsp = rb.rsp;
-      // The query cache doesn't currently store sort field values, and SolrIndexSearcher doesn't
-      // currently have an option to return sort field values.  Because of this, we
-      // take the documents given and re-derive the sort values.
-      //
-      // TODO: See SOLR-5595
-      boolean fsv = req.getParams().getBool(ResponseBuilder.FIELD_SORT_VALUES,false);
-      if(fsv){
-        NamedList<Object[]> sortVals = new NamedList<>(); // order is important for the sort fields
-        IndexReaderContext topReaderContext = searcher.getTopReaderContext();
-        List<LeafReaderContext> leaves = topReaderContext.leaves();
-        LeafReaderContext currentLeaf = null;
-        if (leaves.size()==1) {
-          // if there is a single segment, use that subReader and avoid looking up each time
-          currentLeaf = leaves.get(0);
-          leaves=null;
-        }
-
-        DocList docList = rb.getResults().docList;
-
-        // sort ids from lowest to highest so we can access them in order
-        int nDocs = docList.size();
-        final long[] sortedIds = new long[nDocs];
-        final float[] scores = new float[nDocs]; // doc scores, parallel to sortedIds
-        DocList docs = rb.getResults().docList;
-        DocIterator it = docs.iterator();
-        for (int i=0; i<nDocs; i++) {
-          sortedIds[i] = (((long)it.nextDoc()) << 32) | i;
-          scores[i] = docs.hasScores() ? it.score() : Float.NaN;
-        }
-
-        // sort ids and scores together
-        new InPlaceMergeSorter() {
-          @Override
-          protected void swap(int i, int j) {
-            long tmpId = sortedIds[i];
-            float tmpScore = scores[i];
-            sortedIds[i] = sortedIds[j];
-            scores[i] = scores[j];
-            sortedIds[j] = tmpId;
-            scores[j] = tmpScore;
-          }
-
-          @Override
-          protected int compare(int i, int j) {
-            return Long.compare(sortedIds[i], sortedIds[j]);
-          }
-        }.sort(0, sortedIds.length);
-
-        SortSpec sortSpec = rb.getSortSpec();
-        Sort sort = searcher.weightSort(sortSpec.getSort());
-        SortField[] sortFields = sort==null ? new SortField[]{SortField.FIELD_SCORE} : sort.getSort();
-        List<SchemaField> schemaFields = sortSpec.getSchemaFields();
-
-        for (int fld = 0; fld < schemaFields.size(); fld++) {
-          SchemaField schemaField = schemaFields.get(fld);
-          FieldType ft = null == schemaField? null : schemaField.getType();
-          SortField sortField = sortFields[fld];
-
-          SortField.Type type = sortField.getType();
-          // :TODO: would be simpler to always serialize every position of SortField[]
-          if (type==SortField.Type.SCORE || type==SortField.Type.DOC) continue;
-
-          FieldComparator<?> comparator = null;
-          LeafFieldComparator leafComparator = null;
-          Object[] vals = new Object[nDocs];
-
-          int lastIdx = -1;
-          int idx = 0;
-
-          for (int i = 0; i < sortedIds.length; ++i) {
-            long idAndPos = sortedIds[i];
-            float score = scores[i];
-            int doc = (int)(idAndPos >>> 32);
-            int position = (int)idAndPos;
-
-            if (leaves != null) {
-              idx = ReaderUtil.subIndex(doc, leaves);
-              currentLeaf = leaves.get(idx);
-              if (idx != lastIdx) {
-                // we switched segments.  invalidate comparator.
-                comparator = null;
-              }
-            }
-
-            if (comparator == null) {
-              comparator = sortField.getComparator(1,0);
-              leafComparator = comparator.getLeafComparator(currentLeaf);
-            }
-
-            doc -= currentLeaf.docBase;  // adjust for what segment this is in
-            leafComparator.setScorer(new ScoreAndDoc(doc, score));
-            leafComparator.copy(0, doc);
-            Object val = comparator.value(0);
-            if (null != ft) val = ft.marshalSortValue(val);
-            vals[position] = val;
-          }
-
-          sortVals.add(sortField.getField(), vals);
-        }
-
-        rsp.add("merge_values", sortVals);
-      }
-    }
-
-    private static class ScoreAndDoc extends Scorable {
-
-      final int docid;
-      final float score;
-
-      ScoreAndDoc(int docid, float score) {
-        this.docid = docid;
-        this.score = score;
-      }
-
-      @Override
-      public int docID() {
-        return docid;
-      }
-
-      @Override
-      public float score() {
-        return score;
-      }
-    }
-
-    public void merge(ResponseBuilder rb, ShardRequest sreq) {
-
-      // id to shard mapping, to eliminate any accidental dups
-      HashMap<Object,String> uniqueDoc = new HashMap<>();
-
-
-      NamedList<Object> shardInfo = null;
-      if(rb.req.getParams().getBool(ShardParams.SHARDS_INFO, false)) {
-        shardInfo = new SimpleOrderedMap<>();
-        rb.rsp.getValues().add(ShardParams.SHARDS_INFO,shardInfo);
-      }
-
-      IndexSchema schema = rb.req.getSchema();
-      SchemaField uniqueKeyField = schema.getUniqueKeyField();
-
-      long numFound = 0;
-      Float maxScore=null;
-      boolean partialResults = false;
-      List<ShardDoc> shardDocs = new ArrayList();
-
-      for (ShardResponse srsp : sreq.responses) {
-        SolrDocumentList docs = null;
-
-        if(shardInfo!=null) {
-          SimpleOrderedMap<Object> nl = new SimpleOrderedMap<>();
-
-          if (srsp.getException() != null) {
-            Throwable t = srsp.getException();
-            if(t instanceof SolrServerException) {
-              t = ((SolrServerException)t).getCause();
-            }
-            nl.add("error", t.toString() );
-            StringWriter trace = new StringWriter();
-            t.printStackTrace(new PrintWriter(trace));
-            nl.add("trace", trace.toString() );
-            if (srsp.getShardAddress() != null) {
-              nl.add("shardAddress", srsp.getShardAddress());
-            }
-          }
-          else {
-            docs = (SolrDocumentList)srsp.getSolrResponse().getResponse().get("response");
-            nl.add("numFound", docs.getNumFound());
-            nl.add("maxScore", docs.getMaxScore());
-            nl.add("shardAddress", srsp.getShardAddress());
-          }
-          if(srsp.getSolrResponse()!=null) {
-            nl.add("time", srsp.getSolrResponse().getElapsedTime());
-          }
-
-          shardInfo.add(srsp.getShard(), nl);
-        }
-        // now that we've added the shard info, let's only proceed if we have no error.
-        if (srsp.getException() != null) {
-          partialResults = true;
-          continue;
-        }
-
-        if (docs == null) { // could have been initialized in the shards info block above
-          docs = (SolrDocumentList)srsp.getSolrResponse().getResponse().get("response");
-        }
-
-        NamedList<?> responseHeader = (NamedList<?>)srsp.getSolrResponse().getResponse().get("responseHeader");
-        if (responseHeader != null && Boolean.TRUE.equals(responseHeader.get(SolrQueryResponse.RESPONSE_HEADER_PARTIAL_RESULTS_KEY))) {
-          partialResults = true;
-        }
-
-        // calculate global maxScore and numDocsFound
-        if (docs.getMaxScore() != null) {
-          maxScore = maxScore==null ? docs.getMaxScore() : Math.max(maxScore, docs.getMaxScore());
-        }
-        numFound += docs.getNumFound();
-
-        SortSpec ss = rb.getSortSpec();
-        Sort sort = ss.getSort();
-
-        NamedList sortFieldValues = (NamedList)(srsp.getSolrResponse().getResponse().get("merge_values"));
-        NamedList unmarshalledSortFieldValues = unmarshalSortValues(ss, sortFieldValues, schema);
-        List lst = (List)unmarshalledSortFieldValues.getVal(0);
-
-        for (int i=0; i<docs.size(); i++) {
-          SolrDocument doc = docs.get(i);
-          Object id = doc.getFieldValue(uniqueKeyField.getName());
-
-          String prevShard = uniqueDoc.put(id, srsp.getShard());
-          if (prevShard != null) {
-            // duplicate detected
-            numFound--;
-
-            // For now, just always use the first encountered since we can't currently
-            // remove the previous one added to the priority queue.  If we switched
-            // to the Java5 PriorityQueue, this would be easier.
-            continue;
-            // make which duplicate is used deterministic based on shard
-            // if (prevShard.compareTo(srsp.shard) >= 0) {
-            //  TODO: remove previous from priority queue
-            //  continue;
-            // }
-          }
-
-          ShardDoc shardDoc = new ShardDoc();
-          shardDoc.id = id;
-          shardDoc.shard = srsp.getShard();
-          shardDoc.orderInShard = i;
-          Object scoreObj = lst.get(i);
-          if (scoreObj != null) {
-            shardDoc.score = ((Integer)scoreObj).floatValue();
-          }
-          shardDocs.add(shardDoc);
-        } // end for-each-doc-in-response
-      } // end for-each-response
-
-      Collections.sort(shardDocs, (o1, o2) -> {
-        if (o1.score < o2.score) {
-          return 1;
-        } else if (o1.score > o2.score) {
-          return -1;
-        } else {
-          return 0;  //To change body of implemented methods use File | Settings | File Templates.
-        }
-      });
-
-      int resultSize = shardDocs.size();
-
-      Map<Object,ShardDoc> resultIds = new HashMap<>();
-      for (int i=0; i<shardDocs.size(); i++) {
-        ShardDoc shardDoc = shardDocs.get(i);
-        shardDoc.positionInResponse = i;
-        // Need the toString() for correlation with other lists that must
-        // be strings (like keys in highlighting, explain, etc)
-        resultIds.put(shardDoc.id.toString(), shardDoc);
-      }
-
-      // Add hits for distributed requests
-      // https://issues.apache.org/jira/browse/SOLR-3518
-      rb.rsp.addToLog("hits", numFound);
-
-      SolrDocumentList responseDocs = new SolrDocumentList();
-      if (maxScore!=null) responseDocs.setMaxScore(maxScore);
-      responseDocs.setNumFound(numFound);
-      responseDocs.setStart(0);
-      // size appropriately
-      for (int i=0; i<resultSize; i++) responseDocs.add(null);
-
-      // save these results in a private area so we can access them
-      // again when retrieving stored fields.
-      // TODO: use ResponseBuilder (w/ comments) or the request context?
-      rb.resultIds = resultIds;
-      rb.setResponseDocs(responseDocs);
-
-      if (partialResults) {
-        rb.rsp.getResponseHeader().add(SolrQueryResponse.RESPONSE_HEADER_PARTIAL_RESULTS_KEY, Boolean.TRUE );
-      }
-    }
-
-    private NamedList unmarshalSortValues(SortSpec sortSpec,
-                                          NamedList sortFieldValues,
-                                          IndexSchema schema) {
-      NamedList unmarshalledSortValsPerField = new NamedList();
-
-      if (0 == sortFieldValues.size()) return unmarshalledSortValsPerField;
-
-      List<SchemaField> schemaFields = sortSpec.getSchemaFields();
-      SortField[] sortFields = sortSpec.getSort().getSort();
-
-      int marshalledFieldNum = 0;
-      for (int sortFieldNum = 0; sortFieldNum < sortFields.length; sortFieldNum++) {
-        final SortField sortField = sortFields[sortFieldNum];
-        final SortField.Type type = sortField.getType();
-
-        // :TODO: would be simpler to always serialize every position of SortField[]
-        if (type==SortField.Type.SCORE || type==SortField.Type.DOC) continue;
-
-        final String sortFieldName = sortField.getField();
-        final String valueFieldName = sortFieldValues.getName(marshalledFieldNum);
-        assert sortFieldName.equals(valueFieldName)
-            : "sortFieldValues name key does not match expected SortField.getField";
-
-        List sortVals = (List)sortFieldValues.getVal(marshalledFieldNum);
-
-        final SchemaField schemaField = schemaFields.get(sortFieldNum);
-        if (null == schemaField) {
-          unmarshalledSortValsPerField.add(sortField.getField(), sortVals);
-        } else {
-          FieldType fieldType = schemaField.getType();
-          List unmarshalledSortVals = new ArrayList();
-          for (Object sortVal : sortVals) {
-            unmarshalledSortVals.add(fieldType.unmarshalSortValue(sortVal));
-          }
-          unmarshalledSortValsPerField.add(sortField.getField(), unmarshalledSortVals);
-        }
-        marshalledFieldNum++;
-      }
-      return unmarshalledSortValsPerField;
-    }
-  }
-
-
-  static class TestCollector extends TopDocsCollector {
-
-    private List<ScoreDoc> list = new ArrayList();
-
-    public TestCollector(PriorityQueue pq) {
-      super(pq);
-    }
-
-    @Override
-    public LeafCollector getLeafCollector(LeafReaderContext context) throws IOException {
-      final int base = context.docBase;
-      final NumericDocValues values = DocValues.getNumeric(context.reader(), "sort_i");
-      return new LeafCollector() {
-        
-        @Override
-        public void setScorer(Scorable scorer) throws IOException {}
-        
-        public void collect(int doc) throws IOException {
-          long value;
-          if (values.advanceExact(doc)) {
-            value = values.longValue();
-          } else {
-            value = 0;
-          }
-          list.add(new ScoreDoc(doc+base, (float) value));
-        }
-      };
-    }
-
-    public int topDocsSize() {
-      return list.size();
-    }
-
-    public TopDocs topDocs() {
-      Collections.sort(list, new Comparator() {
-        public int compare(Object o1, Object o2) {
-          ScoreDoc s1 = (ScoreDoc) o1;
-          ScoreDoc s2 = (ScoreDoc) o2;
-          if (s1.score == s2.score) {
-            return 0;
-          } else if (s1.score < s2.score) {
-            return 1;
-          } else {
-            return -1;
-          }
-        }
-      });
-      ScoreDoc[] scoreDocs = list.toArray(new ScoreDoc[list.size()]);
-      return new TopDocs(new TotalHits(list.size(), TotalHits.Relation.EQUAL_TO), scoreDocs);
-    }
-
-    public TopDocs topDocs(int start, int len) {
-      return topDocs();
-    }
-
-    public int getTotalHits() {
-      return list.size();
-    }
-    
-    @Override
-    public ScoreMode scoreMode() {
-      return ScoreMode.COMPLETE;
-    }
-  }
-
-  static class TestCollector1 extends TopDocsCollector {
-
-    private List<ScoreDoc> list = new ArrayList();
-
-    public TestCollector1(PriorityQueue pq) {
-      super(pq);
-    }
-
-    @Override
-    public LeafCollector getLeafCollector(LeafReaderContext context) throws IOException {
-      final int base = context.docBase;
-      return new LeafCollector() {
-        
-        Scorable scorer;
-        
-        @Override
-        public void setScorer(Scorable scorer) throws IOException {
-          this.scorer = scorer;
-        }
-        
-        public void collect(int doc) throws IOException {
-          list.add(new ScoreDoc(doc+base, scorer.score()));
-        }
-      };
-    }
-
-    public int topDocsSize() {
-      return list.size();
-    }
-
-    public TopDocs topDocs() {
-      Collections.sort(list, new Comparator() {
-        public int compare(Object o1, Object o2) {
-          ScoreDoc s1 = (ScoreDoc) o1;
-          ScoreDoc s2 = (ScoreDoc) o2;
-          if (s1.score == s2.score) {
-            return 0;
-          } else if (s1.score > s2.score) {
-            return 1;
-          } else {
-            return -1;
-          }
-        }
-      });
-      ScoreDoc[] scoreDocs = list.toArray(new ScoreDoc[list.size()]);
-      return new TopDocs(new TotalHits(list.size(), TotalHits.Relation.EQUAL_TO), scoreDocs);
-    }
-
-    public TopDocs topDocs(int start, int len) {
-      return topDocs();
-    }
-
-    public int getTotalHits() {
-      return list.size();
-    }
-    
-    @Override
-    public ScoreMode scoreMode() {
-      return ScoreMode.COMPLETE;
-    }
-  }
-
-}