SOLR-911: add filter tags, facet local params, with excludes, output keys, and more efficient facet refinement requests

git-svn-id: https://svn.apache.org/repos/asf/lucene/solr/trunk@727560 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Yonik Seeley 2008-12-17 22:41:35 +00:00
parent c11997b456
commit aa9b5753ad
7 changed files with 342 additions and 102 deletions

View File

@ -112,6 +112,13 @@ New Features
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

View File

@ -115,5 +115,14 @@ public interface CommonParams {
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();
}

View File

@ -27,10 +27,10 @@ import org.apache.solr.common.params.SolrParams;
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 @@ public class FacetComponent extends SearchComponent
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 @@ public class FacetComponent extends SearchComponent
// 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;
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 @@ public class FacetComponent extends SearchComponent
// 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 @@ public class FacetComponent extends SearchComponent
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 @@ public class FacetComponent extends SearchComponent
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 @@ public class FacetComponent extends SearchComponent
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 @@ public class FacetComponent extends SearchComponent
// 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 @@ public class FacetComponent extends SearchComponent
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 @@ public class FacetComponent extends SearchComponent
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");
NamedList facet_fields = (NamedList)facet_counts.get("facet_fields");
// 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();
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;
// 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 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 @@ public class FacetComponent extends SearchComponent
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 @@ public class FacetComponent extends SearchComponent
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 @@ public class FacetComponent extends SearchComponent
}
}
// TODO: list facets (sorted by natural order)
// TODO: facet dates
facet_counts.add("facet_dates", new SimpleOrderedMap());
@ -433,36 +443,76 @@ public class FacetComponent extends SearchComponent
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
class FieldFacet {
String field;
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 extends FacetBase {
String field; // the field to facet on... "myfield" for {!key=foo}myfield
int offset;
int limit;
int minCount;
@ -471,7 +521,12 @@ class FieldFacet {
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 FieldFacet {
}
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 @@ class DistribFieldFacet extends FieldFacet {
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 @@ class DistribFieldFacet extends FieldFacet {
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 @@ class DistribFieldFacet extends FieldFacet {
// 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
}
}

View File

@ -27,9 +27,11 @@ import org.apache.solr.common.SolrException;
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.schema.DateField;
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 @@ public class SimpleFacets {
/** 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 @@ public class SimpleFacets {
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 @@ public class SimpleFacets {
// 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 @@ public class SimpleFacets {
* @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 @@ public class SimpleFacets {
* @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 @@ public class SimpleFacets {
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 @@ public class SimpleFacets {
boolean iLow, boolean iHigh) throws IOException {
return searcher.numDocs(new ConstantScoreRangeQuery(field,low,high,
iLow,iHigh),
docs);
base);
}
/**

View File

@ -22,8 +22,11 @@ import org.apache.lucene.search.Sort;
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 @@ public abstract class QParser {
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()
**/

View File

@ -548,6 +548,15 @@ public class TestDistributedSearch extends TestCase {
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

View File

@ -86,7 +86,31 @@ public class SimpleFacetsTest extends AbstractSolrTestCase {
,"//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"