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 ho...@apache.org on 2006/09/07 20:55:15 UTC

svn commit: r441175 - in /incubator/solr/trunk: ./ src/java/org/apache/solr/request/ src/java/org/apache/solr/util/ src/test/org/apache/solr/

Author: hossman
Date: Thu Sep  7 11:55:14 2006
New Revision: 441175

URL: http://svn.apache.org/viewvc?view=rev&rev=441175
Log:
SOLR-44 - simple facet support for StandardRequestHandler and DisMaxRequestHandler

Added:
    incubator/solr/trunk/src/java/org/apache/solr/request/SimpleFacets.java
    incubator/solr/trunk/src/java/org/apache/solr/util/BoundedTreeSet.java
Modified:
    incubator/solr/trunk/CHANGES.txt
    incubator/solr/trunk/src/java/org/apache/solr/request/DisMaxRequestHandler.java
    incubator/solr/trunk/src/java/org/apache/solr/request/SolrParams.java
    incubator/solr/trunk/src/java/org/apache/solr/request/StandardRequestHandler.java
    incubator/solr/trunk/src/java/org/apache/solr/util/DisMaxParams.java
    incubator/solr/trunk/src/java/org/apache/solr/util/SolrPluginUtils.java
    incubator/solr/trunk/src/test/org/apache/solr/BasicFunctionalityTest.java
    incubator/solr/trunk/src/test/org/apache/solr/DisMaxRequestHandlerTest.java

Modified: incubator/solr/trunk/CHANGES.txt
URL: http://svn.apache.org/viewvc/incubator/solr/trunk/CHANGES.txt?view=diff&rev=441175&r1=441174&r2=441175
==============================================================================
--- incubator/solr/trunk/CHANGES.txt (original)
+++ incubator/solr/trunk/CHANGES.txt Thu Sep  7 11:55:14 2006
@@ -46,6 +46,9 @@
     be compressed using the compress=true setting.  The field type also gains the
     ability to specify a size threshold at which field data is compressed.
     (klaas, SOLR-45)
+24. Simple faceted search support for fields (enumerating terms)
+    and arbitrary queries added to both StandardRequestHandler and
+    DisMaxRequestHandler. (hossman, SOLR-44)
 
 Changes in runtime behavior
  1. classes reorganized into different packages, package names changed to Apache
@@ -59,6 +62,8 @@
     using a '<lst name="defaults">...</lst>' init param, for backwards
     compatability all init prams will be used as defaults if an init param
     with that name does not exist. (hossman, SOLR-43)
+ 6. The DisMaxRequestHandler now supports multiple occurances of the "fq"
+    param. (hossman, SOLR-44)
 
 Optimizations 
  1. getDocListAndSet can now generate both a DocList and a DocSet from a 

Modified: incubator/solr/trunk/src/java/org/apache/solr/request/DisMaxRequestHandler.java
URL: http://svn.apache.org/viewvc/incubator/solr/trunk/src/java/org/apache/solr/request/DisMaxRequestHandler.java?view=diff&rev=441175&r1=441174&r2=441175
==============================================================================
--- incubator/solr/trunk/src/java/org/apache/solr/request/DisMaxRequestHandler.java (original)
+++ incubator/solr/trunk/src/java/org/apache/solr/request/DisMaxRequestHandler.java Thu Sep  7 11:55:14 2006
@@ -22,6 +22,8 @@
 
 import org.apache.solr.search.SolrIndexSearcher;
 import org.apache.solr.search.DocList;
+import org.apache.solr.search.DocSet;
+import org.apache.solr.search.DocListAndSet;
 import org.apache.solr.search.SolrQueryParser;
 import org.apache.solr.search.QueryParsing;
 
@@ -98,6 +100,8 @@
  * <li> fq - (Filter Query) a raw lucene query that can be used
  *           to restrict the super set of products we are interested in - more
  *           efficient then using bq, but doesn't influence score.
+ *           This param can be specified multiple times, and the filters
+ *           are addative.
  * </li>
  * </ul>
  *
@@ -113,7 +117,9 @@
  * </ul>
  *
  * <pre>
- * :TODO: make bf,fq,pf,qf multival params now that SolrParams supports them
+ * :TODO: document facet param support
+ *
+ * :TODO: make bf,pf,qf multival params now that SolrParams supports them
  * </pre>
  */
 public class DisMaxRequestHandler
@@ -310,41 +316,47 @@
             
       /* * * Restrict Results * * */
 
-      List<Query> restrictions = new ArrayList<Query>(1);
-            
-      /* User Restriction */
-      String filterQueryString = params.get(DMP.FQ);
-      Query filterQuery = null;
-      if (null != filterQueryString && !filterQueryString.equals("")) {
-        filterQuery = p.parse(filterQueryString);
-        restrictions.add(filterQuery);
-      }
+      List<Query> restrictions = U.parseFilterQueries(req);
             
       /* * * Generate Main Results * * */
 
       flags |= U.setReturnFields(req,rsp);
