You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by ho...@apache.org on 2014/01/10 17:59:24 UTC

svn commit: r1557196 - in /lucene/dev/branches/branch_4x: ./ dev-tools/ lucene/ lucene/analysis/ lucene/analysis/common/src/java/org/apache/lucene/analysis/standard/std40/ lucene/analysis/icu/src/java/org/apache/lucene/collation/ lucene/backwards/ luce...

Author: hossman
Date: Fri Jan 10 16:59:22 2014
New Revision: 1557196

URL: http://svn.apache.org/r1557196
Log:
SOLR-5463: new 'cursorMark' request param for deep paging of sorted result sets (merge r1556036 and r1557192)

Added:
    lucene/dev/branches/branch_4x/solr/core/src/java/org/apache/solr/search/CursorMark.java
      - copied unchanged from r1556036, lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/CursorMark.java
    lucene/dev/branches/branch_4x/solr/core/src/test-files/solr/collection1/conf/schema-sorts.xml
      - copied unchanged from r1556036, lucene/dev/trunk/solr/core/src/test-files/solr/collection1/conf/schema-sorts.xml
    lucene/dev/branches/branch_4x/solr/core/src/test-files/solr/collection1/conf/solrconfig-deeppaging.xml
      - copied unchanged from r1556036, lucene/dev/trunk/solr/core/src/test-files/solr/collection1/conf/solrconfig-deeppaging.xml
    lucene/dev/branches/branch_4x/solr/core/src/test/org/apache/solr/CursorPagingTest.java
      - copied, changed from r1556036, lucene/dev/trunk/solr/core/src/test/org/apache/solr/CursorPagingTest.java
    lucene/dev/branches/branch_4x/solr/core/src/test/org/apache/solr/TestCursorMarkWithoutUniqueKey.java
      - copied unchanged from r1556036, lucene/dev/trunk/solr/core/src/test/org/apache/solr/TestCursorMarkWithoutUniqueKey.java
    lucene/dev/branches/branch_4x/solr/core/src/test/org/apache/solr/cloud/DistribCursorPagingTest.java
      - copied unchanged from r1556036, lucene/dev/trunk/solr/core/src/test/org/apache/solr/cloud/DistribCursorPagingTest.java
    lucene/dev/branches/branch_4x/solr/core/src/test/org/apache/solr/search/CursorMarkTest.java
      - copied unchanged from r1556036, lucene/dev/trunk/solr/core/src/test/org/apache/solr/search/CursorMarkTest.java
    lucene/dev/branches/branch_4x/solr/solrj/src/java/org/apache/solr/common/params/CursorMarkParams.java
      - copied unchanged from r1556036, lucene/dev/trunk/solr/solrj/src/java/org/apache/solr/common/params/CursorMarkParams.java
