From 447ddf06eabff99123a9e800353ae310c1db338d Mon Sep 17 00:00:00 2001 From: "Chris M. Hostetter" Date: Thu, 7 Sep 2006 18:55:14 +0000 Subject: [PATCH] SOLR-44 - simple facet support for StandardRequestHandler and DisMaxRequestHandler git-svn-id: https://svn.apache.org/repos/asf/incubator/solr/trunk@441175 13f79535-47bb-0310-9956-ffa450edef68 --- CHANGES.txt | 5 + .../solr/request/DisMaxRequestHandler.java | 74 ++++-- .../org/apache/solr/request/SimpleFacets.java | 245 ++++++++++++++++++ .../org/apache/solr/request/SolrParams.java | 35 ++- .../solr/request/StandardRequestHandler.java | 56 +++- .../org/apache/solr/util/BoundedTreeSet.java | 67 +++++ .../org/apache/solr/util/DisMaxParams.java | 4 +- .../org/apache/solr/util/SolrPluginUtils.java | 24 ++ .../apache/solr/BasicFunctionalityTest.java | 168 +++++++++++- .../apache/solr/DisMaxRequestHandlerTest.java | 21 +- 10 files changed, 668 insertions(+), 31 deletions(-) create mode 100644 src/java/org/apache/solr/request/SimpleFacets.java create mode 100644 src/java/org/apache/solr/util/BoundedTreeSet.java diff --git a/CHANGES.txt b/CHANGES.txt index 87e21d6236d..31ce1f5a339 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -46,6 +46,9 @@ New Features 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 @@ Changes in runtime behavior using a '...' 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 diff --git a/src/java/org/apache/solr/request/DisMaxRequestHandler.java b/src/java/org/apache/solr/request/DisMaxRequestHandler.java index da4868b3744..abee8596e50 100644 --- a/src/java/org/apache/solr/request/DisMaxRequestHandler.java +++ b/src/java/org/apache/solr/request/DisMaxRequestHandler.java @@ -22,6 +22,8 @@ import org.apache.solr.core.SolrException; 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 @@ import java.net.URL; *
  • 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. *
  • * * @@ -113,7 +117,9 @@ import java.net.URL; * * *
    - * :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
      * 
    */ public class DisMaxRequestHandler @@ -310,41 +316,47 @@ public class DisMaxRequestHandler /* * * Restrict Results * * */ - List restrictions = new ArrayList(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 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 fqs = new ArrayList(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 @@ public class DisMaxRequestHandler 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 @@ public class DisMaxRequestHandler } } + /** + * 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(); + } + + } diff --git a/src/java/org/apache/solr/request/SimpleFacets.java b/src/java/org/apache/solr/request/SimpleFacets.java new file mode 100644 index 00000000000..789b6a2e416 --- /dev/null +++ b/src/java/org/apache/solr/request/SimpleFacets.java @@ -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> counts + = new HashSet>(); + + String limit = params.getFieldParam(fieldName, params.FACET_LIMIT); + if (null != limit) { + counts = new BoundedTreeSet> + (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 + (ft.indexedToReadable(t.text()), count)); + + } + } while (te.next()); + + NamedList res = new NamedList(); + for (CountPair p : counts) { + res.add(p.key, p.val); + } + return res; + } + + /** + * A simple key=>val pair whose natural order is such that + * higher vals come before lower vals. + * In case of tie vals, then lower keys come before higher keys. + */ + public static class CountPair, V extends Comparable> + implements Comparable> { + + 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) o)); + } + public int compareTo(CountPair o) { + int vc = o.val.compareTo(val); + return (0 != vc ? vc : key.compareTo(o.key)); + } + } +} + diff --git a/src/java/org/apache/solr/request/SolrParams.java b/src/java/org/apache/solr/request/SolrParams.java index 4b923b3a4c0..ceaab9ccada 100644 --- a/src/java/org/apache/solr/request/SolrParams.java +++ b/src/java/org/apache/solr/request/SolrParams.java @@ -37,6 +37,8 @@ public abstract class SolrParams { 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 @@ public abstract class SolrParams { /** 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 */ diff --git a/src/java/org/apache/solr/request/StandardRequestHandler.java b/src/java/org/apache/solr/request/StandardRequestHandler.java index 8dcec977655..b2a57fdbc9a 100644 --- a/src/java/org/apache/solr/request/StandardRequestHandler.java +++ b/src/java/org/apache/solr/request/StandardRequestHandler.java @@ -18,6 +18,7 @@ package org.apache.solr.request; import org.apache.lucene.search.*; +import java.util.ArrayList; import java.util.List; import java.net.URL; @@ -107,20 +108,46 @@ public class StandardRequestHandler implements SolrRequestHandler, SolrInfoMBean } } - 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 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 fqs = new ArrayList(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); @@ -136,6 +163,25 @@ public class StandardRequestHandler implements SolrRequestHandler, SolrInfoMBean } } + /** + * 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 ////////////////////// diff --git a/src/java/org/apache/solr/util/BoundedTreeSet.java b/src/java/org/apache/solr/util/BoundedTreeSet.java new file mode 100644 index 00000000000..365517cd4e9 --- /dev/null +++ b/src/java/org/apache/solr/util/BoundedTreeSet.java @@ -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. + * last() is removed if the size() + * get's bigger then getMaxSize() + */ +public class BoundedTreeSet extends TreeSet { + private int maxSize = Integer.MAX_VALUE; + public BoundedTreeSet(int maxSize) { + super(); + this.setMaxSize(maxSize); + } + public BoundedTreeSet(int maxSize, Collection c) { + super(c); + this.setMaxSize(maxSize); + } + public BoundedTreeSet(int maxSize, Comparator c) { + super(c); + this.setMaxSize(maxSize); + } + public BoundedTreeSet(int maxSize, SortedSet 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 c) { + boolean out = super.addAll(c); + adjust(); + return out; + } +} diff --git a/src/java/org/apache/solr/util/DisMaxParams.java b/src/java/org/apache/solr/util/DisMaxParams.java index 0b45b8cbdc2..756e0f08c03 100755 --- a/src/java/org/apache/solr/util/DisMaxParams.java +++ b/src/java/org/apache/solr/util/DisMaxParams.java @@ -60,7 +60,9 @@ import java.io.IOException; 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"; diff --git a/src/java/org/apache/solr/util/SolrPluginUtils.java b/src/java/org/apache/solr/util/SolrPluginUtils.java index 13c67b24348..cfd6e93231f 100644 --- a/src/java/org/apache/solr/util/SolrPluginUtils.java +++ b/src/java/org/apache/solr/util/SolrPluginUtils.java @@ -719,6 +719,30 @@ public class SolrPluginUtils { 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 parseFilterQueries(SolrQueryRequest req) throws ParseException { + String[] in = req.getParams().getParams(SolrParams.FQ); + + if (null == in || 0 == in.length) return null; + + List out = new LinkedList(); + 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 diff --git a/src/test/org/apache/solr/BasicFunctionalityTest.java b/src/test/org/apache/solr/BasicFunctionalityTest.java index 48c727b4306..08a1a37f511 100644 --- a/src/test/org/apache/solr/BasicFunctionalityTest.java +++ b/src/test/org/apache/solr/BasicFunctionalityTest.java @@ -167,7 +167,7 @@ public class BasicFunctionalityTest extends AbstractSolrTestCase { 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 class BasicFunctionalityTest extends AbstractSolrTestCase { } + 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++) { diff --git a/src/test/org/apache/solr/DisMaxRequestHandlerTest.java b/src/test/org/apache/solr/DisMaxRequestHandlerTest.java index 74665104b44..1c5312f10cc 100644 --- a/src/test/org/apache/solr/DisMaxRequestHandlerTest.java +++ b/src/test/org/apache/solr/DisMaxRequestHandlerTest.java @@ -39,31 +39,39 @@ public class DisMaxRequestHandlerTest extends AbstractSolrTestCase { 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 @@ public class DisMaxRequestHandlerTest extends AbstractSolrTestCase { 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 @@ public class DisMaxRequestHandlerTest extends AbstractSolrTestCase { ,"//*[@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(); }