SOLR-7452: refinement of missing buckets and partial facets through missing buckets

This commit is contained in:
yonik 2017-03-30 12:55:27 -04:00
parent e80643e5a7
commit cc623403bd
4 changed files with 72 additions and 1 deletions

View File

@ -18,8 +18,11 @@
package org.apache.solr.search.facet; package org.apache.solr.search.facet;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
import org.apache.solr.common.util.SimpleOrderedMap; import org.apache.solr.common.util.SimpleOrderedMap;
@ -167,7 +170,29 @@ public class FacetFieldMerger extends FacetRequestSortedMerger<FacetField> {
// basically , only do at the top-level facet? // basically , only do at the top-level facet?
} }
@Override
Map<String, Object> getRefinementSpecial(Context mcontext, Map<String, Object> refinement, Collection<String> tagsWithPartial) {
if (!tagsWithPartial.isEmpty()) {
// Since special buckets missing and allBuckets themselves will always be included, we only need to worry about subfacets being partial.
if (freq.missing) {
refinement = getRefinementSpecial(mcontext, refinement, tagsWithPartial, missingBucket, "missing");
}
if (freq.allBuckets) {
refinement = getRefinementSpecial(mcontext, refinement, tagsWithPartial, allBuckets, "allBuckets");
}
}
return refinement;
}
private Map<String, Object> getRefinementSpecial(Context mcontext, Map<String, Object> refinement, Collection<String> tagsWithPartial, FacetBucket bucket, String label) {
// boolean prev = mcontext.setBucketWasMissing(true); // the special buckets should have the same "missing" status as this facet, so no need to set it again
Map<String, Object> bucketRefinement = bucket.getRefinement(mcontext, tagsWithPartial);
if (bucketRefinement != null) {
refinement = refinement == null ? new HashMap<>(2) : refinement;
refinement.put(label, bucketRefinement);
}
return refinement;
}
private static class FacetNumBucketsMerger extends FacetMerger { private static class FacetNumBucketsMerger extends FacetMerger {
long sumBuckets; long sumBuckets;

View File

@ -36,6 +36,8 @@ import org.apache.solr.schema.FieldType;
import org.apache.solr.schema.SchemaField; import org.apache.solr.schema.SchemaField;
import org.apache.solr.search.DocSet; import org.apache.solr.search.DocSet;
import static org.apache.solr.search.facet.FacetContext.SKIP_FACET;
/** /**
* Facet processing based on field values. (not range nor by query) * Facet processing based on field values. (not range nor by query)
* @see FacetField * @see FacetField
@ -528,6 +530,9 @@ abstract class FacetFieldProcessor extends FacetProcessor<FacetField> {
} }
protected SimpleOrderedMap<Object> refineFacets() throws IOException { protected SimpleOrderedMap<Object> refineFacets() throws IOException {
boolean skipThisFacet = (fcontext.flags & SKIP_FACET) != 0;
List leaves = asList(fcontext.facetInfo.get("_l")); // We have not seen this bucket: do full faceting for this bucket, including all sub-facets List leaves = asList(fcontext.facetInfo.get("_l")); // We have not seen this bucket: do full faceting for this bucket, including all sub-facets
List<List> skip = asList(fcontext.facetInfo.get("_s")); // We have seen this bucket, so skip stats on it, and skip sub-facets except for the specified sub-facets that should calculate specified buckets. List<List> skip = asList(fcontext.facetInfo.get("_s")); // We have seen this bucket, so skip stats on it, and skip sub-facets except for the specified sub-facets that should calculate specified buckets.
List<List> partial = asList(fcontext.facetInfo.get("_p")); // We have not seen this bucket, do full faceting for this bucket, and most sub-facets... but some sub-facets are partial and should only visit specified buckets. List<List> partial = asList(fcontext.facetInfo.get("_p")); // We have not seen this bucket, do full faceting for this bucket, and most sub-facets... but some sub-facets are partial and should only visit specified buckets.
@ -563,6 +568,15 @@ abstract class FacetFieldProcessor extends FacetProcessor<FacetField> {
bucketList.add( refineBucket(bucketVal, false, facetInfo ) ); bucketList.add( refineBucket(bucketVal, false, facetInfo ) );
} }
if (freq.missing) {
Map<String,Object> bucketFacetInfo = (Map<String,Object>)fcontext.facetInfo.get("missing");
if (bucketFacetInfo != null || !skipThisFacet) {
SimpleOrderedMap<Object> missingBucket = new SimpleOrderedMap<>();
fillBucket(missingBucket, getFieldMissingQuery(fcontext.searcher, freq.field), null, skipThisFacet, bucketFacetInfo);
res.add("missing", missingBucket);
}
}
// If there are just a couple of leaves, and if the domain is large, then // If there are just a couple of leaves, and if the domain is large, then
// going by term is likely the most efficient? // going by term is likely the most efficient?

View File

@ -240,6 +240,14 @@ abstract class FacetRequestSortedMerger<FacetRequestT extends FacetRequestSorted
if (skipBuckets != null) refinement.put("_s", skipBuckets); if (skipBuckets != null) refinement.put("_s", skipBuckets);
} }
refinement = getRefinementSpecial(mcontext, refinement, tagsWithPartial);
return refinement;
}
// utility method for subclasses to override to finish calculating faceting (special buckets in field facets)... this feels hacky and we
// should find a better way.
Map<String,Object> getRefinementSpecial(Context mcontext, Map<String,Object> refinement, Collection<String> tagsWithPartial) {
return refinement; return refinement;
} }

View File

@ -219,6 +219,17 @@ public class TestJsonFacetRefinement extends SolrTestCaseHS {
"}" "}"
); );
// test partial _p under a missing bucket
doTestRefine("{top:{type:terms, field:Afield, refine:true, limit:1, missing:true, facet:{x : {type:terms, field:X, limit:1, refine:true} } } }",
"{top: {buckets:[], missing:{count:12, x:{buckets:[{val:x2, count:4},{val:x3, count:2}]} } } }",
"{top: {buckets:[], missing:{count:10, x:{buckets:[{val:x1, count:5},{val:x4, count:3}]} } } }",
"=={top: {" +
"missing:{x:{_l:[x1]}}" +
" } " +
"}"
, null
);
} }
@ -266,6 +277,17 @@ public class TestJsonFacetRefinement extends SolrTestCaseHS {
); );
****/ ****/
// test refining under the special "missing" bucket of a field facet
client.testJQ(params(p, "q", "*:*",
"json.facet", "{" +
"f:{type:terms, field:missing_s, limit:1, overrequest:0, missing:true, refine:true, facet:{ cat:{type:terms, field:${cat_s}, limit:1, overrequest:0, refine:true } }}" +
"}"
)
, "facets=={ count:8" +
", f:{ buckets:[], missing:{count:8, cat:{buckets:[{val:A,count:4}]} } }" + // just like the previous response, just nested under a field facet
"}"
);
client.testJQ(params(p, "q", "*:*", client.testJQ(params(p, "q", "*:*",
"json.facet", "{" + "json.facet", "{" +
@ -317,7 +339,7 @@ public class TestJsonFacetRefinement extends SolrTestCaseHS {
"}" "}"
); );
// test missing buckets (field facet within field facet) // test partial buckets (field facet within field facet)
client.testJQ(params(p, "q", "*:*", client.testJQ(params(p, "q", "*:*",
"json.facet", "{" + "json.facet", "{" +
"ab:{type:terms, field:${cat_s}, limit:1, overrequest:0, refine:true, facet:{ xy:{type:terms, field:${xy_s}, limit:1, overrequest:0, refine:true } }}" + "ab:{type:terms, field:${cat_s}, limit:1, overrequest:0, refine:true, facet:{ xy:{type:terms, field:${xy_s}, limit:1, overrequest:0, refine:true } }}" +
@ -345,6 +367,8 @@ public class TestJsonFacetRefinement extends SolrTestCaseHS {
"}" "}"
); );
} }