SOLR-3316: Distributed grouping failed when rows parameter was set to 0 and sometimes returned a wrong
 hit count as matches.

git-svn-id: https://svn.apache.org/repos/asf/lucene/dev/branches/branch_3x@1310445 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index fc3e810..abe558b 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -319,6 +319,9 @@
 * SOLR-3200: Fix SignatureUpdateProcessor "all fields" mode to use all 
   fields of each document instead of the fields specified by the first 
   document indexed (Spyros Kapnissis via hossman)
+  
+* SOLR-3316: Distributed grouping failed when rows parameter was set to 0 and 
+  sometimes returned a wrong hit count as matches. (Cody Young, Martijn van Groningen)
 
 Other Changes
 ----------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/component/QueryComponent.java b/solr/core/src/java/org/apache/solr/handler/component/QueryComponent.java
index 8ea030c..0e716b1 100644
--- a/solr/core/src/java/org/apache/solr/handler/component/QueryComponent.java
+++ b/solr/core/src/java/org/apache/solr/handler/component/QueryComponent.java
@@ -24,7 +24,6 @@
 import org.apache.lucene.search.grouping.GroupDocs;
 import org.apache.lucene.search.grouping.SearchGroup;
 import org.apache.lucene.search.grouping.TopGroups;
-import org.apache.lucene.util.BytesRef;
 import org.apache.solr.common.SolrDocument;
 import org.apache.solr.common.SolrDocumentList;
 import org.apache.solr.common.SolrException;
@@ -265,6 +264,7 @@
           CommandHandler.Builder topsGroupsActionBuilder = new CommandHandler.Builder()
               .setQueryCommand(cmd)
               .setNeedDocSet(false) // Order matters here
+              .setIncludeHitCount(true)
               .setSearcher(searcher);
 
           for (String field : groupingSpec.getFields()) {
@@ -280,6 +280,7 @@
           commandHandler.execute();
           SearchGroupsResultTransformer serializer = new SearchGroupsResultTransformer(searcher);
           rsp.add("firstPhase", commandHandler.processResult(result, serializer));
+          rsp.add("totalHitCount", commandHandler.getTotalHitCount());
           rb.setResult(result);
           return;
         } else if (params.getBool(GroupParams.GROUP_DISTRIBUTED_SECOND, false)) {
@@ -291,7 +292,7 @@
           for (String field : groupingSpec.getFields()) {
             String[] topGroupsParam = params.getParams(GroupParams.GROUP_DISTRIBUTED_TOPGROUPS_PREFIX + field);
             if (topGroupsParam == null) {
-              continue;
+              topGroupsParam = new String[0];
             }
 
             List<SearchGroup<String>> topGroups = new ArrayList<SearchGroup<String>>(topGroupsParam.length);
@@ -638,7 +639,7 @@
     Map<String, Object> combinedMap = new LinkedHashMap<String, Object>();
     combinedMap.putAll(rb.mergedTopGroups);
     combinedMap.putAll(rb.mergedQueryCommandResults);
-    endResultTransformer.transform(combinedMap, rb.rsp, rb.getGroupingSpec(), solrDocumentSource);
+    endResultTransformer.transform(combinedMap, rb, solrDocumentSource);
   }
 
   private void regularFinishStage(ResponseBuilder rb) {
diff --git a/solr/core/src/java/org/apache/solr/handler/component/ResponseBuilder.java b/solr/core/src/java/org/apache/solr/handler/component/ResponseBuilder.java
index 7fa1e0d..0e81eba 100644
--- a/solr/core/src/java/org/apache/solr/handler/component/ResponseBuilder.java
+++ b/solr/core/src/java/org/apache/solr/handler/component/ResponseBuilder.java
@@ -158,6 +158,7 @@
   public final Map<String, TopGroups<String>> mergedTopGroups = new HashMap<String, TopGroups<String>>();
   public final Map<String, QueryCommandResult> mergedQueryCommandResults = new HashMap<String, QueryCommandResult>();
   public final Map<Object, SolrDocument> retrievedDocuments = new HashMap<Object, SolrDocument>();
+  public int totalHitCount; // Hit count used when distributed grouping is performed.
   // Used for timeAllowed parameter. First phase elapsed time is subtracted from the time allowed for the second phase.
   public int firstPhaseElapsedTime;
 
diff --git a/solr/core/src/java/org/apache/solr/search/grouping/CommandHandler.java b/solr/core/src/java/org/apache/solr/search/grouping/CommandHandler.java
index 0cf162f..52b7573 100644
--- a/solr/core/src/java/org/apache/solr/search/grouping/CommandHandler.java
+++ b/solr/core/src/java/org/apache/solr/search/grouping/CommandHandler.java
@@ -47,6 +47,7 @@
     private SolrIndexSearcher searcher;
     private boolean needDocSet = false;
     private boolean truncateGroups = false;
+    private boolean includeHitCount = false;
 
     public Builder setQueryCommand(SolrIndexSearcher.QueryCommand queryCommand) {
       this.queryCommand = queryCommand;
@@ -76,6 +77,11 @@
       return this;
     }
 
+    public Builder setIncludeHitCount(boolean includeHitCount) {
+      this.includeHitCount = includeHitCount;
+      return this;
+    }
+
     public Builder setTruncateGroups(boolean truncateGroups) {
       this.truncateGroups = truncateGroups;
       return this;
@@ -86,7 +92,7 @@
         throw new IllegalStateException("All fields must be set");
       }
 
-      return new CommandHandler(queryCommand, commands, searcher, needDocSet, truncateGroups);
+      return new CommandHandler(queryCommand, commands, searcher, needDocSet, truncateGroups, includeHitCount);
     }
 
   }