-      DocList results = s.getDocList(query, restrictions,
+      
+      DocListAndSet results = new DocListAndSet();
+      NamedList facetInfo = null;
+      if (params.getBool(FACET,false)) {
+        results = s.getDocListAndSet(query, restrictions,
                                      SolrPluginUtils.getSort(req),
                                      req.getStart(), req.getLimit(),
                                      flags);
-      rsp.add("search-results",results);
+        facetInfo = getFacetInfo(req, rsp, results.docSet);
+      } else {
+        results.docList = s.getDocList(query, restrictions,
+                                       SolrPluginUtils.getSort(req),
+                                       req.getStart(), req.getLimit(),
+                                       flags);
+      }
+      rsp.add("search-results",results.docList);
+      
+      if (null != facetInfo) rsp.add("facet_counts", facetInfo);
 
 
             
       /* * * Debugging Info * * */
 
       try {
-        NamedList debug = U.doStandardDebug(req, userQuery, query, results);
+        NamedList debug = U.doStandardDebug(req, userQuery, query, results.docList);
         if (null != debug) {
           debug.add("boostquery", boostQuery);
           debug.add("boostfunc", boostFunc);
-
-          debug.add("filterquery", filterQueryString);
-          if (null != filterQuery) {
-            debug.add("parsedfilterquery",
-                      QueryParsing.toString(filterQuery, schema));
+          if (null != restrictions) {
+            debug.add("filter_queries", params.getParams(FQ));
+            List<String> fqs = new ArrayList<String>(restrictions.size());
+            for (Query fq : restrictions) {
+              fqs.add(QueryParsing.toString(fq, req.getSchema()));
+            }
+            debug.add("parsed_filter_queries",fqs);
           }
-                    
           rsp.add("debug", debug);
         }
 
@@ -359,8 +371,10 @@
 
         BooleanQuery highlightQuery = new BooleanQuery();
         U.flattenBooleanQuery(highlightQuery, query);
-        NamedList sumData = HighlightingUtils.doHighlighting(results, highlightQuery, 
-                                                     req, queryFields.keySet().toArray(new String[0]));
+        String[] highFields = queryFields.keySet().toArray(new String[0]);
+        NamedList sumData =
+          HighlightingUtils.doHighlighting(results.docList, highlightQuery, 
+                                           req, highFields);
         if(sumData != null)
           rsp.add("highlighting", sumData);
       }
@@ -372,4 +386,22 @@
     }
   }
 
+  /**
+   * Fetches information about Facets for this request.
+   *
+   * Subclasses may with to override this method to provide more 
+   * advanced faceting behavior.
+   * @see SimpleFacets#getFacetCounts
+   */
+  protected NamedList getFacetInfo(SolrQueryRequest req, 
+                                   SolrQueryResponse rsp, 
+                                   DocSet mainSet) {
+
+    SimpleFacets f = new SimpleFacets(req.getSearcher(), 
+                                      mainSet, 
+                                      req.getParams());
+    return f.getFacetCounts();
+  }
+  
+  
 }

