You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by yo...@apache.org on 2010/11/24 20:34:14 UTC

svn commit: r1038779 - in /lucene/dev/trunk/solr/src: common/org/apache/solr/common/params/GroupParams.java java/org/apache/solr/handler/component/QueryComponent.java java/org/apache/solr/search/Grouping.java test/org/apache/solr/TestGroupingSearch.java

Author: yonik
Date: Wed Nov 24 19:34:14 2010
New Revision: 1038779

URL: http://svn.apache.org/viewvc?rev=1038779&view=rev
Log:
SOLR-2063: simple flattened grouped response as main result list

Modified:
    lucene/dev/trunk/solr/src/common/org/apache/solr/common/params/GroupParams.java
    lucene/dev/trunk/solr/src/java/org/apache/solr/handler/component/QueryComponent.java
    lucene/dev/trunk/solr/src/java/org/apache/solr/search/Grouping.java
    lucene/dev/trunk/solr/src/test/org/apache/solr/TestGroupingSearch.java

Modified: lucene/dev/trunk/solr/src/common/org/apache/solr/common/params/GroupParams.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/src/common/org/apache/solr/common/params/GroupParams.java?rev=1038779&r1=1038778&r2=1038779&view=diff
==============================================================================
--- lucene/dev/trunk/solr/src/common/org/apache/solr/common/params/GroupParams.java (original)
+++ lucene/dev/trunk/solr/src/common/org/apache/solr/common/params/GroupParams.java Wed Nov 24 19:34:14 2010
@@ -32,5 +32,8 @@ public interface GroupParams {
   public static final String GROUP_LIMIT = GROUP + ".limit";
   /** the offset for the doclist of each group */
   public static final String GROUP_OFFSET = GROUP + ".offset";
+
+  /** treat the first group result as the main result.  true/false */
+  public static final String GROUP_MAIN = GROUP + ".main";
 }
 

Modified: lucene/dev/trunk/solr/src/java/org/apache/solr/handler/component/QueryComponent.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/src/java/org/apache/solr/handler/component/QueryComponent.java?rev=1038779&r1=1038778&r2=1038779&view=diff
==============================================================================
--- lucene/dev/trunk/solr/src/java/org/apache/solr/handler/component/QueryComponent.java (original)
+++ lucene/dev/trunk/solr/src/java/org/apache/solr/handler/component/QueryComponent.java Wed Nov 24 19:34:14 2010
@@ -306,6 +306,7 @@ public class QueryComponent extends Sear
         String[] funcs = params.getParams(GroupParams.GROUP_FUNC);
         String[] queries = params.getParams(GroupParams.GROUP_QUERY);
         String groupSortStr = params.get(GroupParams.GROUP_SORT);
+        boolean main = params.getBool(GroupParams.GROUP_MAIN, false);
 
         // groupSort defaults to sort
         Sort groupSort = groupSortStr == null ? cmd.getSort() : QueryParsing.parseSort(groupSortStr, req);
@@ -345,6 +346,12 @@ public class QueryComponent extends Sear
             gc.offset = cmd.getOffset();
             gc.sort = cmd.getSort();
 
+            if (main) {
+              gc.main = true;
+              main = false;
+              gc.groupOffset = 0;  // doesn't make sense
+            }
+
             grouping.add(gc);
           }
         }
@@ -361,6 +368,17 @@ public class QueryComponent extends Sear
             gc.docsPerGroup = docsPerGroupDefault;
             gc.groupOffset = groupOffsetDefault;
 
+            // these two params will only be used if this is for the main result set
+            gc.offset = cmd.getOffset();
+            gc.numGroups = limitDefault;
+
+            if (main) {
+              gc.main = true;
+              main = false;
+              gc.docsPerGroup = gc.numGroups;  // doesn't make sense to limit to one
+              gc.groupOffset = gc.offset;
+            }
+
             grouping.add(gc);
           }
         }
@@ -376,6 +394,12 @@ public class QueryComponent extends Sear
         rb.setResult( result );
         rsp.add("grouped", result.groupedResults);
         // TODO: get "hits" a different way to log