@@ -98,19 +104,24 @@
   private final SolrIndexSearcher searcher;
   private final boolean needDocset;
   private final boolean truncateGroups;
+  private final boolean includeHitCount;
   private boolean partialResults = false;
+  private int totalHitCount;
 
   private DocSet docSet;
 
   private CommandHandler(SolrIndexSearcher.QueryCommand queryCommand,
                          List<Command> commands,
                          SolrIndexSearcher searcher,
-                         boolean needDocset, boolean truncateGroups) {
+                         boolean needDocset,
+                         boolean truncateGroups,
+                         boolean includeHitCount) {
     this.queryCommand = queryCommand;
     this.commands = commands;
     this.searcher = searcher;
     this.needDocset = needDocset;
     this.truncateGroups = truncateGroups;
+    this.includeHitCount = includeHitCount;
   }
 
   @SuppressWarnings("unchecked")
@@ -127,12 +138,14 @@
     Filter luceneFilter = pf.filter;
     Query query = QueryUtils.makeQueryable(queryCommand.getQuery());
 
-    if (truncateGroups && nrOfCommands > 0) {
+    if (truncateGroups) {
       docSet = computeGroupedDocSet(query, luceneFilter, collectors);
     } else if (needDocset) {
       docSet = computeDocSet(query, luceneFilter, collectors);
-    } else {
+    } else if (!collectors.isEmpty()) {
       searchWithTimeLimiter(query, luceneFilter, MultiCollector.wrap(collectors.toArray(new Collector[nrOfCommands])));
+    } else {
+      searchWithTimeLimiter(query, luceneFilter, null);
     }
   }
 
@@ -182,12 +195,25 @@
     if (queryCommand.getTimeAllowed() > 0 ) {
       collector = new TimeLimitingCollector(collector, TimeLimitingCollector.getGlobalCounter(), queryCommand.getTimeAllowed());
     }
+
+    TotalHitCountCollector hitCountCollector = new TotalHitCountCollector();
+    if (includeHitCount) {
+      collector = MultiCollector.wrap(collector, hitCountCollector);
+    }
+
     try {
       searcher.search(query, luceneFilter, collector);
     } catch (TimeLimitingCollector.TimeExceededException x) {
       partialResults = true;
       logger.warn( "Query: " + query + "; " + x.getMessage() );
     }