Added: incubator/solr/trunk/src/java/org/apache/solr/request/SimpleFacets.java
URL: http://svn.apache.org/viewvc/incubator/solr/trunk/src/java/org/apache/solr/request/SimpleFacets.java?view=auto&rev=441175
==============================================================================
--- incubator/solr/trunk/src/java/org/apache/solr/request/SimpleFacets.java (added)
+++ incubator/solr/trunk/src/java/org/apache/solr/request/SimpleFacets.java Thu Sep  7 11:55:14 2006
@@ -0,0 +1,245 @@
+/**
+ * Copyright 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.request;
+
+import org.apache.lucene.index.Term;
+import org.apache.lucene.index.TermEnum;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.queryParser.ParseException;
+import org.apache.lucene.queryParser.QueryParser;
+import org.apache.lucene.search.*;
+import org.apache.solr.core.SolrCore;
+import org.apache.solr.core.SolrException;
+import org.apache.solr.request.SolrParams;
+import org.apache.solr.request.SolrQueryRequest;
+import org.apache.solr.request.SolrQueryResponse;
+import org.apache.solr.request.DefaultSolrParams;
+import org.apache.solr.schema.IndexSchema;
+import org.apache.solr.schema.FieldType;
+import org.apache.solr.search.*;
+import org.apache.solr.util.NamedList;
+import org.apache.solr.util.BoundedTreeSet;
+
+import java.io.IOException;
+import java.util.*;
+import java.util.logging.Level;
+
+/**
+ * A class that generates simple Facet information for a request.
+ *
+ * More advanced facet implementations may compose or subclass this class 
+ * to leverage any of it's functionality.
+ */
+public class SimpleFacets {
+
+  /** The main set of documents all facet counts should be relative to */
+  protected DocSet docs;
+  /** Configuration params behavior should be driven by */
+  protected SolrParams params;
+  /** Searcher to use for all calculations */
+  protected SolrIndexSearcher searcher;
+  
+  public SimpleFacets(SolrIndexSearcher searcher, 
+                      DocSet docs, 
+                      SolrParams params) {
+    this.searcher = searcher;
+    this.docs = docs;
+    this.params = params;
+  }
+  
+  /**
+   * Looks at various Params to determing if any simple Facet Constraint count
+   * computations are desired.
+   *
+   * @see #getFacetQueryCounts
+   * @see #getFacetFieldCounts
+   * @see SolrParams#FACET
+   * @return a NamedList of Facet Count info or null
+   */
+  public NamedList getFacetCounts() {
+
+    // if someone called this method, benefit of the doubt: assume true
+    if (!params.getBool(params.FACET,true)) 
+      return null;
+
+    NamedList res = new NamedList();
+    try {
+
+      res.add("facet_queries", getFacetQueryCounts());
+
+      res.add("facet_fields", getFacetFieldCounts());
+      
+    } catch (Exception e) {
+      SolrException.logOnce(SolrCore.log, "Exception during facet counts", e);
+      res.add("exception", SolrException.toStr(e));
+    }
+    return res;
+  }
+
+  /**
+   * Returns a list of facet counts for each of the facet queries 
+   * specified in the params
+   *
+   * @see SolrParams#FACET_QUERY
+   */
+  public NamedList getFacetQueryCounts() throws IOException,ParseException {
+    
+    NamedList res = new NamedList();
+
+    /* Ignore SolrParams.DF - could have init param facet.query assuming
+     * the schema default with query param DF intented to only affect Q.
+     * If user doesn't want schema default for facet.query, they should be
+     * explicit.
+     */
+    SolrQueryParser qp = new SolrQueryParser(searcher.getSchema(),null);
+    
+    String[] facetQs = params.getParams(SolrParams.FACET_QUERY);
+    if (null != facetQs && 0 != facetQs.length) {
+      for (String q : facetQs) {
+        res.add(q, searcher.numDocs(qp.parse(q), docs));
+      }
+    }
+
+    return res;
+  }
+
+  /**
+   * Returns a list of value constraints and the associated facet counts 
+   * for each facet field specified in the params.
+   *
+   * @see SolrParams#FACET_FIELD
+   * @see #getFacetFieldMissingCount
+   * @see #getFacetTermEnumCounts
+   */
+  public NamedList getFacetFieldCounts() 
+    throws IOException {
+    
+    NamedList res = new NamedList();
+    String[] facetFs = params.getParams(SolrParams.FACET_FIELD);
+    if (null != facetFs && 0 != facetFs.length) {
+      
+      for (String f : facetFs) {
+
+        NamedList counts = getFacetTermEnumCounts(f);
+        
+        if (params.getFieldBool(f, params.FACET_MISSING, false))
+          counts.add(null, getFacetFieldMissingCount(f));
+        
+        res.add(f, counts);
+        
+      }
+    }
+    return res;
+  }
+
+  /**
+   * Returns a count of the documents in the set which do not have any 
+   * terms for for the specified field.
+   *
+   * @see SolrParams#FACET_MISSING
+   */
+  public int getFacetFieldMissingCount(String fieldName)
+    throws IOException {
+
+    DocSet hasVal = searcher.getDocSet
+      (new ConstantScoreRangeQuery(fieldName, null, null, false, false));
+    return docs.andNotSize(hasVal);
+  }
+
+  /**
+   * Returns a list of terms in the specified field along with the 
+   * corrisponding count of documents in the set that match that constraint.
+   *
+   * @see SolrParams#FACET_LIMIT
+   * @see SolrParams#FACET_ZEROS
+   */
+  public NamedList getFacetTermEnumCounts(String fieldName) 
+    throws IOException {
+    
+    /* :TODO: potential optimization...
+     * cache the Terms with the highest docFreq and try them first
+     * don't enum if we get our max from them
+     */
+     
+    IndexSchema schema = searcher.getSchema();
+    IndexReader r = searcher.getReader();
+    FieldType ft = schema.getFieldType(fieldName);
+
+    Set<CountPair<String,Integer>> counts 
+      = new HashSet<CountPair<String,Integer>>();
+
+    String limit = params.getFieldParam(fieldName, params.FACET_LIMIT);
+    if (null != limit) {
+      counts = new BoundedTreeSet<CountPair<String,Integer>>
+        (Integer.parseInt(limit));
+    }
+
+    boolean zeros = params.getFieldBool(fieldName, params.FACET_ZEROS, true);
+      
+    TermEnum te = r.terms(new Term(fieldName,""));
+    do {
+      Term t = te.term();
+
+      if (null == t || ! t.field().equals(fieldName)) 
+        break;
+
+      if (0 < te.docFreq()) { /* all docs may be deleted */
+        int count = searcher.numDocs(new TermQuery(t),
+                                     docs);
+
+        /* :TODO: is indexedToReadable correct? */ 
+        if (zeros || 0 < count) 
+          counts.add(new CountPair<String,Integer>
+                     (ft.indexedToReadable(t.text()), count));
+
+      }
+    } while (te.next());
+
+    NamedList res = new NamedList();
+    for (CountPair<String,Integer> p : counts) {
+      res.add(p.key, p.val);
+    }
+    return res;
+  }
+
+  /**
+   * A simple key=>val pair whose natural order is such that 
+   * <b>higher</b> vals come before lower vals.
+   * In case of tie vals, then <b>lower</b> keys come before higher keys.
+   */
+  public static class CountPair<K extends Comparable<? super K>, V extends Comparable<? super V>> 
+    implements Comparable<CountPair<K,V>> {
+
+    public CountPair(K k, V v) {
+      key = k; val = v;
+    }
+    public K key;
+    public V val;
+    public int hashCode() {
+      return key.hashCode() ^ val.hashCode();
+    }
+    public boolean equals(Object o) {
+      return (o instanceof CountPair) 
+        && (0 == this.compareTo((CountPair<K,V>) o));
+    }
+    public int compareTo(CountPair<K,V> o) {
+      int vc = o.val.compareTo(val);
+      return (0 != vc ? vc : key.compareTo(o.key));
+    }
+  }
+}
+

