You are viewing a plain text version of this content. The canonical link for it is here.
Posted to solr-commits@lucene.apache.org by yo...@apache.org on 2008/12/17 23:41:35 UTC

svn commit: r727560 - in /lucene/solr/trunk: ./ src/common/org/apache/solr/common/params/ src/java/org/apache/solr/handler/component/ src/java/org/apache/solr/request/ src/java/org/apache/solr/search/ src/test/org/apache/solr/ src/test/org/apache/solr/...

Author: yonik
Date: Wed Dec 17 14:41:35 2008
New Revision: 727560

URL: http://svn.apache.org/viewvc?rev=727560&view=rev
Log:
SOLR-911: add filter tags, facet local params, with excludes, output keys, and more efficient facet refinement requests

Modified:
    lucene/solr/trunk/CHANGES.txt
    lucene/solr/trunk/src/common/org/apache/solr/common/params/CommonParams.java
    lucene/solr/trunk/src/java/org/apache/solr/handler/component/FacetComponent.java
    lucene/solr/trunk/src/java/org/apache/solr/request/SimpleFacets.java
    lucene/solr/trunk/src/java/org/apache/solr/search/QParser.java
    lucene/solr/trunk/src/test/org/apache/solr/TestDistributedSearch.java
    lucene/solr/trunk/src/test/org/apache/solr/request/SimpleFacetsTest.java

Modified: lucene/solr/trunk/CHANGES.txt
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/CHANGES.txt?rev=727560&r1=727559&r2=727560&view=diff
==============================================================================
--- lucene/solr/trunk/CHANGES.txt (original)
+++ lucene/solr/trunk/CHANGES.txt Wed Dec 17 14:41:35 2008
@@ -112,6 +112,13 @@
     of solrconfig.xml
     (Noble Paul, Akshay Ukey via shalin)
 
+24. SOLR-911: Add support for multi-select faceting by allowing filters to be
+    tagged and facet commands to exclude certain filters.  This patch also
+    added the ability to change the output key for facets in the response, and
+    optimized distributed faceting refinement by lowering parsing overhead and
+    by making requests and responses smaller.
+
+
 Optimizations
 ----------------------
  1. SOLR-374: Use IndexReader.reopen to save resources by re-using parts of the

Modified: lucene/solr/trunk/src/common/org/apache/solr/common/params/CommonParams.java
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/src/common/org/apache/solr/common/params/CommonParams.java?rev=727560&r1=727559&r2=727560&view=diff
==============================================================================
--- lucene/solr/trunk/src/common/org/apache/solr/common/params/CommonParams.java (original)
+++ lucene/solr/trunk/src/common/org/apache/solr/common/params/CommonParams.java Wed Dec 17 14:41:35 2008
@@ -115,5 +115,14 @@
       return null;
     }
   };
+
+  public static final String EXCLUDE = "ex";
+  public static final String TAG = "tag";
+  public static final String TERMS = "terms";
+  public static final String OUTPUT_KEY = "key";
+  public static final String FIELD = "f";
+  public static final String VALUE = "v";
+  public static final String TRUE = Boolean.TRUE.toString();
+  public static final String FALSE = Boolean.FALSE.toString();
 }
 

Modified: lucene/solr/trunk/src/java/org/apache/solr/handler/component/FacetComponent.java
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/src/java/org/apache/solr/handler/component/FacetComponent.java?rev=727560&r1=727559&r2=727560&view=diff
==============================================================================
--- lucene/solr/trunk/src/java/org/apache/solr/handler/component/FacetComponent.java (original)
+++ lucene/solr/trunk/src/java/org/apache/solr/handler/component/FacetComponent.java Wed Dec 17 14:41:35 2008
@@ -27,10 +27,10 @@
 import org.apache.solr.common.params.ModifiableSolrParams;
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.common.util.SimpleOrderedMap;
+import org.apache.solr.common.util.StrUtils;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.request.SimpleFacets;
 import org.apache.lucene.util.OpenBitSet;
-import org.apache.solr.schema.SchemaField;
 import org.apache.solr.search.QueryParsing;
 import org.apache.lucene.queryParser.ParseException;
 
@@ -64,13 +64,15 @@
       SolrParams params = rb.req.getParams();
       SimpleFacets f = new SimpleFacets(rb.req,
               rb.getResults().docSet,
-              params );
+              params,
+              rb );
 
       // TODO ???? add this directly to the response, or to the builder?
       rb.rsp.add( "facet_counts", f.getFacetCounts() );
     }
   }
 