+
+    if (includeHitCount) {
+      totalHitCount = hitCountCollector.getTotalHits();
+    }
   }
 
+  public int getTotalHitCount() {
+    return totalHitCount;
+  }
 }
diff --git a/solr/core/src/java/org/apache/solr/search/grouping/distributed/command/SearchGroupsFieldCommand.java b/solr/core/src/java/org/apache/solr/search/grouping/distributed/command/SearchGroupsFieldCommand.java
index a598b72..57bbd0d 100644
--- a/solr/core/src/java/org/apache/solr/search/grouping/distributed/command/SearchGroupsFieldCommand.java
+++ b/solr/core/src/java/org/apache/solr/search/grouping/distributed/command/SearchGroupsFieldCommand.java
@@ -27,6 +27,7 @@
 import java.io.IOException;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
 
 /**
@@ -69,7 +70,7 @@
   private final Sort groupSort;
   private final int topNGroups;
 
-  private TermFirstPassGroupingCollector collector;
+  private TermFirstPassGroupingCollector firstPassGroupingCollector;
 
   private SearchGroupsFieldCommand(SchemaField field, Sort groupSort, int topNGroups) {
     this.field = field;
@@ -78,12 +79,20 @@
   }
 
   public List<Collector> create() throws IOException {
-    collector = new TermFirstPassGroupingCollector(field.getName(), groupSort, topNGroups);
-    return Arrays.asList((Collector) collector);
+    if (topNGroups > 0) {
+      firstPassGroupingCollector = new TermFirstPassGroupingCollector(field.getName(), groupSort, topNGroups);
+      return Arrays.asList((Collector) firstPassGroupingCollector);
+    } else {
+      return Collections.emptyList();
+    }
   }
 
   public Collection<SearchGroup<String>> result() {
-    return collector.getTopGroups(0, true);
+    if (topNGroups > 0) {
+      return firstPassGroupingCollector.getTopGroups(0, true);
+    } else {
+      return Collections.emptyList();
+    }
   }
 
   public Sort getSortWithinGroup() {
diff --git a/solr/core/src/java/org/apache/solr/search/grouping/distributed/command/TopGroupsFieldCommand.java b/solr/core/src/java/org/apache/solr/search/grouping/distributed/command/TopGroupsFieldCommand.java
index 20e0ba8..2f3dbce 100644
--- a/solr/core/src/java/org/apache/solr/search/grouping/distributed/command/TopGroupsFieldCommand.java
+++ b/solr/core/src/java/org/apache/solr/search/grouping/distributed/command/TopGroupsFieldCommand.java
@@ -19,17 +19,14 @@
 
 import org.apache.lucene.search.Collector;
 import org.apache.lucene.search.Sort;
-import org.apache.lucene.search.grouping.SearchGroup;
-import org.apache.lucene.search.grouping.TermAllGroupsCollector;
-import org.apache.lucene.search.grouping.TermSecondPassGroupingCollector;
-import org.apache.lucene.search.grouping.TopGroups;
-import org.apache.lucene.util.BytesRef;
+import org.apache.lucene.search.grouping.*;
 import org.apache.solr.schema.SchemaField;
 import org.apache.solr.search.grouping.Command;
 
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
 
 /**
@@ -130,6 +127,10 @@
   }
 
   public List<Collector> create() throws IOException {
+    if (firstPhaseGroups.isEmpty()) {
+      return Collections.emptyList();
+    }
+
     List<Collector> collectors = new ArrayList<Collector>();
     secondPassCollector = new TermSecondPassGroupingCollector(
           field.getName(), firstPhaseGroups, groupSort, sortWithinGroup, maxDocPerGroup, needScores, needMaxScore, true
@@ -143,7 +144,12 @@
     return collectors;
   }
 
+  @SuppressWarnings("unchecked")
   public TopGroups<String> result() {
+    if (firstPhaseGroups.isEmpty()) {
+      return new TopGroups<String>(groupSort.getSort(), sortWithinGroup.getSort(), 0, 0, new GroupDocs[0]);
+    }
+
     TopGroups<String> result = secondPassCollector.getTopGroups(0);
     if (allGroupsCollector != null) {
       result = new TopGroups<String>(result, allGroupsCollector.getGroupCount());
diff --git a/solr/core/src/java/org/apache/solr/search/grouping/distributed/responseprocessor/SearchGroupShardResponseProcessor.java b/solr/core/src/java/org/apache/solr/search/grouping/distributed/responseprocessor/SearchGroupShardResponseProcessor.java
index 247e33b..27184d9 100644
--- a/solr/core/src/java/org/apache/solr/search/grouping/distributed/responseprocessor/SearchGroupShardResponseProcessor.java
+++ b/solr/core/src/java/org/apache/solr/search/grouping/distributed/responseprocessor/SearchGroupShardResponseProcessor.java
@@ -57,6 +57,7 @@
     SearchGroupsResultTransformer serializer = new SearchGroupsResultTransformer(rb.req.getSearcher());
     try {
       int maxElapsedTime = 0;
+      int hitCountDuringFirstPhase = 0;
       for (ShardResponse srsp : shardRequest.responses) {
         maxElapsedTime = (int) Math.max(maxElapsedTime, srsp.getSolrResponse().getElapsedTime());
         @SuppressWarnings("unchecked")
@@ -79,7 +80,9 @@
             shards.add(srsp.getShard());
           }
         }
+        hitCountDuringFirstPhase += (Integer) srsp.getSolrResponse().getResponse().get("totalHitCount");
       }
+      rb.totalHitCount = hitCountDuringFirstPhase;
       rb.firstPhaseElapsedTime = maxElapsedTime;
       for (String groupField : commandSearchGroups.keySet()) {
         List<Collection<SearchGroup<String>>> topGroups = commandSearchGroups.get(groupField);
diff --git a/solr/core/src/java/org/apache/solr/search/grouping/endresulttransformer/EndResultTransformer.java b/solr/core/src/java/org/apache/solr/search/grouping/endresulttransformer/EndResultTransformer.java
index efb5859..d559f56 100644
--- a/solr/core/src/java/org/apache/solr/search/grouping/endresulttransformer/EndResultTransformer.java
+++ b/solr/core/src/java/org/apache/solr/search/grouping/endresulttransformer/EndResultTransformer.java
@@ -19,8 +19,7 @@
 
 import org.apache.lucene.search.ScoreDoc;
 import org.apache.solr.common.SolrDocument;
-import org.apache.solr.response.SolrQueryResponse;
-import org.apache.solr.search.grouping.GroupingSpecification;
+import org.apache.solr.handler.component.ResponseBuilder;
 
 import java.util.Map;
 
@@ -35,11 +34,10 @@
    * Transforms the specified result into its final form and puts it into the specified response.
    *
    * @param result The map containing the grouping result (for grouping by field and query)
-   * @param response The response that will be rendered to the client
-   * @param groupingSpecification The grouping specification
+   * @param rb The response builder containing the response used to render the result and the grouping specification
    * @param solrDocumentSource The source of {@link SolrDocument} instances
    */