Modified: incubator/solr/trunk/src/java/org/apache/solr/request/SolrParams.java
URL: http://svn.apache.org/viewvc/incubator/solr/trunk/src/java/org/apache/solr/request/SolrParams.java?view=diff&rev=441175&r1=441174&r2=441175
==============================================================================
--- incubator/solr/trunk/src/java/org/apache/solr/request/SolrParams.java (original)
+++ incubator/solr/trunk/src/java/org/apache/solr/request/SolrParams.java Thu Sep  7 11:55:14 2006
@@ -37,6 +37,8 @@
   public static final String WT ="wt";
   /** query string */
   public static final String Q ="q";
+  /** Lucene query string(s) for filtering the results without affecting scoring */
+  public static final String FQ ="fq";
   /** zero based offset of matching documents to retrieve */
   public static final String START ="start";
   /** number of documents to return starting at "start" */
@@ -62,7 +64,38 @@
   /** override default highlight Formatter class */
   public static final String HIGHLIGHT_FORMATTER_CLASS = "highlightFormatterClass";
 
-
+  /**
+   * Should facet counts be calculated?
+   */
+  public static final String FACET = "facet";
+  
+  /**
+   * Any lucene formated queries the user would like to use for
+   * Facet Contraint Counts (multi-value)
+   */
+  public static final String FACET_QUERY = "facet.query";
+  /**
+   * Any field whose terms the user wants to enumerate over for
+   * Facet Contraint Counts (multi-value)
+   */
+  public static final String FACET_FIELD = "facet.field";
+  /**
+   * Numeric option indicating the maximum number of facet field counts
+   * be included in the response for each field - in descending order of count.
+   * Can be overriden on a per field basis.
+   */
+  public static final String FACET_LIMIT = "facet.limit";
+  /**
+   * Boolean option indicating whether facet field counts of "0" should 
+   * be included in the response.  Can be overriden on a per field basis.
+   */
+  public static final String FACET_ZEROS = "facet.zeros";
+  /**
+   * Boolean option indicating whether the response should include a 
+   * facet field count for all records which have no value for the 
+   * facet field. Can be overriden on a per field basis.
+   */
+  public static final String FACET_MISSING = "facet.missing";
 
 
   /** returns the String value of a param, or null if not set */

Modified: incubator/solr/trunk/src/java/org/apache/solr/request/StandardRequestHandler.java
URL: http://svn.apache.org/viewvc/incubator/solr/trunk/src/java/org/apache/solr/request/StandardRequestHandler.java?view=diff&rev=441175&r1=441174&r2=441175
==============================================================================
--- incubator/solr/trunk/src/java/org/apache/solr/request/StandardRequestHandler.java (original)
+++ incubator/solr/trunk/src/java/org/apache/solr/request/StandardRequestHandler.java Thu Sep  7 11:55:14 2006
@@ -18,6 +18,7 @@
 
 import org.apache.lucene.search.*;
 
+import java.util.ArrayList;
 import java.util.List;
 import java.net.URL;
 