Modified:
    lucene/dev/branches/branch_4x/   (props changed)
    lucene/dev/branches/branch_4x/dev-tools/   (props changed)
    lucene/dev/branches/branch_4x/lucene/   (props changed)
    lucene/dev/branches/branch_4x/lucene/BUILD.txt   (props changed)
    lucene/dev/branches/branch_4x/lucene/CHANGES.txt   (props changed)
    lucene/dev/branches/branch_4x/lucene/JRE_VERSION_MIGRATION.txt   (props changed)
    lucene/dev/branches/branch_4x/lucene/LICENSE.txt   (props changed)
    lucene/dev/branches/branch_4x/lucene/MIGRATE.txt   (props changed)
    lucene/dev/branches/branch_4x/lucene/NOTICE.txt   (props changed)
    lucene/dev/branches/branch_4x/lucene/README.txt   (props changed)
    lucene/dev/branches/branch_4x/lucene/SYSTEM_REQUIREMENTS.txt   (props changed)
    lucene/dev/branches/branch_4x/lucene/analysis/   (props changed)
    lucene/dev/branches/branch_4x/lucene/analysis/common/src/java/org/apache/lucene/analysis/standard/std40/ASCIITLD.jflex-macro   (props changed)
    lucene/dev/branches/branch_4x/lucene/analysis/common/src/java/org/apache/lucene/analysis/standard/std40/SUPPLEMENTARY.jflex-macro   (props changed)
    lucene/dev/branches/branch_4x/lucene/analysis/common/src/java/org/apache/lucene/analysis/standard/std40/StandardTokenizerImpl40.java   (props changed)
    lucene/dev/branches/branch_4x/lucene/analysis/common/src/java/org/apache/lucene/analysis/standard/std40/StandardTokenizerImpl40.jflex   (props changed)
    lucene/dev/branches/branch_4x/lucene/analysis/common/src/java/org/apache/lucene/analysis/standard/std40/UAX29URLEmailTokenizerImpl40.java   (props changed)
    lucene/dev/branches/branch_4x/lucene/analysis/common/src/java/org/apache/lucene/analysis/standard/std40/UAX29URLEmailTokenizerImpl40.jflex   (props changed)
    lucene/dev/branches/branch_4x/lucene/analysis/common/src/java/org/apache/lucene/analysis/standard/std40/package.html   (props changed)
    lucene/dev/branches/branch_4x/lucene/analysis/icu/src/java/org/apache/lucene/collation/ICUCollationKeyFilterFactory.java   (props changed)
    lucene/dev/branches/branch_4x/lucene/backwards/   (props changed)
    lucene/dev/branches/branch_4x/lucene/benchmark/   (props changed)
    lucene/dev/branches/branch_4x/lucene/build.xml   (props changed)
    lucene/dev/branches/branch_4x/lucene/classification/   (props changed)
    lucene/dev/branches/branch_4x/lucene/classification/build.xml   (props changed)
    lucene/dev/branches/branch_4x/lucene/classification/ivy.xml   (props changed)
    lucene/dev/branches/branch_4x/lucene/classification/src/   (props changed)
    lucene/dev/branches/branch_4x/lucene/codecs/   (props changed)
    lucene/dev/branches/branch_4x/lucene/common-build.xml   (props changed)
    lucene/dev/branches/branch_4x/lucene/core/   (props changed)
    lucene/dev/branches/branch_4x/lucene/core/src/test/org/apache/lucene/index/TestBackwardsCompatibility.java   (props changed)
    lucene/dev/branches/branch_4x/lucene/core/src/test/org/apache/lucene/index/index.40.cfs.zip   (props changed)
    lucene/dev/branches/branch_4x/lucene/core/src/test/org/apache/lucene/index/index.40.nocfs.zip   (props changed)
    lucene/dev/branches/branch_4x/lucene/core/src/test/org/apache/lucene/index/index.40.optimized.cfs.zip   (props changed)
    lucene/dev/branches/branch_4x/lucene/core/src/test/org/apache/lucene/index/index.40.optimized.nocfs.zip   (props changed)
    lucene/dev/branches/branch_4x/lucene/core/src/test/org/apache/lucene/search/TestSort.java   (props changed)
    lucene/dev/branches/branch_4x/lucene/core/src/test/org/apache/lucene/search/TestSortDocValues.java   (props changed)
    lucene/dev/branches/branch_4x/lucene/core/src/test/org/apache/lucene/search/TestSortRandom.java   (props changed)
    lucene/dev/branches/branch_4x/lucene/core/src/test/org/apache/lucene/search/TestTopFieldCollector.java   (props changed)
    lucene/dev/branches/branch_4x/lucene/core/src/test/org/apache/lucene/search/TestTotalHitCountCollector.java   (props changed)
    lucene/dev/branches/branch_4x/lucene/demo/   (props changed)
    lucene/dev/branches/branch_4x/lucene/expressions/   (props changed)
    lucene/dev/branches/branch_4x/lucene/facet/   (props changed)
    lucene/dev/branches/branch_4x/lucene/grouping/   (props changed)
    lucene/dev/branches/branch_4x/lucene/highlighter/   (props changed)
    lucene/dev/branches/branch_4x/lucene/ivy-settings.xml   (props changed)
    lucene/dev/branches/branch_4x/lucene/ivy-versions.properties   (props changed)
    lucene/dev/branches/branch_4x/lucene/join/   (props changed)
    lucene/dev/branches/branch_4x/lucene/licenses/   (props changed)
    lucene/dev/branches/branch_4x/lucene/memory/   (props changed)
    lucene/dev/branches/branch_4x/lucene/misc/   (props changed)
    lucene/dev/branches/branch_4x/lucene/module-build.xml   (props changed)
    lucene/dev/branches/branch_4x/lucene/queries/   (props changed)
    lucene/dev/branches/branch_4x/lucene/queries/src/test/org/apache/lucene/queries/function/TestFunctionQuerySort.java   (props changed)
    lucene/dev/branches/branch_4x/lucene/queryparser/   (props changed)
    lucene/dev/branches/branch_4x/lucene/replicator/   (props changed)
    lucene/dev/branches/branch_4x/lucene/sandbox/   (props changed)
    lucene/dev/branches/branch_4x/lucene/site/   (props changed)
    lucene/dev/branches/branch_4x/lucene/spatial/   (props changed)
    lucene/dev/branches/branch_4x/lucene/suggest/   (props changed)
    lucene/dev/branches/branch_4x/lucene/test-framework/   (props changed)
    lucene/dev/branches/branch_4x/lucene/tools/   (props changed)
    lucene/dev/branches/branch_4x/solr/   (props changed)
    lucene/dev/branches/branch_4x/solr/CHANGES.txt
    lucene/dev/branches/branch_4x/solr/LICENSE.txt   (props changed)
    lucene/dev/branches/branch_4x/solr/NOTICE.txt   (props changed)
    lucene/dev/branches/branch_4x/solr/README.txt   (props changed)
    lucene/dev/branches/branch_4x/solr/SYSTEM_REQUIREMENTS.txt   (props changed)
    lucene/dev/branches/branch_4x/solr/build.xml   (props changed)
    lucene/dev/branches/branch_4x/solr/cloud-dev/   (props changed)
    lucene/dev/branches/branch_4x/solr/common-build.xml   (props changed)
    lucene/dev/branches/branch_4x/solr/contrib/   (props changed)
    lucene/dev/branches/branch_4x/solr/core/   (props changed)
    lucene/dev/branches/branch_4x/solr/core/src/java/org/apache/solr/handler/component/QueryComponent.java
    lucene/dev/branches/branch_4x/solr/core/src/java/org/apache/solr/handler/component/ResponseBuilder.java
    lucene/dev/branches/branch_4x/solr/core/src/java/org/apache/solr/search/QParser.java
    lucene/dev/branches/branch_4x/solr/core/src/java/org/apache/solr/search/SolrIndexSearcher.java
    lucene/dev/branches/branch_4x/solr/core/src/test/org/apache/solr/BasicFunctionalityTest.java
    lucene/dev/branches/branch_4x/solr/core/src/test/org/apache/solr/core/TestConfig.java   (props changed)
    lucene/dev/branches/branch_4x/solr/core/src/test/org/apache/solr/schema/SortableBinaryField.java
    lucene/dev/branches/branch_4x/solr/example/   (props changed)
    lucene/dev/branches/branch_4x/solr/licenses/   (props changed)
    lucene/dev/branches/branch_4x/solr/licenses/httpclient-LICENSE-ASL.txt   (props changed)
    lucene/dev/branches/branch_4x/solr/licenses/httpclient-NOTICE.txt   (props changed)
    lucene/dev/branches/branch_4x/solr/licenses/httpcore-LICENSE-ASL.txt   (props changed)
    lucene/dev/branches/branch_4x/solr/licenses/httpcore-NOTICE.txt   (props changed)
    lucene/dev/branches/branch_4x/solr/licenses/httpmime-LICENSE-ASL.txt   (props changed)
    lucene/dev/branches/branch_4x/solr/licenses/httpmime-NOTICE.txt   (props changed)
    lucene/dev/branches/branch_4x/solr/scripts/   (props changed)
    lucene/dev/branches/branch_4x/solr/site/   (props changed)
    lucene/dev/branches/branch_4x/solr/solrj/   (props changed)
    lucene/dev/branches/branch_4x/solr/solrj/src/java/org/apache/solr/client/solrj/response/QueryResponse.java
    lucene/dev/branches/branch_4x/solr/solrj/src/java/org/apache/solr/common/params/CommonParams.java
    lucene/dev/branches/branch_4x/solr/test-framework/   (props changed)
    lucene/dev/branches/branch_4x/solr/test-framework/src/java/org/apache/solr/BaseDistributedSearchTestCase.java
    lucene/dev/branches/branch_4x/solr/test-framework/src/java/org/apache/solr/SolrTestCaseJ4.java
    lucene/dev/branches/branch_4x/solr/webapp/   (props changed)

Modified: lucene/dev/branches/branch_4x/solr/CHANGES.txt
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_4x/solr/CHANGES.txt?rev=1557196&r1=1557195&r2=1557196&view=diff
==============================================================================
--- lucene/dev/branches/branch_4x/solr/CHANGES.txt (original)
+++ lucene/dev/branches/branch_4x/solr/CHANGES.txt Fri Jan 10 16:59:22 2014
@@ -103,6 +103,9 @@ New Features
 * SOLR-5541: Allow QueryElevationComponent to accept elevateIds and excludeIds 
   as http parameters (Joel Bernstein)
 
+* SOLR-5463: new 'cursorMark' request param for deep paging of sorted result sets
+  (sarowe, hossman)
+
 Bug Fixes
 ----------------------
 