+
+        if (grouping.mainResult != null) {
+          rsp.add("response",grouping.mainResult);
+          rsp.getToLog().add("hits", grouping.mainResult.matches());
+        }
+
         return;
 
       } catch (ParseException e) {

Modified: lucene/dev/trunk/solr/src/java/org/apache/solr/search/Grouping.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/src/java/org/apache/solr/search/Grouping.java?rev=1038779&r1=1038778&r2=1038779&view=diff
==============================================================================
--- lucene/dev/trunk/solr/src/java/org/apache/solr/search/Grouping.java (original)
+++ lucene/dev/trunk/solr/src/java/org/apache/solr/search/Grouping.java Wed Nov 24 19:34:14 2010
@@ -33,6 +33,7 @@ import java.util.*;
 
 public class Grouping {
 
+  public enum Format {Grouped, Simple}
 
   public abstract class Command {
     public String key;       // the name to use for this group in the response
@@ -42,6 +43,8 @@ public class Grouping {
     public int groupOffset;  // the offset within each group (for paging within each group)
     public int numGroups;    // how many groups - defaults to the "rows" parameter
     public int offset;       // offset into the list of groups
+    public Format format;
+    public boolean main;     // use as the main result in simple format (grouped.main=true param)
 
 
     abstract void prepare() throws IOException;
@@ -65,7 +68,13 @@ public class Grouping {
 
     DocList getDocList(TopDocsCollector collector) {
       int max = collector.getTotalHits();
-      int docsToCollect = getMax(groupOffset, docsPerGroup, max);
+      int off = groupOffset;
+      int len = docsPerGroup;
+      if (main) {
+        off = offset;
+        len = numGroups;
+      }
+      int docsToCollect = getMax(off, len, max);
 
       // TODO: implement a DocList impl that doesn't need to start at offset=0
       TopDocs topDocs = collector.topDocs(0, docsToCollect);
@@ -80,7 +89,7 @@ public class Grouping {
 
       float score = topDocs.getMaxScore();
       maxScore = Math.max(maxScore, score);
-      DocSlice docs = new DocSlice(groupOffset, Math.max(0, ids.length - groupOffset), ids, scores, topDocs.totalHits, score);
+      DocSlice docs = new DocSlice(off, Math.max(0, ids.length - off), ids, scores, topDocs.totalHits, score);
 
       if (getDocList) {
         DocIterator iter = docs.iterator();
@@ -116,8 +125,12 @@ public class Grouping {
 
     @Override
     void finish() throws IOException {
-      NamedList rsp = commonResponse();
-      addDocList(rsp, (TopDocsCollector)collector.getCollector());
+      if (main) {
+        mainResult = getDocList((TopDocsCollector)collector.getCollector());
+      } else {
+        NamedList rsp = commonResponse();
+        addDocList(rsp, (TopDocsCollector)collector.getCollector());
+      }
     }
 
     @Override
@@ -168,16 +181,24 @@ public class Grouping {
       int docsToCollect = getMax(groupOffset, docsPerGroup, maxDoc);
       docsToCollect = Math.max(docsToCollect, 1);
 
+      // if this for the main result, don't skip groups (since we are counting docs, not groups)
+      int collectorOffset = main ? 0 : offset;
+
       if (groupBy instanceof StrFieldSource) {
-        collector2 = new Phase2StringGroupCollector(collector, groupBy, context, groupSort, docsToCollect, needScores, offset);
+        collector2 = new Phase2StringGroupCollector(collector, groupBy, context, groupSort, docsToCollect, needScores, collectorOffset);
       } else {
-        collector2 = new Phase2GroupCollector(collector, groupBy, context, groupSort, docsToCollect, needScores, offset);
+        collector2 = new Phase2GroupCollector(collector, groupBy, context, groupSort, docsToCollect, needScores, collectorOffset);
       }
       return collector2;
     }
 
     @Override
     void finish() throws IOException {
+      if (main) {
+        createMainResponse();
+        return;
+      }
+
       NamedList groupResult = commonResponse();
 
       List groupList = new ArrayList();
@@ -204,6 +225,57 @@ public class Grouping {
       }
     }
 
+    private void createMainResponse() {
+      int docCount = numGroups;
+      int docOffset = offset;    
+      int docsToGather = getMax(docOffset, docCount, maxDoc);
+
+      float maxScore = Float.NEGATIVE_INFINITY; 
+      List<TopDocs> topDocsList = new ArrayList<TopDocs>();
+      int numDocs = 0;
+      for (SearchGroup group : collector.orderedGroups) {
+        SearchGroupDocs groupDocs = collector2.groupMap.get(group.groupValue);
+        
+        TopDocsCollector collector = groupDocs.collector;
+        int hits = collector.getTotalHits();
+
+        int num = Math.min(docsPerGroup, hits - groupOffset); // how many docs are in this group
+        if (num <= 0) continue;
+
+        TopDocs topDocs = collector.topDocs(groupOffset, Math.min(docsPerGroup,docsToGather-numDocs));
+        topDocsList.add(topDocs);
+        numDocs += topDocs.scoreDocs.length;
+
+        float score = topDocs.getMaxScore();
+        maxScore = Math.max(maxScore, score);
+
+        if (numDocs >= docsToGather) break;
+      }
+      assert numDocs <= docCount; // make sure we didn't gather too many
+      
+      int[] ids = new int[numDocs];
+      float[] scores = needScores ? new float[numDocs] : null;
+      int pos = 0;
+
+      for (TopDocs topDocs : topDocsList) {
+        for (ScoreDoc sd : topDocs.scoreDocs) {
+          ids[pos] = sd.doc;
+          if (scores != null) scores[pos] = sd.score;
+          pos++;
+        }
+      }
+
+      DocSlice docs = new DocSlice(docOffset, Math.max(0, ids.length - docOffset), ids, scores, getMatches(), maxScore);
+
+      if (getDocList) {
+        DocIterator iter = docs.iterator();
+        while (iter.hasNext())
+          idSet.add(iter.nextDoc());
+      }
+
+      mainResult = docs;
+    }
+
     @Override
     int getMatches() {
       return collector.getMatches();
@@ -243,6 +315,8 @@ public class Grouping {
   final SolrIndexSearcher.QueryCommand cmd;
   final List<Command> commands = new ArrayList<Command>();
 
+  public DocList mainResult;  // output if one of the grouping commands should be used as the main result.
+
   public Grouping(SolrIndexSearcher searcher, SolrIndexSearcher.QueryResult qr, SolrIndexSearcher.QueryCommand cmd) {
     this.searcher = searcher;
     this.qr = qr;

Modified: lucene/dev/trunk/solr/src/test/org/apache/solr/TestGroupingSearch.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/src/test/org/apache/solr/TestGroupingSearch.java?rev=1038779&r1=1038778&r2=1038779&view=diff
==============================================================================
--- lucene/dev/trunk/solr/src/test/org/apache/solr/TestGroupingSearch.java (original)
+++ lucene/dev/trunk/solr/src/test/org/apache/solr/TestGroupingSearch.java Wed Nov 24 19:34:14 2010
@@ -291,6 +291,22 @@ public class TestGroupingSearch extends 
            "'doclist':{'numFound':4,'start':10,'docs':[]}}}"
     );
 
+    ///////////////////////// group.query as main result
+    assertJQ(req("fq",filt,  "q","{!func}"+f2, "group","true", "group.query","id:[2 TO 5]", "fl","id", "rows","3", "group.main","true")
+       ,"/response=={'numFound':4,'start':0,'docs':[{'id':'3'},{'id':'4'},{'id':'2'}]}"
+    );
+
+    // group.query and offset
+    assertJQ(req("fq",filt,  "q","{!func}"+f2, "group","true", "group.query","id:[2 TO 5]", "fl","id", "rows","3", "start","2", "group.main","true")
+       ,"/response=={'numFound':4,'start':2,'docs':[{'id':'2'},{'id':'5'}]}"
+    );
+
+    // group.query and big offset
+    assertJQ(req("fq",filt,  "q","{!func}"+f2, "group","true", "group.query","id:[2 TO 5]", "fl","id", "rows","3", "start","10", "group.main","true")
+       ,"/response=={'numFound':4,'start':10,'docs':[]}"
+    );
+
+
     // multiple at once
     assertJQ(req("fq",filt,  "q","{!func}"+f2, "group","true",
         "group.query","id:[2 TO 5]",
@@ -304,6 +320,30 @@ public class TestGroupingSearch extends 
     );
 
 
+    ///////////////////////// group.field as main result
+    assertJQ(req("fq",filt,  "q","{!func}"+f2, "group","true", "group.field",f, "fl","id", "group.main","true")
+        ,"/response=={'numFound':10,'start':0,'docs':[{'id':'8'},{'id':'3'},{'id':'4'},{'id':'1'},{'id':'2'}]}"
+    );
+    // test that rows limits #docs
+    assertJQ(req("fq",filt,  "q","{!func}"+f2, "group","true", "group.field",f, "fl","id", "rows","3", "group.main","true")
+        ,"/response=={'numFound':10,'start':0,'docs':[{'id':'8'},{'id':'3'},{'id':'4'}]}"
+    );
+    // small  offset
+    assertJQ(req("fq",filt,  "q","{!func}"+f2, "group","true", "group.field",f, "fl","id", "rows","2", "start","1", "group.main","true")
+        ,"/response=={'numFound':10,'start':1,'docs':[{'id':'3'},{'id':'4'}]}"
+    );
+    // large offset
+    assertJQ(req("fq",filt,  "q","{!func}"+f2, "group","true", "group.field",f, "fl","id", "rows","2", "start","20", "group.main","true")
+        ,"/response=={'numFound':10,'start':20,'docs':[]}"
+    );
+    // group.limit>1
+    assertJQ(req("fq",filt,  "q","{!func}"+f2, "group","true", "group.field",f, "fl","id", "rows","3", "group.limit","2", "group.main","true")
+        ,"/response=={'numFound':10,'start':0,'docs':[{'id':'8'},{'id':'10'},{'id':'3'}]}"
+    );
+    // group.limit>1 with start>0
+    assertJQ(req("fq",filt,  "q","{!func}"+f2, "group","true", "group.field",f, "fl","id", "rows","3", "start","1", "group.limit","2", "group.main","true")
+        ,"/response=={'numFound':10,'start':1,'docs':[{'id':'10'},{'id':'3'},{'id':'6'}]}"
+    );
   };