@@ -107,20 +108,46 @@
         }
       }
 
-      DocList results = req.getSearcher().getDocList(query, null, sort, p.getInt(START,0), p.getInt(ROWS,10), flags);
-      rsp.add(null,results);
+      DocListAndSet results = new DocListAndSet();
+      NamedList facetInfo = null;
+      List<Query> filters = U.parseFilterQueries(req);
+      SolrIndexSearcher s = req.getSearcher();
+
+      if (p.getBool(FACET,false)) {
+        results = s.getDocListAndSet(query, filters, sort,
+                                     p.getInt(START,0), p.getInt(ROWS,10),
+                                     flags);
+        facetInfo = getFacetInfo(req, rsp, results.docSet);
+      } else {
+        results.docList = s.getDocList(query, filters, sort,
+                                       p.getInt(START,0), p.getInt(ROWS,10),
+                                       flags);
+      }
+      
+      rsp.add(null,results.docList);
+
+      if (null != facetInfo) rsp.add("facet_counts", facetInfo);
 
       try {
-        NamedList dbg = U.doStandardDebug(req, qs, query, results);
-        if (null != dbg) 
+        NamedList dbg = U.doStandardDebug(req, qs, query, results.docList);
+        if (null != dbg) {
+          if (null != filters) {
+            dbg.add("filter_queries",req.getParams().getParams(FQ));
+            List<String> fqs = new ArrayList<String>(filters.size());
+            for (Query fq : filters) {
+              fqs.add(QueryParsing.toString(fq, req.getSchema()));
+            }
+            dbg.add("parsed_filter_queries",fqs);
+          }
           rsp.add("debug", dbg);
+        }
       } catch (Exception e) {
         SolrException.logOnce(SolrCore.log, "Exception durring debug", e);
         rsp.add("exception_during_debug", SolrException.toStr(e));
       }
 
       NamedList sumData = HighlightingUtils.doHighlighting(
-        results, query, req, new String[]{defaultField});
+        results.docList, query, req, new String[]{defaultField});
       if(sumData != null)
         rsp.add("highlighting", sumData);
 
@@ -135,6 +162,25 @@
       return;
     }
   }
+
+  /**
+   * Fetches information about Facets for this request.
+   *
+   * Subclasses may with to override this method to provide more 
+   * advanced faceting behavior.
+   * @see SimpleFacets#getFacetCounts
+   */
+  protected NamedList getFacetInfo(SolrQueryRequest req, 
+                                   SolrQueryResponse rsp, 
+                                   DocSet mainSet) {
+
+    SimpleFacets f = new SimpleFacets(req.getSearcher(), 
+                                      mainSet, 
+                                      req.getParams());
+    return f.getFacetCounts();
+  }
+
+
 
   //////////////////////// SolrInfoMBeans methods //////////////////////
 

Added: incubator/solr/trunk/src/java/org/apache/solr/util/BoundedTreeSet.java
URL: http://svn.apache.org/viewvc/incubator/solr/trunk/src/java/org/apache/solr/util/BoundedTreeSet.java?view=auto&rev=441175
==============================================================================
--- incubator/solr/trunk/src/java/org/apache/solr/util/BoundedTreeSet.java (added)
+++ incubator/solr/trunk/src/java/org/apache/solr/util/BoundedTreeSet.java Thu Sep  7 11:55:14 2006
@@ -0,0 +1,67 @@
+/**
+ * Copyright 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.solr.util;
+
+import java.util.*;
+
+/**
+ * A TreeSet that ensures it never grows beyond a max size.  
+ * <code>last()</code> is removed if the <code>size()</code> 
+ * get's bigger then <code>getMaxSize()</code>
+ */
+public class BoundedTreeSet<E> extends TreeSet<E> {
+  private int maxSize = Integer.MAX_VALUE;
+  public BoundedTreeSet(int maxSize) {
+    super();
+    this.setMaxSize(maxSize);
+  }
+  public BoundedTreeSet(int maxSize, Collection<? extends E> c) {
+    super(c);
+    this.setMaxSize(maxSize);
+  }
+  public BoundedTreeSet(int maxSize, Comparator<? super E> c) {
+    super(c);
+    this.setMaxSize(maxSize);
+  }
+  public BoundedTreeSet(int maxSize, SortedSet<E> s) {
+    super(s);
+    this.setMaxSize(maxSize);
+  }
+  public int getMaxSize() {
+    return maxSize;
+  }
+  public void setMaxSize(int max) {
+    maxSize = max;
+    adjust();
+  }
+  private void adjust() {
+    while (maxSize < size()) {
+      remove(last());
+    }
+  }
+  public boolean add(E item) {
+    boolean out = super.add(item);
+    adjust();
+    return out;
+  }
+  public boolean addAll(Collection<? extends E> c) {
+    boolean out = super.addAll(c);
+    adjust();
+    return out;
+  }
+}