Modified: lucene/dev/branches/branch_4x/solr/core/src/java/org/apache/solr/handler/component/QueryComponent.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_4x/solr/core/src/java/org/apache/solr/handler/component/QueryComponent.java?rev=1557196&r1=1557195&r2=1557196&view=diff
==============================================================================
--- lucene/dev/branches/branch_4x/solr/core/src/java/org/apache/solr/handler/component/QueryComponent.java (original)
+++ lucene/dev/branches/branch_4x/solr/core/src/java/org/apache/solr/handler/component/QueryComponent.java Fri Jan 10 16:59:22 2014
@@ -17,8 +17,6 @@
 
 package org.apache.solr.handler.component;
 
-import org.apache.lucene.document.Field;
-import org.apache.lucene.document.StringField;
 import org.apache.lucene.index.AtomicReaderContext;
 import org.apache.lucene.index.IndexReaderContext;
 import org.apache.lucene.index.ReaderUtil;
@@ -33,12 +31,12 @@ import org.apache.lucene.search.grouping
 import org.apache.lucene.search.grouping.SearchGroup;
 import org.apache.lucene.search.grouping.TopGroups;
 import org.apache.lucene.util.BytesRef;
-import org.apache.lucene.util.CharsRef;
 import org.apache.solr.client.solrj.SolrServerException;
 import org.apache.solr.common.SolrDocument;
 import org.apache.solr.common.SolrDocumentList;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.params.*;
+import org.apache.solr.common.params.CursorMarkParams;
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.common.util.SimpleOrderedMap;
 import org.apache.solr.common.util.StrUtils;
@@ -48,6 +46,7 @@ import org.apache.solr.response.SolrQuer
 import org.apache.solr.schema.FieldType;
 import org.apache.solr.schema.IndexSchema;
 import org.apache.solr.schema.SchemaField;
+import org.apache.solr.search.CursorMark;
 import org.apache.solr.search.DocIterator;
 import org.apache.solr.search.DocList;
 import org.apache.solr.search.DocListAndSet;
@@ -82,6 +81,8 @@ import org.apache.solr.search.grouping.e
 import org.apache.solr.search.grouping.endresulttransformer.SimpleEndResultTransformer;
 import org.apache.solr.util.SolrPluginUtils;
 
+import org.apache.commons.lang.StringUtils;
+
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.io.StringWriter;
@@ -147,8 +148,15 @@ public class QueryComponent extends Sear
       rb.setQuery( q );
       rb.setSortSpec( parser.getSort(true) );
       rb.setQparser(parser);
-      rb.setScoreDoc(parser.getPaging());
       
+      final String cursorStr = rb.req.getParams().get(CursorMarkParams.CURSOR_MARK_PARAM);
+      if (null != cursorStr) {
+        final CursorMark cursorMark = new CursorMark(rb.req.getSchema(),
+                                                     rb.getSortSpec());
+        cursorMark.parseSerializedTotem(cursorStr);
+        rb.setCursorMark(cursorMark);
+      }
+
       String[] fqs = req.getParams().getParams(CommonParams.FQ);
       if (fqs!=null && fqs.length!=0) {
         List<Query> filters = rb.getFilters();
@@ -171,11 +179,23 @@ public class QueryComponent extends Sear
       throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, e);
     }
 
-    boolean grouping = params.getBool(GroupParams.GROUP, false);
-    if (!grouping) {
-      return;
+    if (params.getBool(GroupParams.GROUP, false)) {
+      prepareGrouping(rb);
     }
+  }
+
+  private void prepareGrouping(ResponseBuilder rb) throws IOException {
+
+    SolrQueryRequest req = rb.req;
+    SolrParams params = req.getParams();
 
+    if (null != rb.getCursorMark()) {
+      // It's hard to imagine, conceptually, what it would mean to combine
+      // grouping with a cursor - so for now we just don't allow the combination at all
+      throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Can not use Grouping with " + 
+                              CursorMarkParams.CURSOR_MARK_PARAM);
+    }
+ 
     SolrIndexSearcher.QueryCommand cmd = rb.getQueryCommand();
     SolrIndexSearcher searcher = rb.req.getSearcher();
     GroupingSpecification groupingSpec = new GroupingSpecification();
@@ -242,6 +262,11 @@ public class QueryComponent extends Sear
 
     // -1 as flag if not set.
     long timeAllowed = (long)params.getInt( CommonParams.TIME_ALLOWED, -1 );
+    if (null != rb.getCursorMark() && 0 < timeAllowed) {
+      // fundementally incompatible
+      throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Can not search using both " + 
+                              CursorMarkParams.CURSOR_MARK_PARAM + " and " + CommonParams.TIME_ALLOWED);
+    }
 
     // Optional: This could also be implemented by the top-level searcher sending
     // a filter that lists the ids... that would be transparent to
@@ -434,13 +459,18 @@ public class QueryComponent extends Sear
     searcher.search(result,cmd);
     rb.setResult( result );
 
-
     ResultContext ctx = new ResultContext();
     ctx.docs = rb.getResults().docList;
     ctx.query = rb.getQuery();
     rsp.add("response", ctx);
     rsp.getToLog().add("hits", rb.getResults().docList.matches());
 
+    if ( ! rb.req.getParams().getBool(ShardParams.IS_SHARD,false) ) {
+      if (null != rb.getNextCursorMark()) {
+        rb.rsp.add(CursorMarkParams.CURSOR_MARK_NEXT, 
+                   rb.getNextCursorMark().getSerializedTotem());
+      }
+    }
     doFieldSortValues(rb, searcher);
     doPrefetch(rb);
   }
@@ -452,6 +482,8 @@ public class QueryComponent extends Sear
     // 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<Object[]>(); // order is important for the sort fields
@@ -696,6 +728,10 @@ public class QueryComponent extends Sear
     }
 
     rb.rsp.add("response", rb._responseDocs);
+    if (null != rb.getNextCursorMark()) {
+      rb.rsp.add(CursorMarkParams.CURSOR_MARK_NEXT, 
+                 rb.getNextCursorMark().getSerializedTotem());
+    }
   }
 
   private void createDistributedIdf(ResponseBuilder rb) {
@@ -904,11 +940,66 @@ public class QueryComponent extends Sear
       // TODO: use ResponseBuilder (w/ comments) or the request context?
       rb.resultIds = resultIds;
       rb._responseDocs = responseDocs;
+
+      populateNextCursorMarkFromMergedShards(rb);
+
       if (partialResults) {
         rb.rsp.getResponseHeader().add( "partialResults", Boolean.TRUE );
       }
   }
 
