From cc623403bd82a176a057e1c9567f37d01f7391c4 Mon Sep 17 00:00:00 2001 From: yonik Date: Thu, 30 Mar 2017 12:55:27 -0400 Subject: [PATCH] SOLR-7452: refinement of missing buckets and partial facets through missing buckets --- .../solr/search/facet/FacetFieldMerger.java | 25 ++++++++++++++++++ .../search/facet/FacetFieldProcessor.java | 14 ++++++++++ .../facet/FacetRequestSortedMerger.java | 8 ++++++ .../search/facet/TestJsonFacetRefinement.java | 26 ++++++++++++++++++- 4 files changed, 72 insertions(+), 1 deletion(-) diff --git a/solr/core/src/java/org/apache/solr/search/facet/FacetFieldMerger.java b/solr/core/src/java/org/apache/solr/search/facet/FacetFieldMerger.java index 63e8743c32c..f8f646348ed 100644 --- a/solr/core/src/java/org/apache/solr/search/facet/FacetFieldMerger.java +++ b/solr/core/src/java/org/apache/solr/search/facet/FacetFieldMerger.java @@ -18,8 +18,11 @@ package org.apache.solr.search.facet; import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; import org.apache.solr.common.util.SimpleOrderedMap; @@ -167,7 +170,29 @@ public class FacetFieldMerger extends FacetRequestSortedMerger { // basically , only do at the top-level facet? } + @Override + Map getRefinementSpecial(Context mcontext, Map refinement, Collection 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 getRefinementSpecial(Context mcontext, Map refinement, Collection 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 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 { long sumBuckets; 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 a29e78d9e6c..d4daf08d80a 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 @@ -36,6 +36,8 @@ import org.apache.solr.schema.FieldType; import org.apache.solr.schema.SchemaField; 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) * @see FacetField @@ -528,6 +530,9 @@ abstract class FacetFieldProcessor extends FacetProcessor { } protected SimpleOrderedMap 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 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 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 { bucketList.add( refineBucket(bucketVal, false, facetInfo ) ); } + if (freq.missing) { + Map bucketFacetInfo = (Map)fcontext.facetInfo.get("missing"); + + if (bucketFacetInfo != null || !skipThisFacet) { + SimpleOrderedMap 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 // going by term is likely the most efficient? diff --git a/solr/core/src/java/org/apache/solr/search/facet/FacetRequestSortedMerger.java b/solr/core/src/java/org/apache/solr/search/facet/FacetRequestSortedMerger.java index e05064c4148..9ffdea7835a 100644 --- a/solr/core/src/java/org/apache/solr/search/facet/FacetRequestSortedMerger.java +++ b/solr/core/src/java/org/apache/solr/search/facet/FacetRequestSortedMerger.java @@ -240,6 +240,14 @@ abstract class FacetRequestSortedMerger getRefinementSpecial(Context mcontext, Map refinement, Collection tagsWithPartial) { return refinement; } 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 b4b022066ea..52b8be4fae5 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 @@ -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", "*:*", "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", "*:*", "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 } }}" + @@ -345,6 +367,8 @@ public class TestJsonFacetRefinement extends SolrTestCaseHS { "}" ); + + }