Modified: incubator/solr/trunk/src/java/org/apache/solr/util/DisMaxParams.java
URL: http://svn.apache.org/viewvc/incubator/solr/trunk/src/java/org/apache/solr/util/DisMaxParams.java?view=diff&rev=441175&r1=441174&r2=441175
==============================================================================
--- incubator/solr/trunk/src/java/org/apache/solr/util/DisMaxParams.java (original)
+++ incubator/solr/trunk/src/java/org/apache/solr/util/DisMaxParams.java Thu Sep  7 11:55:14 2006
@@ -60,7 +60,9 @@
     public static String BQ = "bq";
     /** query and init param for boosting functions */
     public static String BF = "bf";
-    /** query and init param for filtering query */
+    /** query and init param for filtering query
+     * @deprecated use SolrParams.FQ or SolrPluginUtils.parseFilterQueries
+     */
     public static String FQ = "fq";
     /** query and init param for field list */
     public static String GEN = "gen";

Modified: incubator/solr/trunk/src/java/org/apache/solr/util/SolrPluginUtils.java
URL: http://svn.apache.org/viewvc/incubator/solr/trunk/src/java/org/apache/solr/util/SolrPluginUtils.java?view=diff&rev=441175&r1=441174&r2=441175
==============================================================================
--- incubator/solr/trunk/src/java/org/apache/solr/util/SolrPluginUtils.java (original)
+++ incubator/solr/trunk/src/java/org/apache/solr/util/SolrPluginUtils.java Thu Sep  7 11:55:14 2006
@@ -719,6 +719,30 @@
     return ss.getSort();
   }
 