-  void transform(Map<String, ?> result, SolrQueryResponse response, GroupingSpecification groupingSpecification, SolrDocumentSource solrDocumentSource);
+  void transform(Map<String, ?> result, ResponseBuilder rb, SolrDocumentSource solrDocumentSource);
 
   /**
    * Abstracts the source for {@link SolrDocument} instances.
diff --git a/solr/core/src/java/org/apache/solr/search/grouping/endresulttransformer/GroupedEndResultTransformer.java b/solr/core/src/java/org/apache/solr/search/grouping/endresulttransformer/GroupedEndResultTransformer.java
index d42e367..f22bef2 100644
--- a/solr/core/src/java/org/apache/solr/search/grouping/endresulttransformer/GroupedEndResultTransformer.java
+++ b/solr/core/src/java/org/apache/solr/search/grouping/endresulttransformer/GroupedEndResultTransformer.java
@@ -23,11 +23,10 @@
 import org.apache.solr.common.SolrDocumentList;
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.common.util.SimpleOrderedMap;
-import org.apache.solr.response.SolrQueryResponse;
+import org.apache.solr.handler.component.ResponseBuilder;
 import org.apache.solr.schema.FieldType;
 import org.apache.solr.schema.SchemaField;
 import org.apache.solr.search.SolrIndexSearcher;
-import org.apache.solr.search.grouping.GroupingSpecification;
 import org.apache.solr.search.grouping.distributed.command.QueryCommandResult;
 
 import java.util.ArrayList;
@@ -35,7 +34,7 @@
 import java.util.Map;
 
 /**
- *
+ * Implementation of {@link EndResultTransformer} that keeps each grouped result separate in the final response.
  */
 public class GroupedEndResultTransformer implements EndResultTransformer {
 
@@ -45,7 +44,10 @@
     this.searcher = searcher;
   }
 