+  private static final String commandPrefix = "{!" + CommonParams.TERMS + "=$";
 
   @Override
   public int distributedProcess(ResponseBuilder rb) throws IOException {
@@ -86,12 +88,42 @@
       // We do this in distributedProcess so we can look at all of the
       // requests in the outgoing queue at once.
 
+
+
       for (int shardNum=0; shardNum<rb.shards.length; shardNum++) {
-        List<String> fqueries = rb._facetInfo._toRefine[shardNum];
-        if (fqueries == null || fqueries.size()==0) continue;
+        List<String> refinements = null;
 
-        String shard = rb.shards[shardNum];
+        for (DistribFieldFacet dff : rb._facetInfo.facets.values()) {
+          if (!dff.needRefinements) continue;
+          List<String> refList = dff._toRefine[shardNum];
+          if (refList == null | refList.size()==0) continue;
+
+          String key = dff.getKey();  // reuse the same key that was used for the main facet
+          String termsKey = key + "__terms";
+          String termsVal = StrUtils.join(refList, ',');
+
+          String facetCommand;
+          // add terms into the original facet.field command
+          // do it via parameter reference to avoid another layer of encoding.
+          if (dff.localParams != null) {
+            facetCommand = commandPrefix+termsKey+dff.facetStr.substring(2);
+          } else {
+            facetCommand = commandPrefix+termsKey+'}'+dff.field;
+          }
+
+          if (refinements == null) {
+            refinements = new ArrayList<String>();
+          }
+
+          refinements.add(facetCommand);
+          refinements.add(termsKey);
+          refinements.add(termsVal);
+        }
 
+        if (refinements == null) continue;
+
+
+        String shard = rb.shards[shardNum];
         ShardRequest refine = null;
         boolean newRequest = false;
 
@@ -100,7 +132,7 @@
         // scalability.
         for (ShardRequest sreq : rb.outgoing) {
           if ((sreq.purpose & ShardRequest.PURPOSE_GET_FIELDS)!=0
-                  && sreq.shards != null 
+                  && sreq.shards != null
                   && sreq.shards.length==1
                   && sreq.shards[0].equals(shard))
           {
@@ -124,8 +156,16 @@
         refine.purpose |= ShardRequest.PURPOSE_REFINE_FACETS;
         refine.params.set(FacetParams.FACET, "true");
         refine.params.remove(FacetParams.FACET_FIELD);
-        // TODO: perhaps create a more compact facet.terms method?
-        refine.params.set(FacetParams.FACET_QUERY, fqueries.toArray(new String[fqueries.size()]));
+        refine.params.remove(FacetParams.FACET_QUERY);
+
+        for (int i=0; i<refinements.size();) {
+          String facetCommand=refinements.get(i++);
+          String termsKey=refinements.get(i++);
+          String termsVal=refinements.get(i++);
+
+          refine.params.add(FacetParams.FACET_FIELD, facetCommand);
+          refine.params.set(termsKey, termsVal);
+        }
 
         if (newRequest) {
           rb.addRequest(this, refine);
@@ -148,7 +188,7 @@
           rb._facetInfo = fi = new FacetInfo();
           fi.parse(rb.req.getParams(), rb);
           // should already be true...
-          // sreq.params.set(FacetParams.FACET, FacetParams.FACET_SORT_COUNT_LEGACY);
+          // sreq.params.set(FacetParams.FACET, "true");
         }
 
         sreq.params.remove(FacetParams.FACET_MINCOUNT);
@@ -206,18 +246,17 @@
       NamedList facet_queries = (NamedList)facet_counts.get("facet_queries");
       if (facet_queries != null) {
         for (int i=0; i<facet_queries.size(); i++) {
-          String facet_q = (String)facet_queries.getName(i);
+          String returnedKey = (String)facet_queries.getName(i);
           long count = ((Number)facet_queries.getVal(i)).longValue();
-          Long prevCount = fi.queryFacets.get(facet_q);
-          if (prevCount != null) count += prevCount;
-          fi.queryFacets.put(facet_q, count);
+          QueryFacet qf = fi.queryFacets.get(returnedKey);
+          qf.count += count;
         }
       }
 
       // step through each facet.field, adding results from this shard
       NamedList facet_fields = (NamedList)facet_counts.get("facet_fields");      
       for (DistribFieldFacet dff : fi.facets.values()) {
-        dff.add(shardNum, (NamedList)facet_fields.get(dff.field), dff.initialLimit);
+        dff.add(shardNum, (NamedList)facet_fields.get(dff.getKey()), dff.initialLimit);
       }
     }
 
@@ -228,24 +267,17 @@
     // otherwise we would need to wait until all facet responses were received.
     //
 
-    // list of queries to send each shard
-    List<String>[] toRefine = new List[rb.shards.length];
-    fi._toRefine = toRefine;
-    for (int i=0; i<toRefine.length; i++) {
-      toRefine[i] = new ArrayList<String>();
-    }
-
-
     for (DistribFieldFacet dff : fi.facets.values()) {
       if (dff.limit <= 0) continue; // no need to check these facets for refinement
       if (dff.minCount <= 1 && dff.sort.equals(FacetParams.FACET_SORT_LEX)) continue;
+
+      dff._toRefine = new List[rb.shards.length];
       ShardFacetCount[] counts = dff.getCountSorted();
       int ntop = Math.min(counts.length, dff.offset + dff.limit);
       long smallestCount = counts.length == 0 ? 0 : counts[ntop-1].count;
 
       for (int i=0; i<counts.length; i++) {
         ShardFacetCount sfc = counts[i];
-        String query = null;
         boolean needRefinement = false;
 
         if (i<ntop) {
@@ -274,8 +306,11 @@
             OpenBitSet obs = dff.counted[shardNum];
             if (!obs.get(sfc.termNum) && dff.maxPossible(sfc,shardNum)>0) {
               dff.needRefinements = true;
-              if (query==null) query = dff.makeQuery(sfc);
-              toRefine[shardNum].add(query);
+              List<String> lst = dff._toRefine[shardNum];
+              if (lst == null) {
+                lst = dff._toRefine[shardNum] = new ArrayList<String>();
+              }
+              lst.add(sfc.name);
             }
           }
         }
@@ -290,44 +325,20 @@
     for (ShardResponse srsp: sreq.responses) {
       // int shardNum = rb.getShardNum(srsp.shard);
       NamedList facet_counts = (NamedList)srsp.getSolrResponse().getResponse().get("facet_counts");
-      NamedList facet_queries = (NamedList)facet_counts.get("facet_queries");
-
-      // These are single term queries used to fill in missing counts
-      // for facet.field queries
-      for (int i=0; i<facet_queries.size(); i++) {
-        try {
-          
-          String facet_q = (String)facet_queries.getName(i);
-          long count = ((Number)facet_queries.getVal(i)).longValue();
-
-          // expect {!field f=field}value style params
-          SolrParams qparams = QueryParsing.getLocalParams(facet_q,null);
-          if (qparams == null) continue;  // not a refinement
-          String field = qparams.get(QueryParsing.F);
-          String val = qparams.get(QueryParsing.V);
-
-          // Find the right field.facet for this field
-          DistribFieldFacet dff = fi.facets.get(field);
-          if (dff == null) continue;  // maybe this wasn't for facet count refinement
-
-          // Find the right constraint count for this value
-          ShardFacetCount sfc = dff.counts.get(val);
-
-          if (sfc == null) {
-            continue;
-            // Just continue, since other components might have added
-            // this facet.query for other purposes.  But if there are charset
-            // issues then the values coming back may not match the values sent.
-          }
-
-// TODO REMOVE
-// System.out.println("Got " + facet_q + " , refining count: " + sfc + " += " + count);
+      NamedList facet_fields = (NamedList)facet_counts.get("facet_fields");      
 
+      for (int i=0; i<facet_fields.size(); i++) {
+        String key = facet_fields.getName(i);
+        DistribFieldFacet dff = (DistribFieldFacet)fi.facets.get(key);
+        if (dff == null) continue;
+
+        NamedList shardCounts = (NamedList)facet_fields.getVal(i);
+
+        for (int j=0; j<shardCounts.size(); j++) {
+          String name = shardCounts.getName(j);
+          long count = ((Number)shardCounts.getVal(j)).longValue();
+          ShardFacetCount sfc = dff.counts.get(name);
           sfc.count += count;
-
-        } catch (ParseException e) {
-          // shouldn't happen, so fail for now rather than covering it up
-          throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, e);
         }
       }
     }
@@ -345,8 +356,8 @@
     NamedList facet_counts = new SimpleOrderedMap();
     NamedList facet_queries = new SimpleOrderedMap();
     facet_counts.add("facet_queries",facet_queries);
-    for (Map.Entry<String,Long> entry : fi.queryFacets.entrySet()) {
-      facet_queries.add(entry.getKey(), num(entry.getValue()));
+    for (QueryFacet qf : fi.queryFacets.values()) {
+      facet_queries.add(qf.getKey(), num(qf.count));
     }
 
     NamedList facet_fields = new SimpleOrderedMap();
@@ -354,7 +365,7 @@
 
     for (DistribFieldFacet dff : fi.facets.values()) {
       NamedList fieldCounts = new NamedList(); // order is more important for facets
-      facet_fields.add(dff.field, fieldCounts);
+      facet_fields.add(dff.getKey(), fieldCounts);
 
       ShardFacetCount[] counts;
       if (dff.sort.equals(FacetParams.FACET_SORT_COUNT)) {
@@ -379,7 +390,6 @@
       }
     }
 
-    // TODO: list facets (sorted by natural order)
     // TODO: facet dates
     facet_counts.add("facet_dates", new SimpleOrderedMap());
 
@@ -433,36 +443,76 @@
 
 
 class FacetInfo {
-  List<String>[] _toRefine;
-
+  LinkedHashMap<String,QueryFacet> queryFacets;
+  LinkedHashMap<String,DistribFieldFacet> facets;
+  
   void parse(SolrParams params, ResponseBuilder rb) {
-    queryFacets = new LinkedHashMap<String,Long>();
+    queryFacets = new LinkedHashMap<String,QueryFacet>();
     facets = new LinkedHashMap<String,DistribFieldFacet>();
 
     String[] facetQs = params.getParams(FacetParams.FACET_QUERY);
     if (facetQs != null) {
       for (String query : facetQs) {
-        queryFacets.put(query,0L);
+        QueryFacet queryFacet = new QueryFacet(rb, query);
+        queryFacets.put(queryFacet.getKey(), queryFacet);
       }
     }
 
     String[] facetFs = params.getParams(FacetParams.FACET_FIELD);
     if (facetFs != null) {
+
       for (String field : facetFs) {
         DistribFieldFacet ff = new DistribFieldFacet(rb, field);
-        ff.fillParams(params, field);
-        facets.put(field, ff);
+        facets.put(ff.getKey(), ff);
       }
     }
   }
+}
 
-  LinkedHashMap<String,Long> queryFacets;
-  LinkedHashMap<String,DistribFieldFacet> facets;
+class FacetBase {
+  String facetType;  // facet.field, facet.query, etc (make enum?)
+  String facetStr;   // original parameter value of facetStr
+  String facetOn;    // the field or query, absent localParams if appropriate
+  private String key; // label in the response for the result... "foo" for {!key=foo}myfield
+  SolrParams localParams;  // any local params for the facet
+
+  public FacetBase(ResponseBuilder rb, String facetType, String facetStr) {
+    this.facetType = facetType;
+    this.facetStr = facetStr;
+    try {
+      this.localParams = QueryParsing.getLocalParams(facetStr, rb.req.getParams());
+    } catch (ParseException e) {
+      throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, e);
+    }
+    this.facetOn = facetStr;
+    this.key = facetStr;
+
+    if (localParams != null) {
+      // remove local params unless it's a query
+      if (!facetType.equals(FacetParams.FACET_QUERY)) {
+        facetOn = localParams.get(CommonParams.VALUE);
+        key = facetOn;
+      }
+
+      key = localParams.get(CommonParams.OUTPUT_KEY, key);
+    }
+  }
+
+  /** returns the key in the response that this facet will be under */
+  String getKey() { return key; }
+  String getType() { return facetType; }
 }
 
+class QueryFacet extends FacetBase {
+  long count;
+
+  public QueryFacet(ResponseBuilder rb, String facetStr) {
+    super(rb, FacetParams.FACET_QUERY, facetStr);
+  }
+}
 
-class FieldFacet {
-  String field;
+class FieldFacet extends FacetBase {
+  String field;     // the field to facet on... "myfield" for {!key=foo}myfield
   int offset;
   int limit;
   int minCount;
@@ -471,7 +521,12 @@
   String prefix;
   long missingCount;
 
-  void fillParams(SolrParams params, String field) {
+  public FieldFacet(ResponseBuilder rb, String facetStr) {
+    super(rb, FacetParams.FACET_FIELD, facetStr);
+    fillParams(rb.req.getParams(), facetOn);
+  }
+
+  private void fillParams(SolrParams params, String field) {
     this.field = field;
     this.offset = params.getFieldInt(field, FacetParams.FACET_OFFSET, 0);
     this.limit = params.getFieldInt(field, FacetParams.FACET_LIMIT, 100);
@@ -496,7 +551,9 @@
 }
 
 class DistribFieldFacet extends FieldFacet {
-  SchemaField sf;
+  List<String>[] _toRefine; // a List<String> of refinements needed, one for each shard.
+
+  // SchemaField sf;    // currently unneeded
 
   // the max possible count for a term appearing on no list
   long missingMaxPossible;
@@ -505,17 +562,16 @@
   OpenBitSet[] counted; // a bitset for each shard, keeping track of which terms seen
   HashMap<String,ShardFacetCount> counts = new HashMap<String,ShardFacetCount>(128);
   int termNum;
-  String queryPrefix;
 
   int initialLimit;  // how many terms requested in first phase
   boolean needRefinements;  
   ShardFacetCount[] countSorted;
 
-  DistribFieldFacet(ResponseBuilder rb, String field) {
-    sf = rb.req.getSchema().getField(field);
+  DistribFieldFacet(ResponseBuilder rb, String facetStr) {
+    super(rb, facetStr);
+    // sf = rb.req.getSchema().getField(field);
     missingMax = new long[rb.shards.length];
     counted = new OpenBitSet[rb.shards.length];
-    queryPrefix = "{!field f=" + field + '}';
   }
 
   void add(int shardNum, NamedList shardCounts, int numRequested) {
@@ -582,10 +638,6 @@
     return arr;
   }
 
-  String makeQuery(ShardFacetCount sfc) {
-    return queryPrefix + sfc.name;    
-  }
-
   // returns the max possible value this ShardFacetCount could have for this shard
   // (assumes the shard did not report a count for this value)
   long maxPossible(ShardFacetCount sfc, int shardNum) {
@@ -593,7 +645,6 @@
     // TODO: could store the last term in the shard to tell if this term
     // comes before or after it.  If it comes before, we could subtract 1
   }
-
 }
 
 

Modified: lucene/solr/trunk/src/java/org/apache/solr/request/SimpleFacets.java
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/src/java/org/apache/solr/request/SimpleFacets.java?rev=727560&r1=727559&r2=727560&view=diff
==============================================================================
--- lucene/solr/trunk/src/java/org/apache/solr/request/SimpleFacets.java (original)
+++ lucene/solr/trunk/src/java/org/apache/solr/request/SimpleFacets.java Wed Dec 17 14:41:35 2008
@@ -27,9 +27,11 @@
 import org.apache.solr.common.params.FacetParams;
 import org.apache.solr.common.params.RequiredSolrParams;
 import org.apache.solr.common.params.SolrParams;
+import org.apache.solr.common.params.CommonParams;
 import org.apache.solr.common.params.FacetParams.FacetDateOther;
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.common.util.SimpleOrderedMap;
+import org.apache.solr.common.util.StrUtils;
 import org.apache.solr.core.SolrCore;
 import org.apache.solr.schema.IndexSchema;
 import org.apache.solr.schema.FieldType;
@@ -39,14 +41,10 @@
 import org.apache.solr.search.*;
 import org.apache.solr.util.BoundedTreeSet;
 import org.apache.solr.util.DateMathParser;
+import org.apache.solr.handler.component.ResponseBuilder;
 
 import java.io.IOException;
-import java.util.Arrays;
-import java.util.Comparator;
-import java.util.Date;
-import java.util.Locale;
-import java.util.Set;
-import java.util.EnumSet;
+import java.util.*;
 
 /**
  * A class that generates simple Facet information for a request.
@@ -63,16 +61,92 @@
   /** Searcher to use for all calculations */
   protected SolrIndexSearcher searcher;
   protected SolrQueryRequest req;
+  protected ResponseBuilder rb;
+
+  // per-facet values
+  SolrParams localParams; // localParams on this particular facet command
+  String facetValue;      // the field to or query to facet on (minus local params)
+  DocSet base;            // the base docset for this particular facet
+  String key;             // what name should the results be stored under
 
   public SimpleFacets(SolrQueryRequest req,
                       DocSet docs,
                       SolrParams params) {
+    this(req,docs,params,null);
+  }
+
+  public SimpleFacets(SolrQueryRequest req,
+                      DocSet docs,
+                      SolrParams params,
+                      ResponseBuilder rb) {
     this.req = req;
     this.searcher = req.getSearcher();
-    this.docs = docs;
+    this.base = this.docs = docs;
     this.params = params;
+    this.rb = rb;
   }
 
+
+  void parseParams(String type, String param) throws ParseException, IOException {
+    localParams = QueryParsing.getLocalParams(param, req.getParams());
+    base = docs;
+    facetValue = param;
+    key = param;
+
+    if (localParams == null) return;
+
+    // remove local params unless it's a query
+    if (type != FacetParams.FACET_QUERY) {
+      facetValue = localParams.get(CommonParams.VALUE);
+    }
+
+    // reset set the default key now that localParams have been removed
+    key = facetValue;
+
+    // allow explicit set of the key
+    key = localParams.get(CommonParams.OUTPUT_KEY, key);
+
+    // figure out if we need a new base DocSet
+    String excludeStr = localParams.get(CommonParams.EXCLUDE);
+    if (excludeStr == null) return;
+
+    Map tagMap = (Map)req.getContext().get("tags");
+    if (tagMap != null && rb != null) {
+      List<String> excludeTagList = StrUtils.splitSmart(excludeStr,',');
+
+      IdentityHashMap<Query,Boolean> excludeSet = new IdentityHashMap<Query,Boolean>();
+      for (String excludeTag : excludeTagList) {
+        Object olst = tagMap.get(excludeTag);
+        // tagMap has entries of List<String,List<QParser>>, but subject to change in the future
+        if (!(olst instanceof Collection)) continue;
+        for (Object o : (Collection)olst) {
+          if (!(o instanceof QParser)) continue;
+          QParser qp = (QParser)o;
+          excludeSet.put(qp.getQuery(), Boolean.TRUE);
+        }
+      }
+      if (excludeSet.size() == 0) return;
+
+      List<Query> qlist = new ArrayList<Query>();
+
+      // add the base query
+      qlist.add(rb.getQuery());
+
+      // add the filters
+      for (Query q : rb.getFilters()) {
+        if (!excludeSet.containsKey(q)) {
+          qlist.add(q);
+        }
+
+      }
+
+      // get the new base docset for this facet
+      base = searcher.getDocSet(qlist);
+    }
+
+  }
+
+
   /**
    * Looks at various Params to determing if any simple Facet Constraint count
    * computations are desired.
@@ -123,8 +197,11 @@
     String[] facetQs = params.getParams(FacetParams.FACET_QUERY);
     if (null != facetQs && 0 != facetQs.length) {
       for (String q : facetQs) {
+        parseParams(FacetParams.FACET_QUERY, q);
+
+        // TODO: slight optimization would prevent double-parsing of any localParams
         Query qobj = QParser.getParser(q, null, req).getQuery();
-        res.add(q, searcher.numDocs(qobj, docs));
+        res.add(key, searcher.numDocs(qobj, base));
       }
     }
 
@@ -164,15 +241,15 @@
 
     // unless the enum method is explicitly specified, use a counting method.
     if (enumMethod) {
-      counts = getFacetTermEnumCounts(searcher, docs, field, offset, limit, mincount,missing,sort,prefix);
+      counts = getFacetTermEnumCounts(searcher, base, field, offset, limit, mincount,missing,sort,prefix);
     } else {
       if (multiToken) {
         UnInvertedField uif = UnInvertedField.getUnInvertedField(field, searcher);
-        counts = uif.getCounts(searcher, docs, offset, limit, mincount,missing,sort,prefix);
+        counts = uif.getCounts(searcher, base, offset, limit, mincount,missing,sort,prefix);
       } else {
         // TODO: future logic could use filters instead of the fieldcache if
         // the number of terms in the field is small enough.
-        counts = getFieldCacheCounts(searcher, docs, field, offset,limit, mincount, missing, sort, prefix);
+        counts = getFieldCacheCounts(searcher, base, field, offset,limit, mincount, missing, sort, prefix);
       }
     }
 
@@ -189,18 +266,39 @@
    * @see #getFacetTermEnumCounts
    */
   public NamedList getFacetFieldCounts()
-          throws IOException {
+          throws IOException, ParseException {
 
     NamedList res = new SimpleOrderedMap();
     String[] facetFs = params.getParams(FacetParams.FACET_FIELD);
     if (null != facetFs) {
       for (String f : facetFs) {
-        res.add(f, getTermCounts(f));
+        parseParams(FacetParams.FACET_FIELD, f);
+        String termList = localParams == null ? null : localParams.get(CommonParams.TERMS);
+        if (termList != null) {
+          res.add(key, getListedTermCounts(facetValue, termList));
+        } else {
+          res.add(key, getTermCounts(facetValue));
+        }
       }
     }
     return res;
   }
 
+
+  private NamedList getListedTermCounts(String field, String termList) throws IOException {
+    FieldType ft = searcher.getSchema().getFieldType(field);
+    List<String> terms = StrUtils.splitSmart(termList, ",", true);
+    NamedList res = new NamedList();
+    Term t = new Term(field);
+    for (String term : terms) {
+      String internal = ft.toInternal(term);
+      int count = searcher.numDocs(new TermQuery(t.createTerm(internal)), base);
+      res.add(term, count);
+    }
+    return res;    
+  }
+
+
   /**
    * Returns a count of the documents in the set which do not have any 
    * terms for for the specified field.
@@ -441,7 +539,7 @@
    * @see FacetParams#FACET_DATE
    */
   public NamedList getFacetDateCounts()
-          throws IOException {
+          throws IOException, ParseException {
 
     final SolrParams required = new RequiredSolrParams(params);
     final NamedList resOuter = new SimpleOrderedMap();
@@ -452,8 +550,12 @@
     
     final IndexSchema schema = searcher.getSchema();
     for (String f : fields) {
+      parseParams(FacetParams.FACET_DATE, f);
+      f = facetValue;
+
+
       final NamedList resInner = new SimpleOrderedMap();
-      resOuter.add(f, resInner);
+      resOuter.add(key, resInner);
       final FieldType trash = schema.getFieldType(f);
       if (! (trash instanceof DateField)) {
         throw new SolrException
@@ -571,7 +673,7 @@
                            boolean iLow, boolean iHigh) throws IOException {
     return searcher.numDocs(new ConstantScoreRangeQuery(field,low,high,
                                                         iLow,iHigh),
-                            docs);
+                            base);
   }
   
   /**

Modified: lucene/solr/trunk/src/java/org/apache/solr/search/QParser.java
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/src/java/org/apache/solr/search/QParser.java?rev=727560&r1=727559&r2=727560&view=diff
==============================================================================
--- lucene/solr/trunk/src/java/org/apache/solr/search/QParser.java (original)
+++ lucene/solr/trunk/src/java/org/apache/solr/search/QParser.java Wed Dec 17 14:41:35 2008
@@ -22,8 +22,11 @@
 import org.apache.solr.common.params.CommonParams;
 import org.apache.solr.common.params.SolrParams;
 import org.apache.solr.common.util.NamedList;
+import org.apache.solr.common.util.StrUtils;
 import org.apache.solr.request.SolrQueryRequest;
 
+import java.util.*;
+
 public abstract class QParser {
   String qstr;
   SolrParams params;
@@ -33,13 +36,48 @@
 
   Query query;
 
+
   public QParser(String qstr, SolrParams localParams, SolrParams params, SolrQueryRequest req) {
     this.qstr = qstr;
     this.localParams = localParams;
+
+    // insert tags into tagmap.
+    // WARNING: the internal representation of tagged objects in the request context is
+    // experimental and subject to change!
+    if (localParams != null) {
+      String tagStr = localParams.get("tag");
+      if (tagStr != null) {
+        Map context = req.getContext();
+        Map<String,Collection<Object>> tagMap = (Map<String, Collection<Object>>)req.getContext().get("tags");
+        if (tagMap == null) {
+          tagMap = new HashMap<String,Collection<Object>>();
+          context.put("tags", tagMap);          
+        }
+        if (tagStr.indexOf(',') >= 0) {
+          List<String> tags = StrUtils.splitSmart(tagStr, ',');
+          for (String tag : tags) {
+            addTag(tagMap, tag, this);
+          }
+        } else {
+          addTag(tagMap, tagStr, this);
+        }
+      }
+    }
+
     this.params = params;
     this.req = req;
   }
 
+
+  private static void addTag(Map tagMap, Object key, Object val) {
+    Collection lst = (Collection)tagMap.get(key);
+    if (lst == null) {
+      lst = new ArrayList(2);
+      tagMap.put(key, lst);
+    }
+    lst.add(val);
+  }
+
   /** Create and return the <code>Query</code> object represented by <code>qstr</code>
    * @see #getQuery()
    **/

Modified: lucene/solr/trunk/src/test/org/apache/solr/TestDistributedSearch.java
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/src/test/org/apache/solr/TestDistributedSearch.java?rev=727560&r1=727559&r2=727560&view=diff
==============================================================================
--- lucene/solr/trunk/src/test/org/apache/solr/TestDistributedSearch.java (original)
+++ lucene/solr/trunk/src/test/org/apache/solr/TestDistributedSearch.java Wed Dec 17 14:41:35 2008
@@ -548,6 +548,15 @@
     query("q","*:*", "rows",100, "facet","true", "facet.query","quick", "facet.query","all", "facet.query","*:*"
     ,"facet.field",t1);
 
+    // test filter tagging, facet exclusion, and naming (multi-select facet support)
+    query("q","*:*", "rows",100, "facet","true", "facet.query","{!key=myquick}quick", "facet.query","{!key=myall ex=a}all", "facet.query","*:*"
+    ,"facet.field","{!key=mykey ex=a}"+t1
+    ,"facet.field","{!key=other ex=b}"+t1
+    ,"facet.field","{!key=again ex=a,b}"+t1
+    ,"facet.field",t1
+    ,"fq","{!tag=a}id:[1 TO 7]", "fq","{!tag=b}id:[3 TO 9]"
+    );
+
     // test field that is valid in schema but missing in all shards
     query("q","*:*", "rows",100, "facet","true", "facet.field",missingField, "facet.mincount",2);
     // test field that is valid in schema and missing in some shards

Modified: lucene/solr/trunk/src/test/org/apache/solr/request/SimpleFacetsTest.java
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/src/test/org/apache/solr/request/SimpleFacetsTest.java?rev=727560&r1=727559&r2=727560&view=diff
==============================================================================
--- lucene/solr/trunk/src/test/org/apache/solr/request/SimpleFacetsTest.java (original)
+++ lucene/solr/trunk/src/test/org/apache/solr/request/SimpleFacetsTest.java Wed Dec 17 14:41:35 2008
@@ -86,7 +86,31 @@
             ,"//lst[@name='trait_s']/int[@name='Obnoxious'][.='2']"
             ,"//lst[@name='trait_s']/int[@name='Pig'][.='1']"
             );
- 
+
+    assertQ("check multi-select facets with naming",
+            req("q", "id:[42 TO 47]"
+                ,"facet", "true"
+                ,"facet.query", "{!ex=1}trait_s:Obnoxious"
+                ,"facet.query", "{!ex=2 key=foo}id:[42 TO 45]"    // tag=2 same as 1
+                ,"facet.query", "{!ex=3,4 key=bar}id:[43 TO 47]"  // tag=3,4 don't exist
+                ,"facet.field", "{!ex=3,1}trait_s"                // 3,1 same as 1
+                ,"fq", "{!tag=1,2}id:47"                          // tagged as 1 and 2
+                )
+            ,"*[count(//doc)=1]"
+
+            ,"//lst[@name='facet_counts']/lst[@name='facet_queries']"
+            ,"//lst[@name='facet_queries']/int[@name='{!ex=1}trait_s:Obnoxious'][.='2']"
+            ,"//lst[@name='facet_queries']/int[@name='foo'][.='4']"
+            ,"//lst[@name='facet_queries']/int[@name='bar'][.='1']"
+
+            ,"//lst[@name='facet_counts']/lst[@name='facet_fields']"
+            ,"//lst[@name='facet_fields']/lst[@name='trait_s']"
+            ,"*[count(//lst[@name='trait_s']/int)=4]"
+            ,"//lst[@name='trait_s']/int[@name='Tool'][.='2']"
+            ,"//lst[@name='trait_s']/int[@name='Obnoxious'][.='2']"
+            ,"//lst[@name='trait_s']/int[@name='Pig'][.='1']"
+            );
+
     assertQ("check counts for applied facet queries using filtering (fq)",
             req("q", "id:[42 TO 47]"
                 ,"facet", "true"