+  /**
+   * Inspects the state of the {@link ResponseBuilder} and populates the next 
+   * {@link ResponseBuilder#setNextCursorMark} as appropriate based on the merged 
+   * sort values from individual shards
+   *
+   * @param rb A <code>ResponseBuilder</code> that already contains merged 
+   *           <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) {
+
+    final CursorMark lastCursorMark = rb.getCursorMark();
+    if (null == lastCursorMark) {
+      // Not a cursor based request
+      return; // NOOP
+    }
+
+    assert null != rb.resultIds : "resultIds was not set in ResponseBuilder";
+
+    Collection<ShardDoc> docsOnThisPage = rb.resultIds.values();
+
+    if (0 == docsOnThisPage.size()) {
+      // nothing more matching query, re-use existing totem so user can "resume" 
+      // search later if it makes sense for this sort.
+      rb.setNextCursorMark(lastCursorMark);
+      return;
+    }
+
+    ShardDoc lastDoc = null;
+    // ShardDoc and rb.resultIds are weird structures to work with...
+    for (ShardDoc eachDoc : docsOnThisPage) {
+      if (null == lastDoc || lastDoc.positionInResponse  < eachDoc.positionInResponse) {
+        lastDoc = eachDoc;
+      }
+    }
+    SortField[] sortFields = lastCursorMark.getSortSpec().getSort().getSort();
+    List<Object> nextCursorMarkValues = new ArrayList<Object>(sortFields.length);
+    for (SortField sf : sortFields) {
+      if (sf.getType().equals(SortField.Type.SCORE)) {
+        assert null != lastDoc.score : "lastDoc has null score";
+        nextCursorMarkValues.add(lastDoc.score);
+      } else {
+        assert null != sf.getField() : "SortField has null field";
+        List<Object> fieldVals = (List<Object>) lastDoc.sortFieldValues.get(sf.getField());
+        nextCursorMarkValues.add(fieldVals.get(lastDoc.orderInShard));
+      }
+    }
+    CursorMark nextCursorMark = lastCursorMark.createNext(nextCursorMarkValues);
+    assert null != nextCursorMark : "null nextCursorMark";
+    rb.setNextCursorMark(nextCursorMark);
+  }
+
   private NamedList unmarshalSortValues(SortSpec sortSpec, 
                                         NamedList sortFieldValues, 
                                         IndexSchema schema) {
@@ -982,6 +1073,7 @@ public class QueryComponent extends Sear
 
       // no need for a sort, we already have order
       sreq.params.remove(CommonParams.SORT);
+      sreq.params.remove(CursorMarkParams.CURSOR_MARK_PARAM);
 
       // we already have the field sort values
       sreq.params.remove(ResponseBuilder.FIELD_SORT_VALUES);

Modified: lucene/dev/branches/branch_4x/solr/core/src/java/org/apache/solr/handler/component/ResponseBuilder.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_4x/solr/core/src/java/org/apache/solr/handler/component/ResponseBuilder.java?rev=1557196&r1=1557195&r2=1557196&view=diff
==============================================================================
--- lucene/dev/branches/branch_4x/solr/core/src/java/org/apache/solr/handler/component/ResponseBuilder.java (original)
+++ lucene/dev/branches/branch_4x/solr/core/src/java/org/apache/solr/handler/component/ResponseBuilder.java Fri Jan 10 16:59:22 2014
@@ -18,7 +18,6 @@
 package org.apache.solr.handler.component;
 
 import org.apache.lucene.search.Query;
-import org.apache.lucene.search.ScoreDoc;
 import org.apache.lucene.search.grouping.SearchGroup;
 import org.apache.lucene.search.grouping.TopGroups;
 import org.apache.lucene.util.BytesRef;
@@ -30,6 +29,7 @@ import org.apache.solr.common.util.Simpl
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.request.SolrRequestInfo;
 import org.apache.solr.response.SolrQueryResponse;
+import org.apache.solr.search.CursorMark;
 import org.apache.solr.search.DocListAndSet;
 import org.apache.solr.search.QParser;
 import org.apache.solr.search.SolrIndexSearcher;
@@ -70,9 +70,8 @@ public class ResponseBuilder
   private List<Query> filters = null;
   private SortSpec sortSpec = null;
   private GroupingSpecification groupingSpec;
-  //used for handling deep paging
-  private ScoreDoc scoreDoc;
-
+  private CursorMark cursorMark;
+  private CursorMark nextCursorMark;
 
   private DocListAndSet results = null;
   private NamedList<Object> debugInfo = null;
@@ -395,7 +394,7 @@ public class ResponseBuilder
             .setLen(getSortSpec().getCount())
             .setFlags(getFieldFlags())
             .setNeedDocSet(isNeedDocSet())
-            .setScoreDoc(getScoreDoc()); //Issue 1726
+            .setCursorMark(getCursorMark());
     return cmd;
   }
 
@@ -407,6 +406,10 @@ public class ResponseBuilder
     if (result.isPartialResults()) {
       rsp.getResponseHeader().add("partialResults", Boolean.TRUE);
     }
+    if (null != cursorMark) {
+      assert null != result.getNextCursorMark() : "using cursor but no next cursor set";
+      this.setNextCursorMark(result.getNextCursorMark());
+    }
   }
   
   public long getNumberDocumentsFound() {
@@ -416,13 +419,17 @@ public class ResponseBuilder
     return _responseDocs.getNumFound();
   }
 
-  public ScoreDoc getScoreDoc()
-  {
-    return scoreDoc;
+  public CursorMark getCursorMark() {
+    return cursorMark;
   }
-  
-  public void setScoreDoc(ScoreDoc scoreDoc)
-  {
-    this.scoreDoc = scoreDoc;
+  public void setCursorMark(CursorMark cursorMark) {
+    this.cursorMark = cursorMark;
+  }
+
+  public CursorMark getNextCursorMark() {
+    return nextCursorMark;
+  }
+  public void setNextCursorMark(CursorMark nextCursorMark) {
+    this.nextCursorMark = nextCursorMark;
   }
 }

Modified: lucene/dev/branches/branch_4x/solr/core/src/java/org/apache/solr/search/QParser.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_4x/solr/core/src/java/org/apache/solr/search/QParser.java?rev=1557196&r1=1557195&r2=1557196&view=diff
==============================================================================
--- lucene/dev/branches/branch_4x/solr/core/src/java/org/apache/solr/search/QParser.java (original)
+++ lucene/dev/branches/branch_4x/solr/core/src/java/org/apache/solr/search/QParser.java Fri Jan 10 16:59:22 2014
@@ -17,7 +17,6 @@
 package org.apache.solr.search;
 
 import org.apache.lucene.search.Query;
-import org.apache.lucene.search.ScoreDoc; //Issue 1726
 import org.apache.lucene.search.Sort;
 import org.apache.solr.common.params.CommonParams;
 import org.apache.solr.common.params.MapSolrParams;
@@ -209,37 +208,6 @@ public abstract class QParser {
   }
 
   /**
-   * use common params to look up pageScore and pageDoc in global params
-   * @return the ScoreDoc
-   */
-  public ScoreDoc getPaging() throws SyntaxError
-  {
-    return null;
-
-    /*** This is not ready for prime-time... see SOLR-1726
-
-    String pageScoreS = null;
-    String pageDocS = null;
-
-    pageScoreS = params.get(CommonParams.PAGESCORE);
-    pageDocS = params.get(CommonParams.PAGEDOC);
-
-    if (pageScoreS == null || pageDocS == null)
-      return null;
-
-    int pageDoc = pageDocS != null ? Integer.parseInt(pageDocS) : -1;
-    float pageScore = pageScoreS != null ? new Float(pageScoreS) : -1;
-    if(pageDoc != -1 && pageScore != -1){
-      return new ScoreDoc(pageDoc, pageScore);
-    }
-    else {
-      return null;
-    }
-
-    ***/
-  }
-  
-  /**
    * @param useGlobalParams look up sort, start, rows in global params if not in local params
    * @return the sort specification
    */

