mirror of https://github.com/apache/lucene.git
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:
parent
11b4d43627
commit
15076ecf28
|
@ -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 ==================
|
||||
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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 )
|
||||
|
|
Loading…
Reference in New Issue