You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by ab...@apache.org on 2017/03/23 11:50:10 UTC
[23/46] lucene-solr:jira/solr-9959: SOLR-7452: add refine param to
json facets, implement for array field faceting
SOLR-7452: add refine param to json facets, implement for array field faceting
Project: http://git-wip-us.apache.org/repos/asf/lucene-solr/repo
Commit: http://git-wip-us.apache.org/repos/asf/lucene-solr/commit/540ee1db
Tree: http://git-wip-us.apache.org/repos/asf/lucene-solr/tree/540ee1db
Diff: http://git-wip-us.apache.org/repos/asf/lucene-solr/diff/540ee1db
Branch: refs/heads/jira/solr-9959
Commit: 540ee1db10b64aead7d7756b161c2c7348319d81
Parents: 3ca4d80
Author: yonik <yo...@apache.org>
Authored: Fri Mar 17 12:13:43 2017 -0400
Committer: yonik <yo...@apache.org>
Committed: Fri Mar 17 12:13:43 2017 -0400
----------------------------------------------------------------------
.../solr/search/facet/FacetFieldProcessor.java | 42 +++++-
.../facet/FacetFieldProcessorByArray.java | 4 +
.../FacetFieldProcessorByEnumTermsStream.java | 2 +-
.../apache/solr/search/facet/FacetModule.java | 127 +++++++++++--------
.../solr/search/facet/FacetProcessor.java | 27 +++-
.../apache/solr/search/facet/FacetQuery.java | 6 +-
.../apache/solr/search/facet/FacetRange.java | 4 +-
.../apache/solr/search/facet/FacetRequest.java | 3 +
.../search/facet/TestJsonFacetRefinement.java | 94 ++++++++++++++
9 files changed, 247 insertions(+), 62 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/540ee1db/solr/core/src/java/org/apache/solr/search/facet/FacetFieldProcessor.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/search/facet/FacetFieldProcessor.java b/solr/core/src/java/org/apache/solr/search/facet/FacetFieldProcessor.java
index bbc782c..fb44f62 100644
--- a/solr/core/src/java/org/apache/solr/search/facet/FacetFieldProcessor.java
+++ b/solr/core/src/java/org/apache/solr/search/facet/FacetFieldProcessor.java
@@ -31,6 +31,7 @@ import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.search.Query;
import org.apache.lucene.util.PriorityQueue;
import org.apache.solr.common.util.SimpleOrderedMap;
+import org.apache.solr.schema.FieldType;
import org.apache.solr.schema.SchemaField;
import org.apache.solr.search.DocSet;
@@ -310,7 +311,7 @@ abstract class FacetFieldProcessor extends FacetProcessor<FacetField> {
if (freq.missing) {
// TODO: it would be more efficient to build up a missing DocSet if we need it here anyway.
SimpleOrderedMap<Object> missingBucket = new SimpleOrderedMap<>();
- fillBucket(missingBucket, getFieldMissingQuery(fcontext.searcher, freq.field), null);
+ fillBucket(missingBucket, getFieldMissingQuery(fcontext.searcher, freq.field), null, false);
res.add("missing", missingBucket);
}
@@ -378,7 +379,7 @@ abstract class FacetFieldProcessor extends FacetProcessor<FacetField> {
}
}
- processSubs(target, filter, subDomain);
+ processSubs(target, filter, subDomain, false);
}
@Override
@@ -510,4 +511,41 @@ abstract class FacetFieldProcessor extends FacetProcessor<FacetField> {
}
}
}
+
+
+
+ protected SimpleOrderedMap<Object> refineFacets() throws IOException {
+ List leaves = (List)fcontext.facetInfo.get("_l");
+
+ // For leaf refinements, we do full faceting for each leaf bucket. Any sub-facets of these buckets will be fully evaluated. Because of this, we should never
+ // encounter leaf refinements that have sub-facets that return partial results.
+
+ SimpleOrderedMap<Object> res = new SimpleOrderedMap<>();
+ List<SimpleOrderedMap> bucketList = new ArrayList<>(leaves.size());
+ res.add("buckets", bucketList);
+
+ // TODO: an alternate implementations can fill all accs at once
+ createAccs(-1, 1);
+
+ FieldType ft = sf.getType();
+ for (Object bucketVal : leaves) {
+ SimpleOrderedMap<Object> bucket = new SimpleOrderedMap<>();
+ bucketList.add(bucket);
+ bucket.add("val", bucketVal);
+
+ // String internal = ft.toInternal( tobj.toString() ); // TODO - we need a better way to get from object to query...
+
+ Query domainQ = ft.getFieldQuery(null, sf, bucketVal.toString());
+
+ fillBucket(bucket, domainQ, null, false);
+ }
+
+ // If there are just a couple of leaves, and if the domain is large, then
+ // going by term is likely the most efficient?
+ // If the domain is small, or if the number of leaves is large, then doing
+ // the normal collection method may be best.
+
+ return res;
+ }
+
}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/540ee1db/solr/core/src/java/org/apache/solr/search/facet/FacetFieldProcessorByArray.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/search/facet/FacetFieldProcessorByArray.java b/solr/core/src/java/org/apache/solr/search/facet/FacetFieldProcessorByArray.java
index 767bb55..95b9f0b 100644
--- a/solr/core/src/java/org/apache/solr/search/facet/FacetFieldProcessorByArray.java
+++ b/solr/core/src/java/org/apache/solr/search/facet/FacetFieldProcessorByArray.java
@@ -57,6 +57,10 @@ abstract class FacetFieldProcessorByArray extends FacetFieldProcessor {
}
private SimpleOrderedMap<Object> calcFacets() throws IOException {
+ if (fcontext.facetInfo != null) {
+ return refineFacets();
+ }
+
String prefix = freq.prefix;
if (prefix == null || prefix.length() == 0) {
prefixRef = null;
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/540ee1db/solr/core/src/java/org/apache/solr/search/facet/FacetFieldProcessorByEnumTermsStream.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/search/facet/FacetFieldProcessorByEnumTermsStream.java b/solr/core/src/java/org/apache/solr/search/facet/FacetFieldProcessorByEnumTermsStream.java
index 2feff15..94f3b2d 100644
--- a/solr/core/src/java/org/apache/solr/search/facet/FacetFieldProcessorByEnumTermsStream.java
+++ b/solr/core/src/java/org/apache/solr/search/facet/FacetFieldProcessorByEnumTermsStream.java
@@ -333,7 +333,7 @@ class FacetFieldProcessorByEnumTermsStream extends FacetFieldProcessor implement
bucket.add("val", bucketVal);
addStats(bucket, 0);
if (hasSubFacets) {
- processSubs(bucket, bucketQuery, termSet);
+ processSubs(bucket, bucketQuery, termSet, false);
}
// TODO... termSet needs to stick around for streaming sub-facets?
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/540ee1db/solr/core/src/java/org/apache/solr/search/facet/FacetModule.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/search/facet/FacetModule.java b/solr/core/src/java/org/apache/solr/search/facet/FacetModule.java
index 87aaa8f..630e968 100644
--- a/solr/core/src/java/org/apache/solr/search/facet/FacetModule.java
+++ b/solr/core/src/java/org/apache/solr/search/facet/FacetModule.java
@@ -39,6 +39,7 @@ import org.apache.solr.search.QueryContext;
import org.apache.solr.search.SyntaxError;
import org.apache.solr.util.RTimer;
import org.noggit.JSONUtil;
+import org.noggit.ObjectBuilder;
public class FacetModule extends SearchComponent {
@@ -52,7 +53,7 @@ public class FacetModule extends SearchComponent {
public final static int PURPOSE_REFINE_JSON_FACETS = 0x00200000;
// Internal information passed down from the top level to shards for distributed faceting.
- private final static String FACET_STATE = "_facet_";
+ private final static String FACET_INFO = "_facet_";
private final static String FACET_REFINE = "refine";
@@ -62,43 +63,6 @@ public class FacetModule extends SearchComponent {
return (FacetComponentState) rb.req.getContext().get(FacetComponentState.class);
}
- @Override
- public void process(ResponseBuilder rb) throws IOException {
- // if this is null, faceting is not enabled
- FacetComponentState facetState = getFacetComponentState(rb);
- if (facetState == null) return;
-
- boolean isShard = rb.req.getParams().getBool(ShardParams.IS_SHARD, false);
-
- FacetContext fcontext = new FacetContext();
- fcontext.base = rb.getResults().docSet;
- fcontext.req = rb.req;
- fcontext.searcher = rb.req.getSearcher();
- fcontext.qcontext = QueryContext.newContext(fcontext.searcher);
- if (isShard) {
- fcontext.flags |= FacetContext.IS_SHARD;
- }
-
- FacetProcessor fproc = facetState.facetRequest.createFacetProcessor(fcontext);
- if (rb.isDebug()) {
- FacetDebugInfo fdebug = new FacetDebugInfo();
- fcontext.setDebugInfo(fdebug);
- fdebug.setReqDescription(facetState.facetRequest.getFacetDescription());
- fdebug.setProcessor(fproc.getClass().getSimpleName());
-
- final RTimer timer = new RTimer();
- fproc.process();
- long timeElapsed = (long) timer.getTime();
- fdebug.setElapse(timeElapsed);
- fdebug.putInfoItem("domainSize", (long)fcontext.base.size());
- rb.req.getContext().put("FacetDebugInfo", fdebug);
- } else {
- fproc.process();
- }
-
- rb.rsp.add("facets", fproc.getResponse());
- }
-
@Override
public void prepare(ResponseBuilder rb) throws IOException {
@@ -118,12 +82,14 @@ public class FacetModule extends SearchComponent {
SolrParams params = rb.req.getParams();
boolean isShard = params.getBool(ShardParams.IS_SHARD, false);
+ Map<String,Object> facetInfo = null;
if (isShard) {
- String jfacet = params.get(FACET_STATE);
+ String jfacet = params.get(FACET_INFO);
if (jfacet == null) {
- // if this is a shard request, but there is no facet state, then don't do anything.
+ // if this is a shard request, but there is no _facet_ info, then don't do anything.
return;
}
+ facetInfo = (Map<String,Object>) ObjectBuilder.fromJSON(jfacet);
}
// At this point, we know we need to do something. Create and save the state.
@@ -141,6 +107,7 @@ public class FacetModule extends SearchComponent {
FacetComponentState fcState = new FacetComponentState();
fcState.rb = rb;
fcState.isShard = isShard;
+ fcState.facetInfo = facetInfo;
fcState.facetCommands = jsonFacet;
fcState.facetRequest = facetRequest;
@@ -148,12 +115,57 @@ public class FacetModule extends SearchComponent {
}
+ @Override
+ public void process(ResponseBuilder rb) throws IOException {
+ // if this is null, faceting is not enabled
+ FacetComponentState facetState = getFacetComponentState(rb);
+ if (facetState == null) return;
+
+ boolean isShard = rb.req.getParams().getBool(ShardParams.IS_SHARD, false);
+
+ FacetContext fcontext = new FacetContext();
+ fcontext.base = rb.getResults().docSet;
+ fcontext.req = rb.req;
+ fcontext.searcher = rb.req.getSearcher();
+ fcontext.qcontext = QueryContext.newContext(fcontext.searcher);
+ if (isShard) {
+ fcontext.flags |= FacetContext.IS_SHARD;
+ fcontext.facetInfo = facetState.facetInfo.isEmpty() ? null : (Map<String,Object>)facetState.facetInfo.get(FACET_REFINE);
+ if (fcontext.facetInfo != null) {
+ fcontext.flags |= FacetContext.IS_REFINEMENT;
+ fcontext.flags |= FacetContext.SKIP_FACET; // the root bucket should have been received from all shards previously
+ }
+ }
+
+ FacetProcessor fproc = facetState.facetRequest.createFacetProcessor(fcontext);
+ if (rb.isDebug()) {
+ FacetDebugInfo fdebug = new FacetDebugInfo();
+ fcontext.setDebugInfo(fdebug);
+ fdebug.setReqDescription(facetState.facetRequest.getFacetDescription());
+ fdebug.setProcessor(fproc.getClass().getSimpleName());
+
+ final RTimer timer = new RTimer();
+ fproc.process();
+ long timeElapsed = (long) timer.getTime();
+ fdebug.setElapse(timeElapsed);
+ fdebug.putInfoItem("domainSize", (long)fcontext.base.size());
+ rb.req.getContext().put("FacetDebugInfo", fdebug);
+ } else {
+ fproc.process();
+ }
+
+ rb.rsp.add("facets", fproc.getResponse());
+ }
+
+
+
+
private void clearFaceting(List<ShardRequest> outgoing) {
// turn off faceting for requests not marked as being for faceting refinements
for (ShardRequest sreq : outgoing) {
if ((sreq.purpose & PURPOSE_REFINE_JSON_FACETS) != 0) continue;
- sreq.params.remove("json.facet"); // this just saves space... the presence of FACET_STATE really control the faceting
- sreq.params.remove(FACET_STATE);
+ sreq.params.remove("json.facet"); // this just saves space... the presence of FACET_INFO is enough to control the faceting
+ sreq.params.remove(FACET_INFO);
}
}
@@ -215,16 +227,15 @@ public class FacetModule extends SearchComponent {
// don't request any documents
shardsRefineRequest.params.remove(CommonParams.START);
shardsRefineRequest.params.set(CommonParams.ROWS, "0");
- shardsRefineRequest.params.set(CommonParams.ROWS, "0");
shardsRefineRequest.params.set(FacetParams.FACET, false);
}
shardsRefineRequest.purpose |= PURPOSE_REFINE_JSON_FACETS;
- Map<String,Object> fstate = new HashMap<>(1);
- fstate.put(FACET_REFINE, refinement);
- String fstateString = JSONUtil.toJSON(fstate);
- shardsRefineRequest.params.add(FACET_STATE, fstateString);
+ Map<String,Object> finfo = new HashMap<>(1);
+ finfo.put(FACET_REFINE, refinement);
+ String finfoStr = JSONUtil.toJSON(finfo);
+ shardsRefineRequest.params.add(FACET_INFO, finfoStr);
if (newRequest) {
rb.addRequest(this, shardsRefineRequest);
@@ -242,12 +253,12 @@ public class FacetModule extends SearchComponent {
if ((sreq.purpose & ShardRequest.PURPOSE_GET_TOP_IDS) != 0) {
sreq.purpose |= FacetModule.PURPOSE_GET_JSON_FACETS;
- sreq.params.set(FACET_STATE, "{}"); // The presence of FACET_STATE (_facet_) turns on json faceting
+ sreq.params.set(FACET_INFO, "{}"); // The presence of FACET_INFO (_facet_) turns on json faceting
} else {
// turn off faceting on other requests
/*** distributedProcess will need to use other requests for refinement
- sreq.params.remove("json.facet"); // this just saves space... the presence of FACET_STATE really control the faceting
- sreq.params.remove(FACET_STATE);
+ sreq.params.remove("json.facet"); // this just saves space... the presence of FACET_INFO really control the faceting
+ sreq.params.remove(FACET_INFO);
**/
}
}
@@ -267,6 +278,18 @@ public class FacetModule extends SearchComponent {
facetState.merger = facetState.facetRequest.createFacetMerger(facet);
facetState.mcontext = new FacetMerger.Context( sreq.responses.size() );
}
+
+ if ((sreq.purpose & PURPOSE_REFINE_JSON_FACETS) != 0) {
+ System.err.println("REFINE FACET RESULT FROM SHARD = " + facet);
+ // call merge again with a diff flag set on the context???
+// throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "WORK IN PROGRESS, MERGING FACET REFINEMENT NOT SUPPORTED YET!");
+
+ facetState.mcontext.root = facet;
+ facetState.mcontext.setShard(shardRsp.getShard()); // TODO: roll newShard into setShard?
+ facetState.merger.merge(facet , facetState.mcontext);
+ return;
+ }
+
facetState.mcontext.root = facet;
facetState.mcontext.newShard(shardRsp.getShard());
facetState.merger.merge(facet , facetState.mcontext);
@@ -304,11 +327,15 @@ public class FacetModule extends SearchComponent {
}
+// TODO: perhaps factor out some sort of root/parent facet object that doesn't depend
+// on stuff like ResponseBuilder, but contains request parameters,
+// root filter lists (for filter exclusions), etc?
class FacetComponentState {
ResponseBuilder rb;
Map<String,Object> facetCommands;
FacetRequest facetRequest;
boolean isShard;
+ Map<String,Object> facetInfo; // _facet_ param: contains out-of-band facet info, mainly for refinement requests
//
// Only used for distributed search
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/540ee1db/solr/core/src/java/org/apache/solr/search/facet/FacetProcessor.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/search/facet/FacetProcessor.java b/solr/core/src/java/org/apache/solr/search/facet/FacetProcessor.java
index 4a839a2..de6dd72 100644
--- a/solr/core/src/java/org/apache/solr/search/facet/FacetProcessor.java
+++ b/solr/core/src/java/org/apache/solr/search/facet/FacetProcessor.java
@@ -366,10 +366,13 @@ public abstract class FacetProcessor<FacetRequestT extends FacetRequest> {
}
}
- void fillBucket(SimpleOrderedMap<Object> bucket, Query q, DocSet result) throws IOException {
+ // TODO: rather than just have a raw "response", perhaps we should model as a bucket object that contains the response plus extra info?
+ void fillBucket(SimpleOrderedMap<Object> bucket, Query q, DocSet result, boolean skip) throws IOException {
+
+ // TODO: we don't need the DocSet if we've already calculated everything during the first phase
boolean needDocSet = freq.getFacetStats().size() > 0 || freq.getSubFacets().size() > 0;
- // TODO: always collect counts or not???
+ // TODO: put info in for the merger (like "skip=true"?) Maybe we don't need to if we leave out all extraneous info?
int count;
@@ -382,7 +385,7 @@ public abstract class FacetProcessor<FacetRequestT extends FacetRequest> {
} else {
result = fcontext.searcher.getDocSet(q, fcontext.base);
}
- count = result.size();
+ count = result.size(); // don't really need this if we are skipping, but it's free.
} else {
if (q == null) {
count = fcontext.base.size();
@@ -392,8 +395,10 @@ public abstract class FacetProcessor<FacetRequestT extends FacetRequest> {
}
try {
- processStats(bucket, result, count);
- processSubs(bucket, q, result);
+ if (!skip) {
+ processStats(bucket, result, count);
+ }
+ processSubs(bucket, q, result, skip);
} finally {
if (result != null) {
// result.decref(); // OFF-HEAP
@@ -402,7 +407,7 @@ public abstract class FacetProcessor<FacetRequestT extends FacetRequest> {
}
}
- void processSubs(SimpleOrderedMap<Object> response, Query filter, DocSet domain) throws IOException {
+ void processSubs(SimpleOrderedMap<Object> response, Query filter, DocSet domain, boolean skip) throws IOException {
boolean emptyDomain = domain == null || domain.size() == 0;
@@ -417,8 +422,18 @@ public abstract class FacetProcessor<FacetRequestT extends FacetRequest> {
continue;
}
+ Map<String,Object>facetInfoSub = null;
+ if (fcontext.facetInfo != null) {
+ facetInfoSub = (Map<String,Object>)fcontext.facetInfo.get(sub.getKey());
+ }
+
+ // If we're skipping this node, then we only need to process sub-facets that have facet info specified.
+ if (skip && facetInfoSub == null) continue;
+
// make a new context for each sub-facet since they can change the domain
FacetContext subContext = fcontext.sub(filter, domain);
+ subContext.facetInfo = facetInfoSub;
+ if (!skip) subContext.flags &= ~FacetContext.SKIP_FACET; // turn off the skip flag if we're not skipping this bucket
FacetProcessor subProcessor = subRequest.createFacetProcessor(subContext);
if (fcontext.getDebugInfo() != null) { // if fcontext.debugInfo != null, it means rb.debug() == true
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/540ee1db/solr/core/src/java/org/apache/solr/search/facet/FacetQuery.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/search/facet/FacetQuery.java b/solr/core/src/java/org/apache/solr/search/facet/FacetQuery.java
index 174b832..584bec3 100644
--- a/solr/core/src/java/org/apache/solr/search/facet/FacetQuery.java
+++ b/solr/core/src/java/org/apache/solr/search/facet/FacetQuery.java
@@ -56,8 +56,12 @@ class FacetQueryProcessor extends FacetProcessor<FacetQuery> {
@Override
public void process() throws IOException {
super.process();
+
+ if (fcontext.facetInfo != null) {
+ // FIXME - what needs to be done here?
+ }
response = new SimpleOrderedMap<>();
- fillBucket(response, freq.q, null);
+ fillBucket(response, freq.q, null, (fcontext.flags & FacetContext.SKIP_FACET)!=0);
}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/540ee1db/solr/core/src/java/org/apache/solr/search/facet/FacetRange.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/search/facet/FacetRange.java b/solr/core/src/java/org/apache/solr/search/facet/FacetRange.java
index a50fa2c..5d0989b 100644
--- a/solr/core/src/java/org/apache/solr/search/facet/FacetRange.java
+++ b/solr/core/src/java/org/apache/solr/search/facet/FacetRange.java
@@ -350,7 +350,7 @@ class FacetRangeProcessor extends FacetProcessor<FacetRange> {
if (freq.getSubFacets().size() > 0) {
DocSet subBase = intersections[slot];
try {
- processSubs(bucket, filters[slot], subBase);
+ processSubs(bucket, filters[slot], subBase, false);
} finally {
// subContext.base.decref(); // OFF-HEAP
// subContext.base = null; // do not modify context after creation... there may be deferred execution (i.e. streaming)
@@ -367,7 +367,7 @@ class FacetRangeProcessor extends FacetProcessor<FacetRange> {
}
Query rangeQ = sf.getType().getRangeQuery(null, sf, range.low == null ? null : calc.formatValue(range.low), range.high==null ? null : calc.formatValue(range.high), range.includeLower, range.includeUpper);
- fillBucket(bucket, rangeQ, null);
+ fillBucket(bucket, rangeQ, null, false);
return bucket;
}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/540ee1db/solr/core/src/java/org/apache/solr/search/facet/FacetRequest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/search/facet/FacetRequest.java b/solr/core/src/java/org/apache/solr/search/facet/FacetRequest.java
index 636460f..9835f7d 100644
--- a/solr/core/src/java/org/apache/solr/search/facet/FacetRequest.java
+++ b/solr/core/src/java/org/apache/solr/search/facet/FacetRequest.java
@@ -168,7 +168,10 @@ public abstract class FacetRequest {
class FacetContext {
// Context info for actually executing a local facet command
public static final int IS_SHARD=0x01;
+ public static final int IS_REFINEMENT=0x02;
+ public static final int SKIP_FACET=0x04; // refinement: skip calculating this immediate facet, but proceed to specific sub-facets based on facetInfo
+ Map<String,Object> facetInfo; // refinement info for this node
QueryContext qcontext;
SolrQueryRequest req; // TODO: replace with params?
SolrIndexSearcher searcher;
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/540ee1db/solr/core/src/test/org/apache/solr/search/facet/TestJsonFacetRefinement.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/search/facet/TestJsonFacetRefinement.java b/solr/core/src/test/org/apache/solr/search/facet/TestJsonFacetRefinement.java
index a8f8ff2..f23ae8c 100644
--- a/solr/core/src/test/org/apache/solr/search/facet/TestJsonFacetRefinement.java
+++ b/solr/core/src/test/org/apache/solr/search/facet/TestJsonFacetRefinement.java
@@ -18,9 +18,12 @@
package org.apache.solr.search.facet;
import java.io.IOException;
+import java.util.List;
import org.apache.solr.JSONTestUtil;
import org.apache.solr.SolrTestCaseHS;
+import org.apache.solr.client.solrj.SolrClient;
+import org.apache.solr.common.params.ModifiableSolrParams;
import org.apache.solr.common.util.SimpleOrderedMap;
import org.apache.solr.request.SolrQueryRequest;
import org.junit.AfterClass;
@@ -209,6 +212,97 @@ public class TestJsonFacetRefinement extends SolrTestCaseHS {
}
+ @Test
+ public void testBasicRefinement() throws Exception {
+ initServers();
+ Client client = servers.getClient(random().nextInt());
+ client.queryDefaults().set( "shards", servers.getShards(), "debugQuery", Boolean.toString(random().nextBoolean()) );
+
+ List<SolrClient> clients = client.getClientProvider().all();
+ assertTrue(clients.size() >= 3);
+
+ client.deleteByQuery("*:*", null);
+
+ ModifiableSolrParams p = params("cat_s", "cat_s", "num_d", "num_d");
+ String cat_s = p.get("cat_s");
+ String num_d = p.get("num_d");
+
+ clients.get(0).add( sdoc("id", "01", cat_s, "A", num_d, -1) ); // A wins count tie
+ clients.get(0).add( sdoc("id", "02", cat_s, "B", num_d, 3) );
+
+ clients.get(1).add( sdoc("id", "11", cat_s, "B", num_d, -5) ); // B highest count
+ clients.get(1).add( sdoc("id", "12", cat_s, "B", num_d, -11) );
+ clients.get(1).add( sdoc("id", "13", cat_s, "A", num_d, 7) );
+
+ clients.get(2).add( sdoc("id", "21", cat_s, "A", num_d, 17) ); // A highest count
+ clients.get(2).add( sdoc("id", "22", cat_s, "A", num_d, -19) );
+ clients.get(2).add( sdoc("id", "23", cat_s, "B", num_d, 11) );
+
+ client.commit();
+
+ // Shard responses should be A=1, B=2, A=2, merged should be "A=3, B=2"
+ // One shard will have _facet_={"refine":{"cat0":{"_l":["A"]}}} on the second phase
+
+ /****
+ // fake a refinement request... good for development/debugging
+ assertJQ(clients.get(1),
+ params(p, "q", "*:*", "_facet_","{refine:{cat0:{_l:[A]}}}", "isShard","true", "distrib","false", "shards.purpose","2097216", "ids","11,12,13",
+ "json.facet", "{" +
+ "cat0:{type:terms, field:cat_s, sort:'count desc', limit:1, overrequest:0, refine:true}" +
+ "}"
+ )
+ , "facets=={foo:555}"
+ );
+ ****/
+
+ client.testJQ(params(p, "q", "*:*",
+ "json.facet", "{" +
+ "cat0:{type:terms, field:${cat_s}, sort:'count desc', limit:1, overrequest:0, refine:false}" +
+ "}"
+ )
+ , "facets=={ count:8" +
+ ", cat0:{ buckets:[ {val:A,count:3} ] }" + // w/o overrequest and refinement, count is lower than it should be (we don't see the A from the middle shard)
+ "}"
+ );
+
+ client.testJQ(params(p, "q", "*:*",
+ "json.facet", "{" +
+ "cat0:{type:terms, field:${cat_s}, sort:'count desc', limit:1, overrequest:0, refine:true}" +
+ "}"
+ )
+ , "facets=={ count:8" +
+ ", cat0:{ buckets:[ {val:A,count:4} ] }" + // w/o overrequest, we need refining to get the correct count.
+ "}"
+ );
+
+ // test that basic stats work for refinement
+ client.testJQ(params(p, "q", "*:*",
+ "json.facet", "{" +
+ "cat0:{type:terms, field:${cat_s}, sort:'count desc', limit:1, overrequest:0, refine:true, facet:{ stat1:'sum(${num_d})'} }" +
+ "}"
+ )
+ , "facets=={ count:8" +
+ ", cat0:{ buckets:[ {val:A,count:4, stat1:4.0} ] }" +
+ "}"
+ );
+
+ // test sorting buckets by a different stat
+ client.testJQ(params(p, "q", "*:*",
+ "json.facet", "{" +
+ " cat0:{type:terms, field:${cat_s}, sort:'min1 asc', limit:1, overrequest:0, refine:false, facet:{ min1:'min(${num_d})'} }" +
+ ",cat1:{type:terms, field:${cat_s}, sort:'min1 asc', limit:1, overrequest:0, refine:true, facet:{ min1:'min(${num_d})'} }" +
+ ",sum1:'sum(num_d)'" + // make sure that root bucket stats aren't affected by refinement
+ "}"
+ )
+ , "facets=={ count:8" +
+ ", cat0:{ buckets:[ {val:A,count:3, min1:-19.0} ] }" + // B wins in shard2, so we're missing the "A" count for that shar w/o refinement.
+ ", cat1:{ buckets:[ {val:A,count:4, min1:-19.0} ] }" + // with refinement, we get the right count
+ ", sum1:2.0" +
+ "}"
+ );
+
+
+ }
}