Modified: lucene/dev/branches/branch_4x/solr/core/src/java/org/apache/solr/search/SolrIndexSearcher.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_4x/solr/core/src/java/org/apache/solr/search/SolrIndexSearcher.java?rev=1557196&r1=1557195&r2=1557196&view=diff
==============================================================================
--- lucene/dev/branches/branch_4x/solr/core/src/java/org/apache/solr/search/SolrIndexSearcher.java (original)
+++ lucene/dev/branches/branch_4x/solr/core/src/java/org/apache/solr/search/SolrIndexSearcher.java Fri Jan 10 16:59:22 2014
@@ -69,6 +69,7 @@ import org.apache.lucene.search.Filter;
 import org.apache.lucene.search.IndexSearcher;
 import org.apache.lucene.search.MatchAllDocsQuery;
 import org.apache.lucene.search.Query;
+import org.apache.lucene.search.FieldDoc;
 import org.apache.lucene.search.ScoreDoc;
 import org.apache.lucene.search.Scorer;
 import org.apache.lucene.search.Sort;
@@ -76,6 +77,7 @@ import org.apache.lucene.search.SortFiel
 import org.apache.lucene.search.TermQuery;
 import org.apache.lucene.search.TimeLimitingCollector;
 import org.apache.lucene.search.TopDocs;
+import org.apache.lucene.search.TopFieldDocs;
 import org.apache.lucene.search.TopDocsCollector;
 import org.apache.lucene.search.TopFieldCollector;
 import org.apache.lucene.search.TopScoreDocCollector;
@@ -1345,6 +1347,7 @@ public class SolrIndexSearcher extends I
         key = null;  // we won't be caching the result
       }
     }
+    cmd.setSupersetMaxDoc(supersetMaxDoc);
 
 
     // OK, so now we need to generate an answer.
@@ -1367,7 +1370,6 @@ public class SolrIndexSearcher extends I
       }
     }
 
-    // disable useFilterCache optimization temporarily
     if (useFilterCache) {
       // now actually use the filter cache.
       // for large filters that match few documents, this may be
@@ -1380,11 +1382,9 @@ public class SolrIndexSearcher extends I
       // todo: there could be a sortDocSet that could take a list of
       // the filters instead of anding them first...
       // perhaps there should be a multi-docset-iterator
-      superset = sortDocSet(out.docSet,cmd.getSort(),supersetMaxDoc);
-      out.docList = superset.subset(cmd.getOffset(),cmd.getLen());
+      sortDocSet(qr, cmd);
     } else {
       // do it the normal way...
-      cmd.setSupersetMaxDoc(supersetMaxDoc);
       if ((flags & GET_DOCSET)!=0) {
         // this currently conflates returning the docset for the base query vs
         // the base query and all filters.
@@ -1393,11 +1393,28 @@ public class SolrIndexSearcher extends I
         if (qDocSet!=null && filterCache!=null && !qr.isPartialResults()) filterCache.put(cmd.getQuery(),qDocSet);
       } else {
         getDocListNC(qr,cmd);
-        //Parameters: cmd.getQuery(),theFilt,cmd.getSort(),0,supersetMaxDoc,cmd.getFlags(),cmd.getTimeAllowed(),responseHeader);
       }
+      assert null != out.docList : "docList is null";
+    }
 
+    if (null == cmd.getCursorMark()) {
+      // Kludge...
+      // we can't use DocSlice.subset, even though it should be an identity op
+      // because it gets confused by situations where there are lots of matches, but
+      // less docs in the slice then were requested, (due to the cursor)
+      // so we have to short circuit the call.
+      // None of which is really a problem since we can't use caching with
+      // cursors anyway, but it still looks weird to have to special case this
+      // behavior based on this condition - hence the long explanation.
       superset = out.docList;
       out.docList = superset.subset(cmd.getOffset(),cmd.getLen());
+    } else {
+      // sanity check our cursor assumptions
+      assert null == superset : "cursor: superset isn't null";
+      assert 0 == cmd.getOffset() : "cursor: command offset mismatch";
+      assert 0 == out.docList.offset() : "cursor: docList offset mismatch";
+      assert cmd.getLen() >= supersetMaxDoc : "cursor: superset len mismatch: " +
+        cmd.getLen() + " vs " + supersetMaxDoc;
     }
 
     // lastly, put the superset in the cache if the size is less than or equal
@@ -1407,7 +1424,76 @@ public class SolrIndexSearcher extends I
     }
   }
 