-  public void transform(Map<String, ?> result, SolrQueryResponse response, GroupingSpecification groupingSpecification, SolrDocumentSource solrDocumentSource) {
+  /**
+   * {@inheritDoc}
+   */
+  public void transform(Map<String, ?> result, ResponseBuilder rb, SolrDocumentSource solrDocumentSource) {
     NamedList<Object> commands = new NamedList<Object>();
     for (Map.Entry<String, ?> entry : result.entrySet()) {
       Object value = entry.getValue();
@@ -53,7 +55,7 @@
         @SuppressWarnings("unchecked")
         TopGroups<String> topGroups = (TopGroups<String>) value;
         NamedList<Object> command = new SimpleOrderedMap<Object>();
-        command.add("matches", topGroups.totalHitCount);
+        command.add("matches", rb.totalHitCount);
         if (topGroups.totalGroupCount != null) {
           command.add("ngroups", topGroups.totalGroupCount);
         }
@@ -75,7 +77,7 @@
           if (!Float.isNaN(group.maxScore)) {
             docList.setMaxScore(group.maxScore);
           }
-          docList.setStart(groupingSpecification.getGroupOffset());
+          docList.setStart(rb.getGroupingSpec().getGroupOffset());
           for (ScoreDoc scoreDoc : group.scoreDocs) {
             docList.add(solrDocumentSource.retrieve(scoreDoc));
           }
@@ -93,7 +95,7 @@
         if (!Float.isNaN(queryCommandResult.getTopDocs().getMaxScore())) {
           docList.setMaxScore(queryCommandResult.getTopDocs().getMaxScore());
         }
-        docList.setStart(groupingSpecification.getGroupOffset());
+        docList.setStart(rb.getGroupingSpec().getGroupOffset());
         for (ScoreDoc scoreDoc :queryCommandResult.getTopDocs().scoreDocs){
           docList.add(solrDocumentSource.retrieve(scoreDoc));
         }
@@ -101,7 +103,7 @@
         commands.add(entry.getKey(), command);
       }
     }
-    response.add("grouped", commands);
+    rb.rsp.add("grouped", commands);
   }
 
 }
diff --git a/solr/core/src/java/org/apache/solr/search/grouping/endresulttransformer/MainEndResultTransformer.java b/solr/core/src/java/org/apache/solr/search/grouping/endresulttransformer/MainEndResultTransformer.java
index 79a3972..ae5053c 100644
--- a/solr/core/src/java/org/apache/solr/search/grouping/endresulttransformer/MainEndResultTransformer.java
+++ b/solr/core/src/java/org/apache/solr/search/grouping/endresulttransformer/MainEndResultTransformer.java
@@ -22,24 +22,27 @@
 import org.apache.lucene.search.grouping.TopGroups;
 import org.apache.lucene.util.BytesRef;
 import org.apache.solr.common.SolrDocumentList;
