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"