+  /**
+   * Helper method for extracting the {@link FieldDoc} sort values from a 
+   * {@link TopFieldDocs} when available and making the appropriate call to 
+   * {@link QueryResult#setNextCursorMark} when applicable.
+   *
+   * @param qr <code>QueryResult</code> to modify
+   * @param qc <code>QueryCommand</code> for context of method
+   * @param topDocs May or may not be a <code>TopFieldDocs</code> 
+   */
+  private void populateNextCursorMarkFromTopDocs(QueryResult qr, QueryCommand qc, 
+                                                 TopDocs topDocs) {
+    // TODO: would be nice to rename & generalize this method for non-cursor cases...
+    // ...would be handy to reuse the ScoreDoc/FieldDoc sort vals directly in distrib sort
+    // ...but that has non-trivial queryResultCache implications
+    // See: SOLR-5595
+    
+    if (null == qc.getCursorMark()) {
+      // nothing to do, short circuit out
+      return;
+    }
 
+    final CursorMark lastCursorMark = qc.getCursorMark();
+    
+    // if we have a cursor, then we have a sort that at minimum involves uniqueKey..
+    // so we must have a TopFieldDocs containing FieldDoc[]
+    assert topDocs instanceof TopFieldDocs : "TopFieldDocs cursor constraint violated";
+    final TopFieldDocs topFieldDocs = (TopFieldDocs) topDocs;
+    final ScoreDoc[] scoreDocs = topFieldDocs.scoreDocs;
+
+    if (0 == scoreDocs.length) {
+      // no docs on this page, re-use existing cursor mark
+      qr.setNextCursorMark(lastCursorMark);
+    } else {
+      ScoreDoc lastDoc = scoreDocs[scoreDocs.length-1];
+      assert lastDoc instanceof FieldDoc : "FieldDoc cursor constraint violated";
+      
+      List<Object> lastFields = Arrays.<Object>asList(((FieldDoc)lastDoc).fields);
+      CursorMark nextCursorMark = lastCursorMark.createNext(lastFields);
+      assert null != nextCursorMark : "null nextCursorMark";
+      qr.setNextCursorMark(nextCursorMark);
+    }
+  }
+
+  /**
+   * Helper method for inspecting QueryCommand and creating the appropriate 
+   * {@link TopDocsCollector}
+   *
+   * @param len the number of docs to return
+   * @param cmd The Command whose properties should determine the type of 
+   *        TopDocsCollector to use.
+   */
+  private TopDocsCollector buildTopDocsCollector(int len, QueryCommand cmd) throws IOException {
+    
+    if (null == cmd.getSort()) {
+      assert null == cmd.getCursorMark() : "have cursor but no sort";
+      return TopScoreDocCollector.create(len, true);
+    } else {
+      // we have a sort
+      final boolean needScores = (cmd.getFlags() & GET_SCORES) != 0;
+      final Sort weightedSort = weightSort(cmd.getSort());
+      final CursorMark cursor = cmd.getCursorMark();
+
+      // :TODO: make fillFields it's own QueryCommand flag? ...
+      // ... see comments in populateNextCursorMarkFromTopDocs for cache issues (SOLR-5595)
+      final boolean fillFields = (null != cursor);
+      final FieldDoc searchAfter = (null != cursor ? cursor.getSearchAfterFieldDoc() : null);
+      return TopFieldCollector.create(weightedSort, len, searchAfter,
+                                      fillFields, needScores, needScores, true); 
+    }
+  }
 
   private void getDocListNC(QueryResult qr,QueryCommand cmd) throws IOException {
     final long timeAllowed = cmd.getTimeAllowed();
@@ -1502,18 +1588,10 @@ public class SolrIndexSearcher extends I
       scores = new float[nDocsReturned];
       totalHits = numHits[0];
       maxScore = totalHits>0 ? topscore[0] : 0.0f;
+      // no docs on this page, so cursor doesn't change
+      qr.setNextCursorMark(cmd.getCursorMark());
     } else {
-      TopDocsCollector topCollector;
-      if (cmd.getSort() == null) {
-        if(cmd.getScoreDoc() != null) {
-          topCollector = TopScoreDocCollector.create(len, cmd.getScoreDoc(), true); //create the Collector with InOrderPagingCollector
-        } else {
-          topCollector = TopScoreDocCollector.create(len, true);
-        }
-
-      } else {
-        topCollector = TopFieldCollector.create(weightSort(cmd.getSort()), len, false, needScores, needScores, true);
-      }
+      final TopDocsCollector topCollector = buildTopDocsCollector(len, cmd);
       Collector collector = topCollector;
       if (terminateEarly) {
         collector = new EarlyTerminatingCollector(collector, cmd.len);
@@ -1538,6 +1616,8 @@ public class SolrIndexSearcher extends I
 
       totalHits = topCollector.getTotalHits();
       TopDocs topDocs = topCollector.topDocs(0, len);
+      populateNextCursorMarkFromTopDocs(qr, cmd, topDocs);
+
       maxScore = totalHits>0 ? topDocs.getMaxScore() : 0.0f;
       nDocsReturned = topDocs.scoreDocs.length;
       ids = new int[nDocsReturned];
@@ -1638,16 +1718,11 @@ public class SolrIndexSearcher extends I
       scores = new float[nDocsReturned];
       totalHits = set.size();
       maxScore = totalHits>0 ? topscore[0] : 0.0f;
+      // no docs on this page, so cursor doesn't change
+      qr.setNextCursorMark(cmd.getCursorMark());
     } else {
 
-      TopDocsCollector topCollector;
-
-      if (cmd.getSort() == null) {
-        topCollector = TopScoreDocCollector.create(len, true);
-      } else {
-        topCollector = TopFieldCollector.create(weightSort(cmd.getSort()), len, false, needScores, needScores, true);
-      }
-
+      final TopDocsCollector topCollector = buildTopDocsCollector(len, cmd);
       DocSetCollector setCollector = new DocSetDelegateCollector(maxDoc>>6, maxDoc, topCollector);
       Collector collector = setCollector;
       if (terminateEarly) {
@@ -1677,6 +1752,7 @@ public class SolrIndexSearcher extends I
       assert(totalHits == set.size());
 
       TopDocs topDocs = topCollector.topDocs(0, len);
+      populateNextCursorMarkFromTopDocs(qr, cmd, topDocs);
       maxScore = totalHits>0 ? topDocs.getMaxScore() : 0.0f;
       nDocsReturned = topDocs.scoreDocs.length;
 
@@ -1925,16 +2001,21 @@ public class SolrIndexSearcher extends I
     return qr.getDocListAndSet();
   }
 
-  protected DocList sortDocSet(DocSet set, Sort sort, int nDocs) throws IOException {
+  protected void sortDocSet(QueryResult qr, QueryCommand cmd) throws IOException {
+    DocSet set = qr.getDocListAndSet().docSet;
+    int nDocs = cmd.getSupersetMaxDoc();
     if (nDocs == 0) {
       // SOLR-2923
-      return new DocSlice(0, 0, new int[0], null, 0, 0f);
+      qr.getDocListAndSet().docList = new DocSlice(0, 0, new int[0], null, set.size(), 0f);
+      qr.setNextCursorMark(cmd.getCursorMark());
+      return;
     }
 
+
     // bit of a hack to tell if a set is sorted - do it better in the future.
     boolean inOrder = set instanceof BitDocSet || set instanceof SortedIntDocSet;
 
-    TopDocsCollector topCollector = TopFieldCollector.create(weightSort(sort), nDocs, false, false, false, inOrder);
+    TopDocsCollector topCollector = buildTopDocsCollector(nDocs, cmd);
 
     DocIterator iter = set.iterator();
     int base=0;
@@ -1963,7 +2044,8 @@ public class SolrIndexSearcher extends I
       ids[i] = scoreDoc.doc;
     }
 
-    return new DocSlice(0,nDocsReturned,ids,null,topDocs.totalHits,0.0f);
+    qr.getDocListAndSet().docList = new DocSlice(0,nDocsReturned,ids,null,topDocs.totalHits,0.0f);
+    populateNextCursorMarkFromTopDocs(qr, cmd, topDocs);
   }
 
 
@@ -2187,20 +2269,27 @@ public class SolrIndexSearcher extends I
     private int supersetMaxDoc;
     private int flags;
     private long timeAllowed = -1;
-    //Issue 1726 start
-    private ScoreDoc scoreDoc;
+    private CursorMark cursorMark;
     
-    public ScoreDoc getScoreDoc()
-    {
-      return scoreDoc;
+    public CursorMark getCursorMark() {
+      return cursorMark;
     }
-    public void setScoreDoc(ScoreDoc scoreDoc)
-    {
-      this.scoreDoc = scoreDoc;
+    public QueryCommand setCursorMark(CursorMark cursorMark) {
+      this.cursorMark = cursorMark;
+      if (null != cursorMark) {
+        // If we're using a cursor then we can't allow queryResult caching because the 
+        // cache keys don't know anything about the collector used.
+        //
+        // in theory, we could enhance the cache keys to be aware of the searchAfter
+        // FieldDoc but then there would still be complexity around things like the cache
+        // window size that would need to be worked out
+        //
+        // we *can* however allow the use of checking the filterCache for non-score based
+        // sorts, because that still runs our paging collector over the entire DocSet
+        this.flags |= (NO_CHECK_QCACHE | NO_SET_QCACHE);
+      }
+      return this;
     }
-    //Issue 1726 end
-
-    // public List<Grouping.Command> groupCommands;
 
     public Query getQuery() { return query; }
     public QueryCommand setQuery(Query query) {
@@ -2309,6 +2398,7 @@ public class SolrIndexSearcher extends I
   public static class QueryResult {
     private boolean partialResults;
     private DocListAndSet docListAndSet;
+    private CursorMark nextCursorMark;
 
     public Object groupedResults;   // TODO: currently for testing
     
@@ -2333,6 +2423,13 @@ public class SolrIndexSearcher extends I
 
     public void setDocListAndSet( DocListAndSet listSet ) { docListAndSet = listSet; }
     public DocListAndSet getDocListAndSet() { return docListAndSet; }
+
+    public void setNextCursorMark(CursorMark next) {
+      this.nextCursorMark = next;
+    }
+    public CursorMark getNextCursorMark() {
+      return nextCursorMark;
+    }
   }
 
 }

Modified: lucene/dev/branches/branch_4x/solr/core/src/test/org/apache/solr/BasicFunctionalityTest.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_4x/solr/core/src/test/org/apache/solr/BasicFunctionalityTest.java?rev=1557196&r1=1557195&r2=1557196&view=diff
==============================================================================
--- lucene/dev/branches/branch_4x/solr/core/src/test/org/apache/solr/BasicFunctionalityTest.java (original)
+++ lucene/dev/branches/branch_4x/solr/core/src/test/org/apache/solr/BasicFunctionalityTest.java Fri Jan 10 16:59:22 2014
@@ -948,51 +948,6 @@ public class BasicFunctionalityTest exte
     }
   }
 
-  @Ignore("See SOLR-1726")
-  @Test
-  public void testDeepPaging() throws Exception {
-    for (int i = 0; i < 1000; i++){
-      assertU(adoc("id", String.valueOf(i),  "foo_t", English.intToEnglish(i)));
-    }
-    assertU(commit());
-    SolrQueryRequest goldReq = null;
-    try {
-      goldReq = req("q", "foo_t:one", "rows", "50", "fl", "docid, score");
-      SolrQueryResponse gold = h.queryAndResponse("standard", goldReq);
-      ResultContext response = (ResultContext) gold.getValues().get("response");
-      assertQ("page: " + 0 + " failed",
-          req("q", "foo_t:one", "rows", "10", CommonParams.QT, "standard", "fl", "[docid], score"),
-          "*[count(//doc)=10]");
-      //ugh, what a painful way to get the document
-      DocIterator iterator = response.docs.subset(9, 1).iterator();
-      int lastDoc = iterator.nextDoc();
-      float lastScore = iterator.score();
-      for (int i = 1; i < 5; i++){
-        //page through some results
-        DocList subset = response.docs.subset(i * 10, 1);
-        iterator = subset.iterator();
-        int compareDoc = iterator.nextDoc();
-        float compareScore = iterator.score();
-        assertQ("page: " + i + " failed",
-            req("q", "foo_t:one", CommonParams.QT, "standard", "fl", "[docid], score",
-                "start", String.valueOf(i * 10), "rows", "1",  //only get one doc, and then compare it to gold
-                CommonParams.PAGEDOC, String.valueOf(lastDoc), CommonParams.PAGESCORE, String.valueOf(lastScore)),
-            "*[count(//doc)=1]",
-            "//float[@name='score'][.='" + compareScore + "']",
-            "//int[@name='[docid]'][.='" + compareDoc + "']"
-        );
-        lastScore = compareScore;
-        lastDoc = compareDoc;
-
-      }
-    } finally {
-      if (goldReq != null ) {
-        goldReq.close();
-      }
-    }
-  }
-
-
 //   /** this doesn't work, but if it did, this is how we'd test it. */
 //   public void testOverwriteFalse() {
 

Copied: lucene/dev/branches/branch_4x/solr/core/src/test/org/apache/solr/CursorPagingTest.java (from r1556036, lucene/dev/trunk/solr/core/src/test/org/apache/solr/CursorPagingTest.java)
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_4x/solr/core/src/test/org/apache/solr/CursorPagingTest.java?p2=lucene/dev/branches/branch_4x/solr/core/src/test/org/apache/solr/CursorPagingTest.java&p1=lucene/dev/trunk/solr/core/src/test/org/apache/solr/CursorPagingTest.java&r1=1556036&r2=1557196&rev=1557196&view=diff
==============================================================================
--- lucene/dev/trunk/solr/core/src/test/org/apache/solr/CursorPagingTest.java (original)
+++ lucene/dev/branches/branch_4x/solr/core/src/test/org/apache/solr/CursorPagingTest.java Fri Jan 10 16:59:22 2014
@@ -679,7 +679,7 @@ public class CursorPagingTest extends So
 
     }
     if (useField()) {
-      int numBytes = (int) skewed(_TestUtil.nextInt(random(), 20, 50), 2);
+      int numBytes = (Integer) skewed(_TestUtil.nextInt(random(), 20, 50), 2);
       byte[] randBytes = new byte[numBytes];
       random().nextBytes(randBytes);
       doc.addField("bin", ByteBuffer.wrap(randBytes));

Modified: lucene/dev/branches/branch_4x/solr/core/src/test/org/apache/solr/schema/SortableBinaryField.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_4x/solr/core/src/test/org/apache/solr/schema/SortableBinaryField.java?rev=1557196&r1=1557195&r2=1557196&view=diff
==============================================================================
--- lucene/dev/branches/branch_4x/solr/core/src/test/org/apache/solr/schema/SortableBinaryField.java (original)
+++ lucene/dev/branches/branch_4x/solr/core/src/test/org/apache/solr/schema/SortableBinaryField.java Fri Jan 10 16:59:22 2014
@@ -40,10 +40,7 @@ public class SortableBinaryField extends
 
   @Override
   public void checkSchemaField(final SchemaField field) {
-    if (field.hasDocValues() && !field.multiValued() && !(field.isRequired() || field.getDefaultValue() != null)) {
-      throw new IllegalStateException(
-          "Field " + this + " has single-valued doc values enabled, but has no default value and is not required");
-    }
+    // NOOP, It's Aaaaaall Good.
   }
 
   @Override

Modified: lucene/dev/branches/branch_4x/solr/solrj/src/java/org/apache/solr/client/solrj/response/QueryResponse.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_4x/solr/solrj/src/java/org/apache/solr/client/solrj/response/QueryResponse.java?rev=1557196&r1=1557195&r2=1557196&view=diff
==============================================================================
--- lucene/dev/branches/branch_4x/solr/solrj/src/java/org/apache/solr/client/solrj/response/QueryResponse.java (original)
+++ lucene/dev/branches/branch_4x/solr/solrj/src/java/org/apache/solr/client/solrj/response/QueryResponse.java Fri Jan 10 16:59:22 2014
@@ -22,6 +22,7 @@ import org.apache.solr.client.solrj.bean
 import org.apache.solr.common.SolrDocumentList;
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.common.util.SimpleOrderedMap;
+import org.apache.solr.common.params.CursorMarkParams;
 
 import java.util.*;
 
@@ -43,6 +44,7 @@ public class QueryResponse extends SolrR
   private NamedList<NamedList<Object>> _spellInfo = null;
   private NamedList<Object> _statsInfo = null;
   private NamedList<NamedList<Number>> _termsInfo = null;
+  private String _cursorMarkNext = null;
 
   // Grouping response
   private NamedList<Object> _groupedInfo = null;
@@ -133,6 +135,9 @@ public class QueryResponse extends SolrR
         _termsInfo = (NamedList<NamedList<Number>>) res.getVal( i );
         extractTermsInfo( _termsInfo );
       }
+      else if ( CursorMarkParams.CURSOR_MARK_NEXT.equals( n ) ) {
+        _cursorMarkNext = (String) res.getVal( i );
+      }
     }
     if(_facetInfo != null) extractFacetInfo( _facetInfo );
   }
