SOLR-2255 local-param support for pivot faceting

git-svn-id: https://svn.apache.org/repos/asf/lucene/dev/trunk@1389680 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
David Wayne Smiley 2012-09-25 01:56:33 +00:00
parent 11b4d43627
commit 15076ecf28
5 changed files with 100 additions and 64 deletions

View File

@ -26,7 +26,11 @@ $Id$
================== 4.1.0 ==================
(No changes)
* SOLR-2255: Enhanced pivot faceting to use local-params in the same way that
regular field value faceting can. This means support for excluding a filter
query, using a different output key, and specifying 'threads' to do
facet.method=fcs concurrently. PivotFacetHelper now extends SimpleFacet and
the getFacetImplementation() extension hook was removed. (dsmiley)
================== 4.0.0 ==================

View File

@ -53,14 +53,6 @@ public class FacetComponent extends SearchComponent
static final String PIVOT_KEY = "facet_pivot";
PivotFacetHelper pivotHelper;
@Override
public void init( NamedList args )
{
pivotHelper = new PivotFacetHelper(); // Maybe this would configurable?
}
@Override
public void prepare(ResponseBuilder rb) throws IOException
{
@ -86,7 +78,11 @@ public class FacetComponent extends SearchComponent
NamedList<Object> counts = f.getFacetCounts();
String[] pivots = params.getParams( FacetParams.FACET_PIVOT );
if( pivots != null && pivots.length > 0 ) {
NamedList v = pivotHelper.process(rb, params, pivots);
PivotFacetHelper pivotHelper = new PivotFacetHelper(rb.req,
rb.getResults().docSet,
params,
rb );
NamedList v = pivotHelper.process(pivots);
if( v != null ) {
counts.add( PIVOT_KEY, v );
}

View File

@ -17,6 +17,7 @@
package org.apache.solr.handler.component;
import org.apache.lucene.queryparser.classic.ParseException;
import org.apache.lucene.util.BytesRef;
import org.apache.solr.schema.SchemaField;
import org.apache.solr.search.SolrIndexSearcher;
@ -43,55 +44,60 @@ import java.util.List;
import java.util.Map;
/**
* This is thread safe
* @since solr 4.0
*/
public class PivotFacetHelper
public class PivotFacetHelper extends SimpleFacets
{
/**
* Designed to be overridden by subclasses that provide different faceting implementations.
* TODO: Currently this is returning a SimpleFacets object, but those capabilities would
* be better as an extracted abstract class or interface.
*/
protected SimpleFacets getFacetImplementation(SolrQueryRequest req, DocSet docs, SolrParams params) {
return new SimpleFacets(req, docs, params);
protected int minMatch;
public PivotFacetHelper(SolrQueryRequest req, DocSet docs, SolrParams params, ResponseBuilder rb) {
super(req, docs, params, rb);
minMatch = params.getInt( FacetParams.FACET_PIVOT_MINCOUNT, 1 );
}
public SimpleOrderedMap<List<NamedList<Object>>> process(ResponseBuilder rb, SolrParams params, String[] pivots) throws IOException {
public SimpleOrderedMap<List<NamedList<Object>>> process(String[] pivots) throws IOException {
if (!rb.doFacets || pivots == null)
return null;
int minMatch = params.getInt( FacetParams.FACET_PIVOT_MINCOUNT, 1 );
SimpleOrderedMap<List<NamedList<Object>>> pivotResponse = new SimpleOrderedMap<List<NamedList<Object>>>();
for (String pivot : pivots) {
String[] fields = pivot.split(","); // only support two levels for now
//ex: pivot == "features,cat" or even "{!ex=mytag}features,cat"
try {
this.parseParams(FacetParams.FACET_PIVOT, pivot);
} catch (ParseException e) {
throw new SolrException(ErrorCode.BAD_REQUEST, e);
}
pivot = facetValue;//facetValue potentially modified from parseParams()
String[] fields = pivot.split(",");
if( fields.length < 2 ) {
throw new SolrException( ErrorCode.BAD_REQUEST,
throw new SolrException( ErrorCode.BAD_REQUEST,
"Pivot Facet needs at least two fields: "+pivot );
}
DocSet docs = rb.getResults().docSet;
String field = fields[0];
String subField = fields[1];
Deque<String> fnames = new LinkedList<String>();
for( int i=fields.length-1; i>1; i-- ) {
fnames.push( fields[i] );
}
SimpleFacets sf = getFacetImplementation(rb.req, rb.getResults().docSet, rb.req.getParams());
NamedList<Integer> superFacets = sf.getTermCounts(field);
pivotResponse.add(pivot, doPivots(superFacets, field, subField, fnames, rb, docs, minMatch));
NamedList<Integer> superFacets = this.getTermCounts(field);
//super.key usually == pivot unless local-param 'key' used
pivotResponse.add(key, doPivots(superFacets, field, subField, fnames, docs));
}
return pivotResponse;
}
/**
* Recursive function to do all the pivots
*/
protected List<NamedList<Object>> doPivots( NamedList<Integer> superFacets, String field, String subField, Deque<String> fnames, ResponseBuilder rb, DocSet docs, int minMatch ) throws IOException
protected List<NamedList<Object>> doPivots(NamedList<Integer> superFacets,
String field, String subField, Deque<String> fnames,
DocSet docs) throws IOException
{
SolrIndexSearcher searcher = rb.req.getSearcher();
// TODO: optimize to avoid converting to an external string and then having to convert back to internal below
@ -103,7 +109,7 @@ public class PivotFacetHelper
List<NamedList<Object>> values = new ArrayList<NamedList<Object>>( superFacets.size() );
for (Map.Entry<String, Integer> kv : superFacets) {
// Only sub-facet if parent facet has positive count - still may not be any values for the sub-field though
if (kv.getValue() >= minMatch ) {
if (kv.getValue() >= minMatch) {
// may be null when using facet.missing
final String fieldValue = kv.getKey();
@ -136,11 +142,11 @@ public class PivotFacetHelper
Query query = new TermQuery(new Term(field, termval));
subset = searcher.getDocSet(query, docs);
}
SimpleFacets sf = getFacetImplementation(rb.req, subset, rb.req.getParams());
NamedList<Integer> nl = sf.getTermCounts(subField);
if (nl.size() >= minMatch ) {
pivot.add( "pivot", doPivots( nl, subField, nextField, fnames, rb, subset, minMatch ) );
super.docs = subset;//used by getTermCounts()
NamedList<Integer> nl = this.getTermCounts(subField);
if (nl.size() >= minMatch) {
pivot.add( "pivot", doPivots( nl, subField, nextField, fnames, subset) );
values.add( pivot ); // only add response if there are some counts
}
}

View File

@ -58,7 +58,7 @@ import java.util.concurrent.TimeUnit;
public class SimpleFacets {
/** The main set of documents all facet counts should be relative to */
protected DocSet docs;
protected DocSet docsOrig;
/** Configuration params behavior should be driven by */
protected SolrParams params;
protected SolrParams required;
@ -70,11 +70,11 @@ public class SimpleFacets {
protected SimpleOrderedMap<Object> facetResponse;
// 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
int threads;
protected SolrParams localParams; // localParams on this particular facet command
protected String facetValue; // the field to or query to facet on (minus local params)
protected DocSet docs; // the base docset for this particular facet
protected String key; // what name should the results be stored under
protected int threads;
public SimpleFacets(SolrQueryRequest req,
DocSet docs,
@ -88,16 +88,16 @@ public class SimpleFacets {
ResponseBuilder rb) {
this.req = req;
this.searcher = req.getSearcher();
this.base = this.docs = docs;
this.docs = this.docsOrig = docs;
this.params = params;
this.required = new RequiredSolrParams(params);
this.rb = rb;
}
void parseParams(String type, String param) throws ParseException, IOException {
protected void parseParams(String type, String param) throws ParseException, IOException {
localParams = QueryParsing.getLocalParams(param, req.getParams());
base = docs;
docs = docsOrig;
facetValue = param;
key = param;
threads = -1;
@ -166,7 +166,7 @@ public class SimpleFacets {
} else if (rb.getGroupingSpec().getFunctions().length > 0) {
grouping.addFunctionCommand(rb.getGroupingSpec().getFunctions()[0], req);
} else {
this.base = base;
this.docs = base;
return;
}
AbstractAllGroupHeadsCollector allGroupHeadsCollector = grouping.getCommands().get(0).createAllGroupCollector();
@ -174,9 +174,9 @@ public class SimpleFacets {
int maxDoc = searcher.maxDoc();
FixedBitSet fixedBitSet = allGroupHeadsCollector.retrieveGroupHeads(maxDoc);
long[] bits = fixedBitSet.getBits();
this.base = new BitDocSet(new OpenBitSet(bits, bits.length));
this.docs = new BitDocSet(new OpenBitSet(bits, bits.length));
} else {
this.base = base;
this.docs = base;
}
}
@ -184,7 +184,7 @@ public class SimpleFacets {
/**
* Looks at various Params to determing if any simple Facet Constraint count
* Looks at various Params to determining if any simple Facet Constraint count
* computations are desired.
*
* @see #getFacetQueryCounts
@ -245,7 +245,7 @@ public class SimpleFacets {
if (params.getBool(GroupParams.GROUP_FACET, false)) {
res.add(key, getGroupedFacetQueryCount(qobj));
} else {
res.add(key, searcher.numDocs(qobj, base));
res.add(key, searcher.numDocs(qobj, docs));
}
}
}
@ -269,7 +269,7 @@ public class SimpleFacets {
}
TermAllGroupsCollector collector = new TermAllGroupsCollector(groupField);
Filter mainQueryFilter = docs.getTopFilter(); // This returns a filter that only matches documents matching with q param and fq params
Filter mainQueryFilter = docsOrig.getTopFilter(); // This returns a filter that only matches documents matching with q param and fq params
searcher.search(facetQuery, mainQueryFilter, collector);
return collector.getGroupCount();
}
@ -316,25 +316,25 @@ public class SimpleFacets {
}
if (params.getFieldBool(field, GroupParams.GROUP_FACET, false)) {
counts = getGroupedCounts(searcher, base, field, multiToken, offset,limit, mincount, missing, sort, prefix);
counts = getGroupedCounts(searcher, docs, field, multiToken, offset,limit, mincount, missing, sort, prefix);
} else {
// unless the enum method is explicitly specified, use a counting method.
if (enumMethod) {
counts = getFacetTermEnumCounts(searcher, base, field, offset, limit, mincount,missing,sort,prefix);
counts = getFacetTermEnumCounts(searcher, docs, field, offset, limit, mincount,missing,sort,prefix);
} else {
if (multiToken) {
UnInvertedField uif = UnInvertedField.getUnInvertedField(field, searcher);
counts = uif.getCounts(searcher, base, offset, limit, mincount,missing,sort,prefix);
counts = uif.getCounts(searcher, docs, 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.
if (per_segment) {
PerSegmentSingleValuedFaceting ps = new PerSegmentSingleValuedFaceting(searcher, base, field, offset,limit, mincount, missing, sort, prefix);
PerSegmentSingleValuedFaceting ps = new PerSegmentSingleValuedFaceting(searcher, docs, field, offset,limit, mincount, missing, sort, prefix);
Executor executor = threads == 0 ? directExecutor : facetExecutor;
ps.setNumThreads(threads);
counts = ps.getFacetCounts(executor);
} else {
counts = getFieldCacheCounts(searcher, base, field, offset,limit, mincount, missing, sort, prefix);
counts = getFieldCacheCounts(searcher, docs, field, offset,limit, mincount, missing, sort, prefix);
}
}
@ -434,7 +434,7 @@ public class SimpleFacets {
NamedList<Integer> res = new NamedList<Integer>();
for (String term : terms) {
String internal = ft.toInternal(term);
int count = searcher.numDocs(new TermQuery(new Term(field, internal)), base);
int count = searcher.numDocs(new TermQuery(new Term(field, internal)), docs);
res.add(term, count);
}
return res;
@ -1212,7 +1212,7 @@ public class SimpleFacets {
if (params.getBool(GroupParams.GROUP_FACET, false)) {
return getGroupedFacetQueryCount(rangeQ);
} else {
return searcher.numDocs(rangeQ ,base);
return searcher.numDocs(rangeQ , docs);
}
}
@ -1223,7 +1223,7 @@ public class SimpleFacets {
protected int rangeCount(SchemaField sf, Date low, Date high,
boolean iLow, boolean iHigh) throws IOException {
Query rangeQ = ((DateField)(sf.getType())).getRangeQuery(null, sf,low,high,iLow,iHigh);
return searcher.numDocs(rangeQ ,base);
return searcher.numDocs(rangeQ , docs);
}
/**

View File

@ -1134,6 +1134,36 @@ abstract public class SolrExampleTests extends SolrJettyTestBase
assertEquals( null, p.getPivot() );
}
// -- SOLR-2255 Test excluding a filter Query --
// this test is a slight modification to the first pivot facet test
query = new SolrQuery( "*:*" );
query.addFacetPivotField( "{!ex=mytag key=mykey}features,cat" );
query.addFilterQuery("{!tag=mytag}-(features:bbb AND cat:a AND inStock:true)");//filters out one
query.setFacetMinCount( 0 );
query.setRows( 0 );
rsp = server.query( query );
assertEquals( docs.size() - 1, rsp.getResults().getNumFound() );//one less due to filter
//The rest of this test should be just like the original since we've
// excluded the 'fq' from the facet count
pivots = rsp.getFacetPivot();
pivot = pivots.getVal(0);
assertEquals( "mykey", pivots.getName( 0 ) );
assertEquals( 2, pivot.size() );
ff = pivot.get( 0 );
assertEquals( "features", ff.getField() );
assertEquals( "bbb", ff.getValue() );
assertEquals( 6, ff.getCount() );
counts = ff.getPivot();
assertEquals( 2, counts.size() );
assertEquals( "cat", counts.get(0).getField() );
assertEquals( "b", counts.get(0).getValue() );
assertEquals( 4, counts.get(0).getCount() );
assertEquals( "a", counts.get(1).getValue() );
assertEquals( 2, counts.get(1).getCount() );
}
public static SolrInputDocument makeTestDoc( Object ... kvp )