-import org.apache.solr.response.SolrQueryResponse;
-import org.apache.solr.search.grouping.GroupingSpecification;
+import org.apache.solr.handler.component.ResponseBuilder;
 
 import java.util.Map;
 
 /**
- *
+ * Implementation of {@link EndResultTransformer} that transforms the grouped result into the main result list in the
+ * response.
  */
 public class MainEndResultTransformer implements EndResultTransformer {
 
-  public void transform(Map<String, ?> result, SolrQueryResponse response, GroupingSpecification groupingSpecification, SolrDocumentSource solrDocumentSource) {
-    Object value = result.get(groupingSpecification.getFields()[0]);
+  /**
+   * {@inheritDoc}
+   */
+  public void transform(Map<String, ?> result, ResponseBuilder rb, SolrDocumentSource solrDocumentSource) {
+    Object value = result.get(rb.getGroupingSpec().getFields()[0]);
     if (TopGroups.class.isInstance(value)) {
       @SuppressWarnings("unchecked")
       TopGroups<BytesRef> topGroups = (TopGroups<BytesRef>) value;
       SolrDocumentList docList = new SolrDocumentList();
-      docList.setStart(groupingSpecification.getOffset());
-      docList.setNumFound(topGroups.totalHitCount);
+      docList.setStart(rb.getGroupingSpec().getOffset());
+      docList.setNumFound(rb.totalHitCount);
 
       Float maxScore = Float.NEGATIVE_INFINITY;
       for (GroupDocs<BytesRef> group : topGroups.groups) {
@@ -53,7 +56,7 @@
       if (maxScore != Float.NEGATIVE_INFINITY) {
         docList.setMaxScore(maxScore);
       }
-      response.add("response", docList);
+      rb.rsp.add("response", docList);
     }
   }
 }
diff --git a/solr/core/src/java/org/apache/solr/search/grouping/endresulttransformer/SimpleEndResultTransformer.java b/solr/core/src/java/org/apache/solr/search/grouping/endresulttransformer/SimpleEndResultTransformer.java
index ccae84a..aa2acda 100644
--- a/solr/core/src/java/org/apache/solr/search/grouping/endresulttransformer/SimpleEndResultTransformer.java
+++ b/solr/core/src/java/org/apache/solr/search/grouping/endresulttransformer/SimpleEndResultTransformer.java
@@ -24,17 +24,19 @@
 import org.apache.solr.common.SolrDocumentList;
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.common.util.SimpleOrderedMap;
-import org.apache.solr.response.SolrQueryResponse;
-import org.apache.solr.search.grouping.GroupingSpecification;
+import org.apache.solr.handler.component.ResponseBuilder;
 
 import java.util.Map;
 
 /**
- *
+ * Implementation of {@link EndResultTransformer} that transforms the grouped result into a single flat list.
  */
 public class SimpleEndResultTransformer implements EndResultTransformer {
 
-  public void transform(Map<String, ?> result, SolrQueryResponse response, GroupingSpecification groupingSpecification, SolrDocumentSource solrDocumentSource) {
+  /**
+   * {@inheritDoc}
+   */
+  public void transform(Map<String, ?> result, ResponseBuilder rb, SolrDocumentSource solrDocumentSource) {
     NamedList<Object> commands = new SimpleOrderedMap<Object>();
     for (Map.Entry<String, ?> entry : result.entrySet()) {
       Object value = entry.getValue();
@@ -42,12 +44,12 @@
         @SuppressWarnings("unchecked")
         TopGroups<BytesRef> topGroups = (TopGroups<BytesRef>) value;
         NamedList<Object> command = new SimpleOrderedMap<Object>();
-        command.add("matches", topGroups.totalHitCount);
+        command.add("matches", rb.totalHitCount);
         if (topGroups.totalGroupCount != null) {
           command.add("ngroups", topGroups.totalGroupCount);
         }
         SolrDocumentList docList = new SolrDocumentList();
-        docList.setStart(groupingSpecification.getOffset());
+        docList.setStart(rb.getGroupingSpec().getOffset());
         docList.setNumFound(topGroups.totalHitCount);
 
         Float maxScore = Float.NEGATIVE_INFINITY;
@@ -67,6 +69,6 @@
       }
     }
 
-    response.add("grouped", commands);
+    rb.rsp.add("grouped", commands);
   }
 }
diff --git a/solr/core/src/test/org/apache/solr/TestDistributedGrouping.java b/solr/core/src/test/org/apache/solr/TestDistributedGrouping.java
index 3b396e1..eff7f10 100644
--- a/solr/core/src/test/org/apache/solr/TestDistributedGrouping.java
+++ b/solr/core/src/test/org/apache/solr/TestDistributedGrouping.java
@@ -17,9 +17,12 @@
  * limitations under the License.
  */
 
+import org.apache.solr.client.solrj.SolrServer;
 import org.apache.solr.client.solrj.SolrServerException;
+import org.apache.solr.client.solrj.response.QueryResponse;
 import org.apache.solr.common.params.CommonParams;
 import org.apache.solr.common.params.ModifiableSolrParams;
+import org.apache.solr.common.util.NamedList;
 
 /**
  * TODO? perhaps use:
@@ -169,6 +172,26 @@
     query("q", "*:*", "fq", s2 + ":a", "rows", 100, "fl", "id," + s1, "group", "true", "group.field", s1, "group.limit", 10, "sort", s1 + " asc, id asc", "group.truncate", "true");
     query("q", "*:*", "fq", s2 + ":a", "rows", 100, "fl", "id," + s1, "group", "true", "group.field", s1, "group.limit", 10, "sort", s1 + " asc, id asc", "group.truncate", "true", "facet", "true", "facet.field", t1);
 
+    query("q", "*:*", "fq", s2 + ":a", "rows", 0, "fl", "id," + s1, "group", "true", "group.field", s1, "group.limit", 10, "sort", s1 + " asc, id asc", "facet", "true", "facet.field", t1);
+    query("q", "*:*", "fq", s2 + ":a", "rows", 0, "fl", "id," + s1, "group", "true", "group.field", s1, "group.limit", 10, "sort", s1 + " asc, id asc", "group.truncate", "true", "facet", "true", "facet.field", t1);
+
+    ModifiableSolrParams params = new ModifiableSolrParams();
+    Object[] q =  {"q", "*:*", "fq", s2 + ":a", "rows", 1, "fl", "id," + s1, "group", "true", "group.field", s1, "group.limit", 10};
+
+    for (int i = 0; i < q.length; i += 2) {
+      params.add(q[i].toString(), q[i + 1].toString());
+    }
+
+    params.set("shards", shards);
+
+    int which = r.nextInt(clients.size());
+    SolrServer client = clients.get(which);
+    QueryResponse rsp = client.query(params);
+    NamedList nl = (NamedList<?>) rsp.getResponse().get("grouped");
+    nl = (NamedList<?>) nl.getVal(0);
+    int matches = (Integer) nl.getVal(0);
+    assertEquals(100 * clients.size(), matches);
+
     // We cannot validate distributed grouping with scoring as first sort. since there is no global idf. We can check if no errors occur
     simpleQuery("q", "*:*", "rows", 100, "fl", "id," + s1, "group", "true", "group.field", s1, "group.limit", 10, "sort", s1 + " desc", "group.sort", "score desc"); // SOLR-2955
     simpleQuery("q", "*:*", "rows", 100, "fl", "id," + s1, "group", "true", "group.field", s1, "group.limit", 10, "sort", "score desc, _docid_ asc, id asc");