@@ -487,6 +492,10 @@ public class QueryResponse extends SolrR
   public Map<String, FieldStatsInfo> getFieldStatsInfo() {
     return _fieldStatsInfo;
   }
+
+  public String getNextCursorMark() {
+    return _cursorMarkNext;
+  }
 }
 
 

Modified: lucene/dev/branches/branch_4x/solr/solrj/src/java/org/apache/solr/common/params/CommonParams.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_4x/solr/solrj/src/java/org/apache/solr/common/params/CommonParams.java?rev=1557196&r1=1557195&r2=1557196&view=diff
==============================================================================
--- lucene/dev/branches/branch_4x/solr/solrj/src/java/org/apache/solr/common/params/CommonParams.java (original)
+++ lucene/dev/branches/branch_4x/solr/solrj/src/java/org/apache/solr/common/params/CommonParams.java Fri Jan 10 16:59:22 2014
@@ -76,15 +76,7 @@ public interface CommonParams {
   /** "ping" value for SolrPing action */
   public static final String PING = "ping";
   // SOLR-4228 end
-  
-  //Issue 1726 start
-  /** score of the last document of the previous page */
-  public static final String PAGESCORE ="pageScore";
-  
-  /** docid of the last document of the previous page */
-  public static final String PAGEDOC ="pageDoc";
-  //Issue 1726 end
-  
+
   /** stylesheet to apply to XML results */
   public static final String XSL ="xsl";
   

Modified: lucene/dev/branches/branch_4x/solr/test-framework/src/java/org/apache/solr/BaseDistributedSearchTestCase.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_4x/solr/test-framework/src/java/org/apache/solr/BaseDistributedSearchTestCase.java?rev=1557196&r1=1557195&r2=1557196&view=diff
==============================================================================
--- lucene/dev/branches/branch_4x/solr/test-framework/src/java/org/apache/solr/BaseDistributedSearchTestCase.java (original)
+++ lucene/dev/branches/branch_4x/solr/test-framework/src/java/org/apache/solr/BaseDistributedSearchTestCase.java Fri Jan 10 16:59:22 2014
@@ -522,6 +522,14 @@ public abstract class BaseDistributedSea
   }
 
   /**
+   * Sets distributed params.
+   * Returns the QueryResponse from {@link #queryServer},
+   */
+  protected QueryResponse query(SolrParams params) throws Exception {
+    return query(true, params);
+  }
+
+  /**
    * Returns the QueryResponse from {@link #queryServer}  
    */
   protected QueryResponse query(boolean setDistribParams, Object[] q) throws Exception {
@@ -531,6 +539,16 @@ public abstract class BaseDistributedSea
     for (int i = 0; i < q.length; i += 2) {
       params.add(q[i].toString(), q[i + 1].toString());
     }
+    return query(setDistribParams, params);
+  }
+
+  /**
+   * Returns the QueryResponse from {@link #queryServer}  
+   */
+  protected QueryResponse query(boolean setDistribParams, SolrParams p) throws Exception {
+    
+    final ModifiableSolrParams params = new ModifiableSolrParams(p);
+
     // TODO: look into why passing true causes fails
     params.set("distrib", "false");
     final QueryResponse controlRsp = controlClient.query(params);

Modified: lucene/dev/branches/branch_4x/solr/test-framework/src/java/org/apache/solr/SolrTestCaseJ4.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_4x/solr/test-framework/src/java/org/apache/solr/SolrTestCaseJ4.java?rev=1557196&r1=1557195&r2=1557196&view=diff
==============================================================================
--- lucene/dev/branches/branch_4x/solr/test-framework/src/java/org/apache/solr/SolrTestCaseJ4.java (original)
+++ lucene/dev/branches/branch_4x/solr/test-framework/src/java/org/apache/solr/SolrTestCaseJ4.java Fri Jan 10 16:59:22 2014
@@ -673,9 +673,10 @@ public abstract class SolrTestCaseJ4 ext
    * Validates a query matches some JSON test expressions using the default double delta tolerance.
    * @see JSONTestUtil#DEFAULT_DELTA
    * @see #assertJQ(SolrQueryRequest,double,String...)
+   * @return The request response as a JSON String if all test patterns pass
    */
-  public static void assertJQ(SolrQueryRequest req, String... tests) throws Exception {
-    assertJQ(req, JSONTestUtil.DEFAULT_DELTA, tests);
+  public static String assertJQ(SolrQueryRequest req, String... tests) throws Exception {
+    return assertJQ(req, JSONTestUtil.DEFAULT_DELTA, tests);
   }
   /**
    * Validates a query matches some JSON test expressions and closes the
@@ -690,8 +691,9 @@ public abstract class SolrTestCaseJ4 ext
    * @param req Solr request to execute
    * @param delta tolerance allowed in comparing float/double values
    * @param tests JSON path expression + '==' + expected value
+   * @return The request response as a JSON String if all test patterns pass
    */
-  public static void assertJQ(SolrQueryRequest req, double delta, String... tests) throws Exception {
+  public static String assertJQ(SolrQueryRequest req, double delta, String... tests) throws Exception {
     SolrParams params =  null;
     try {
       params = req.getParams();
@@ -739,6 +741,7 @@ public abstract class SolrTestCaseJ4 ext
           }
         }
       }
+      return response;
     } finally {
       // restore the params
       if (params != null && params != req.getParams()) req.setParams(params);