+  /**
+   * Builds a list of Query objects that should be used to filter results
+   * @see SolrParams#FQ
+   * @return null if no filter queries
+   */
+  public static List<Query> parseFilterQueries(SolrQueryRequest req) throws ParseException {
+    String[] in = req.getParams().getParams(SolrParams.FQ);
+    
+    if (null == in || 0 == in.length) return null;
+
+    List<Query> out = new LinkedList<Query>();
+    SolrIndexSearcher s = req.getSearcher();
+    /* Ignore SolrParams.DF - could have init param FQs assuming the
+     * schema default with query param DF intented to only affect Q.
+     * If user doesn't want schema default, they should be explicit in the FQ.
+     */
+    SolrQueryParser qp = new SolrQueryParser(s.getSchema(), null);
+    for (String q : in) {
+      if (null != q && 0 != q.trim().length()) {
+        out.add(qp.parse(q));
+      }
+    }
+    return out;
+  }
 
   /**
    * A CacheRegenerator that can be used whenever the items in the cache

Modified: incubator/solr/trunk/src/test/org/apache/solr/BasicFunctionalityTest.java
URL: http://svn.apache.org/viewvc/incubator/solr/trunk/src/test/org/apache/solr/BasicFunctionalityTest.java?view=diff&rev=441175&r1=441174&r2=441175
==============================================================================
--- incubator/solr/trunk/src/test/org/apache/solr/BasicFunctionalityTest.java (original)
+++ incubator/solr/trunk/src/test/org/apache/solr/BasicFunctionalityTest.java Thu Sep  7 11:55:14 2006
@@ -167,7 +167,7 @@
 
     DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
     builder.parse(new ByteArrayInputStream
-                             (writer.toString().getBytes("UTF-8")));
+                  (writer.toString().getBytes("UTF-8")));
   }
 
   public void testLocalSolrQueryRequestParams() {
@@ -319,7 +319,173 @@
 
   }
       
+  public void testSimpleFacetCounts() {
+    assertU(adoc("id", "42", "trait_s", "Tool", "trait_s", "Obnoxious",
+                 "name", "Zapp Brannigan"));
+    assertU(adoc("id", "43" ,
+                 "title", "Democratic Order of Planets"));
+    assertU(adoc("id", "44", "trait_s", "Tool",
+                 "name", "The Zapper"));
+    assertU(adoc("id", "45", "trait_s", "Chauvinist",
+                 "title", "25 star General"));
+    assertU(adoc("id", "46", "trait_s", "Obnoxious",
+                 "subject", "Defeated the pacifists of the Gandhi nebula"));
+    assertU(adoc("id", "47", "trait_s", "Pig",
+                 "text", "line up and fly directly at the enemy death cannons, clogging them with wreckage!"));
+    assertU(commit());
+ 
+    assertQ("standard request handler returns all matches",
+            req("id:[42 TO 47]"),
+            "*[count(//doc)=6]"
+            );
+ 
+    assertQ("filter results using fq",
+            req("q","id:[42 TO 46]",
+                "fq", "id:[43 TO 47]"),
+            "*[count(//doc)=4]"
+            );
+    
+    assertQ("don't filter results using blank fq",
+            req("q","id:[42 TO 46]",
+                "fq", " "),
+            "*[count(//doc)=5]"
+            );
+     
+    assertQ("filter results using multiple fq params",
+            req("q","id:[42 TO 46]",
+                "fq", "trait_s:Obnoxious",
+                "fq", "id:[43 TO 47]"),
+            "*[count(//doc)=1]"
+            );
+ 
+    assertQ("check counts for facet queries",
+            req("q", "id:[42 TO 47]"
+                ,"facet", "true"
+                ,"facet.query", "trait_s:Obnoxious"
+                ,"facet.query", "id:[42 TO 45]"
+                ,"facet.query", "id:[43 TO 47]"
+                ,"facet.field", "trait_s"
+                )
+            ,"*[count(//doc)=6]"
+ 
+            ,"//lst[@name='facet_counts']/lst[@name='facet_queries']"
+            ,"//lst[@name='facet_queries']/int[@name='trait_s:Obnoxious'][.='2']"
+            ,"//lst[@name='facet_queries']/int[@name='id:[42 TO 45]'][.='4']"
+            ,"//lst[@name='facet_queries']/int[@name='id:[43 TO 47]'][.='5']"
+ 
+            ,"//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"
+                ,"fq", "id:[42 TO 45]"
+                ,"facet.field", "trait_s"
+                ,"facet.query", "id:[42 TO 45]"
+                ,"facet.query", "id:[43 TO 47]"
+                )
+            ,"*[count(//doc)=4]"
+            ,"//lst[@name='facet_counts']/lst[@name='facet_queries']"
+            ,"//lst[@name='facet_queries']/int[@name='id:[42 TO 45]'][.='4']"
+            ,"//lst[@name='facet_queries']/int[@name='id:[43 TO 47]'][.='3']"
+            ,"*[count(//lst[@name='trait_s']/int)=4]"
+            ,"//lst[@name='trait_s']/int[@name='Tool'][.='2']"
+            ,"//lst[@name='trait_s']/int[@name='Obnoxious'][.='1']"
+            ,"//lst[@name='trait_s']/int[@name='Chauvinist'][.='1']"
+            ,"//lst[@name='trait_s']/int[@name='Pig'][.='0']"
+            );
+ 
+    assertQ("check counts with facet.zero=false&facet.missing=true using fq",
+            req("q", "id:[42 TO 47]"
+                ,"facet", "true"
+                ,"facet.zeros", "false"
+                ,"f.trait_s.facet.missing", "true"
+                ,"fq", "id:[42 TO 45]"
+                ,"facet.field", "trait_s"
+                )
+            ,"*[count(//doc)=4]"
+            ,"*[count(//lst[@name='trait_s']/int)=4]"
+            ,"//lst[@name='trait_s']/int[@name='Tool'][.='2']"
+            ,"//lst[@name='trait_s']/int[@name='Obnoxious'][.='1']"
+            ,"//lst[@name='trait_s']/int[@name='Chauvinist'][.='1']"
+            ,"//lst[@name='trait_s']/int[not(@name)][.='1']"
+            );
+ 
+  }
+ 
+  public void testSimpleFacetCountsWithLimits() {
+    assertU(adoc("id", "1",  "t_s", "A"));
+    assertU(adoc("id", "2",  "t_s", "B"));
+    assertU(adoc("id", "3",  "t_s", "C"));
+    assertU(adoc("id", "4",  "t_s", "C"));
+    assertU(adoc("id", "5",  "t_s", "D"));
+    assertU(adoc("id", "6",  "t_s", "E"));
+    assertU(adoc("id", "7",  "t_s", "E"));
+    assertU(adoc("id", "8",  "t_s", "E"));
+    assertU(adoc("id", "9",  "t_s", "F"));
+    assertU(adoc("id", "10", "t_s", "G"));
+    assertU(adoc("id", "11", "t_s", "G"));
+    assertU(adoc("id", "12", "t_s", "G"));
+    assertU(adoc("id", "13", "t_s", "G"));
+    assertU(adoc("id", "14", "t_s", "G"));
+    assertU(commit());
+ 
+    assertQ("check counts for unlimited facet",
+            req("q", "id:[* TO *]"
+                ,"facet", "true"
+                ,"facet.field", "t_s"
+                )
+            ,"*[count(//lst[@name='facet_fields']/lst[@name='t_s']/int)=7]"
+ 
+            ,"//lst[@name='t_s']/int[@name='G'][.='5']"
+            ,"//lst[@name='t_s']/int[@name='E'][.='3']"
+            ,"//lst[@name='t_s']/int[@name='C'][.='2']"
+ 
+            ,"//lst[@name='t_s']/int[@name='A'][.='1']"
+            ,"//lst[@name='t_s']/int[@name='B'][.='1']"
+            ,"//lst[@name='t_s']/int[@name='D'][.='1']"
+            ,"//lst[@name='t_s']/int[@name='F'][.='1']"
+            );
+ 
+    assertQ("check counts for facet with generous limit",
+            req("q", "id:[* TO *]"
+                ,"facet", "true"
+                ,"facet.limit", "100"
+                ,"facet.field", "t_s"
+                )
+            ,"*[count(//lst[@name='facet_fields']/lst[@name='t_s']/int)=7]"
+ 
+            ,"//lst[@name='t_s']/int[1][@name='G'][.='5']"
+            ,"//lst[@name='t_s']/int[2][@name='E'][.='3']"
+            ,"//lst[@name='t_s']/int[3][@name='C'][.='2']"
+ 
+            ,"//lst[@name='t_s']/int[@name='A'][.='1']"
+            ,"//lst[@name='t_s']/int[@name='B'][.='1']"
+            ,"//lst[@name='t_s']/int[@name='D'][.='1']"
+            ,"//lst[@name='t_s']/int[@name='F'][.='1']"
+            );
+ 
+    assertQ("check counts for limited facet",
+            req("q", "id:[* TO *]"
+                ,"facet", "true"
+                ,"facet.limit", "2"
+                ,"facet.field", "t_s"
+                )
+            ,"*[count(//lst[@name='facet_fields']/lst[@name='t_s']/int)=2]"
+ 
+            ,"//lst[@name='t_s']/int[1][@name='G'][.='5']"
+            ,"//lst[@name='t_s']/int[2][@name='E'][.='3']"
+            );
+ 
+  }
+  
 
+  
   private String mkstr(int len) {
     StringBuilder sb = new StringBuilder(len);
     for (int i = 0; i < len; i++) {

Modified: incubator/solr/trunk/src/test/org/apache/solr/DisMaxRequestHandlerTest.java
URL: http://svn.apache.org/viewvc/incubator/solr/trunk/src/test/org/apache/solr/DisMaxRequestHandlerTest.java?view=diff&rev=441175&r1=441174&r2=441175
==============================================================================
--- incubator/solr/trunk/src/test/org/apache/solr/DisMaxRequestHandlerTest.java (original)
+++ incubator/solr/trunk/src/test/org/apache/solr/DisMaxRequestHandlerTest.java Thu Sep  7 11:55:14 2006
@@ -39,31 +39,39 @@
   public void setUp() throws Exception {
     super.setUp();
     lrf = h.getRequestFactory
-      ("dismax",0,20,"version","2.0");
+      ("dismax", 0, 20,
+       "version","2.0",
+       "facet", "true",
+       "facet.field","t_s"
+       );
   }
   public void testSomeStuff() throws Exception {
 
     assertU(adoc("id", "666",
                  "features_t", "cool and scary stuff",
                  "subject", "traveling in hell",
+                 "t_s", "movie",
                  "title", "The Omen",
                  "weight", "87.9",
                  "iind", "666"));
     assertU(adoc("id", "42",
                  "features_t", "cool stuff",
                  "subject", "traveling the galaxy",
+                 "t_s", "movie", "t_s", "book",
                  "title", "Hitch Hiker's Guide to the Galaxy",
                  "weight", "99.45",
                  "iind", "42"));
     assertU(adoc("id", "1",
                  "features_t", "nothing",
                  "subject", "garbage",
+                 "t_s", "book",
                  "title", "Most Boring Guide Ever",
                  "weight", "77",
                  "iind", "4"));
     assertU(adoc("id", "8675309",
                  "features_t", "Wikedly memorable chorus and stuff",
                  "subject", "One Cool Hot Chick",
+                 "t_s", "song",
                  "title", "Jenny",
                  "weight", "97.3",
                  "iind", "8675309"));
@@ -72,6 +80,10 @@
     assertQ("basic match",
             req("guide")
             ,"//*[@numFound='2']"
+            ,"//lst[@name='facet_fields']/lst[@name='t_s']"
+            ,"*[count(//lst[@name='t_s']/int)=3]"
+            ,"//lst[@name='t_s']/int[@name='book'][.='2']"
+            ,"//lst[@name='t_s']/int[@name='movie'][.='1']"
             );
     
     assertQ("basic cross field matching, boost on same field matching",
@@ -94,12 +106,17 @@
             ,"//*[@numFound='3']"
             );
 
+
   }
 
   public void testOldStyleDefaults() throws Exception {
 
     lrf = h.getRequestFactory
-      ("dismaxOldStyleDefaults",0,20,"version","2.0");
+      ("dismax", 0, 20,
+       "version","2.0",
+       "facet", "true",
+       "facet.field","t_s"
+       );
     testSomeStuff();
   }