You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by kr...@apache.org on 2016/11/10 21:15:54 UTC

[24/36] lucene-solr:jira/solr-8593: SOLR-9519: recurse sub-facets of empty buckets if they can widen domain again

SOLR-9519: recurse sub-facets of empty buckets if they can widen domain again


Project: http://git-wip-us.apache.org/repos/asf/lucene-solr/repo
Commit: http://git-wip-us.apache.org/repos/asf/lucene-solr/commit/cfcf4081
Tree: http://git-wip-us.apache.org/repos/asf/lucene-solr/tree/cfcf4081
Diff: http://git-wip-us.apache.org/repos/asf/lucene-solr/diff/cfcf4081

Branch: refs/heads/jira/solr-8593
Commit: cfcf4081fcf04cf2e1d6293a05a2005f0a99942c
Parents: 46fad72
Author: yonik <yo...@apache.org>
Authored: Tue Nov 8 12:10:34 2016 -0500
Committer: yonik <yo...@apache.org>
Committed: Tue Nov 8 12:10:53 2016 -0500

----------------------------------------------------------------------
 solr/CHANGES.txt                                |  4 ++++
 .../solr/search/facet/FacetProcessor.java       | 23 ++++++++++++--------
 .../apache/solr/search/facet/FacetRequest.java  | 19 ++++++++++++++++
 .../solr/search/facet/TestJsonFacets.java       | 19 ++++++++++++++++
 4 files changed, 56 insertions(+), 9 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/cfcf4081/solr/CHANGES.txt
----------------------------------------------------------------------
diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index 5c0ff06..77897ef 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -130,6 +130,10 @@ Bug Fixes
 
 * SOLR-9005: In files example, add a guard condition to javascript URP script (Alexandre Rafalovitch)
 
+* SOLR-9519: JSON Facet API: don't stop at an empty facet bucket if any sub-facets still have a chance
+  of matching something due to filter exclusions (which can widen the domain again).
+  (Michael Sun, yonik)
+
 Other Changes
 ----------------------
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/cfcf4081/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 3a26e5b..84b11a0 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
@@ -401,24 +401,29 @@ public abstract class FacetProcessor<FacetRequestT extends FacetRequest>  {
 
   void processSubs(SimpleOrderedMap<Object> response, Query filter, DocSet domain) throws IOException {
 
-    // TODO: what if a zero bucket has a sub-facet with an exclusion that would yield results?
-    // should we check for domain-altering exclusions, or even ask the sub-facet for
-    // it's domain and then only skip it if it's 0?
-
-    if (domain == null || domain.size() == 0 && !freq.processEmpty) {
-      return;
-    }
+    boolean emptyDomain = domain == null || domain.size() == 0;
 
     for (Map.Entry<String,FacetRequest> sub : freq.getSubFacets().entrySet()) {
+      FacetRequest subRequest = sub.getValue();
+
+      // This includes a static check if a sub-facet can possibly produce something from
+      // an empty domain.  Should this be changed to a dynamic check as well?  That would
+      // probably require actually executing the facet anyway, and dropping it at the
+      // end if it was unproductive.
+      if (emptyDomain && !freq.processEmpty && !subRequest.canProduceFromEmpty()) {
+        continue;
+      }
+
       // make a new context for each sub-facet since they can change the domain
       FacetContext subContext = fcontext.sub(filter, domain);
-      FacetProcessor subProcessor = sub.getValue().createFacetProcessor(subContext);
+      FacetProcessor subProcessor = subRequest.createFacetProcessor(subContext);
+
       if (fcontext.getDebugInfo() != null) {   // if fcontext.debugInfo != null, it means rb.debug() == true
         FacetDebugInfo fdebug = new FacetDebugInfo();
         subContext.setDebugInfo(fdebug);
         fcontext.getDebugInfo().addChild(fdebug);
 
-        fdebug.setReqDescription(sub.getValue().getFacetDescription());
+        fdebug.setReqDescription(subRequest.getFacetDescription());
         fdebug.setProcessor(subProcessor.getClass().getSimpleName());
         if (subContext.filter != null) fdebug.setFilter(subContext.filter.toString());
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/cfcf4081/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 273466c..9f68380 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
@@ -88,6 +88,16 @@ public abstract class FacetRequest {
     public boolean toChildren;
     public String parents; // identifies the parent filter... the full set of parent documents for any block join operation
     public List<Object> filters; // list of symbolic filters (JSON query format)
+
+    // True if a starting set of documents can be mapped onto a different set of documents not originally in the starting set.
+    public boolean canTransformDomain() {
+      return toParent || toChildren || excludeTags != null;
+    }
+
+    // Can this domain become non-empty if the input domain is empty?  This does not check any sub-facets (see canProduceFromEmpty for that)
+    public boolean canBecomeNonEmpty() {
+      return excludeTags != null;
+    }
   }
 
   public FacetRequest() {
@@ -119,6 +129,15 @@ public abstract class FacetRequest {
     return false;
   }
 
+  /** Returns true if this facet, or any sub-facets can produce results from an empty domain. */
+  public boolean canProduceFromEmpty() {
+    if (domain != null && domain.canBecomeNonEmpty()) return true;
+    for (FacetRequest freq : subFacets.values()) {
+      if (freq.canProduceFromEmpty()) return true;
+    }
+    return false;
+  }
+
   public void addStat(String key, AggValueSource stat) {
     facetStats.put(key, stat);
   }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/cfcf4081/solr/core/src/test/org/apache/solr/search/facet/TestJsonFacets.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/search/facet/TestJsonFacets.java b/solr/core/src/test/org/apache/solr/search/facet/TestJsonFacets.java
index b08e940..32f9dfa 100644
--- a/solr/core/src/test/org/apache/solr/search/facet/TestJsonFacets.java
+++ b/solr/core/src/test/org/apache/solr/search/facet/TestJsonFacets.java
@@ -998,6 +998,25 @@ public class TestJsonFacets extends SolrTestCaseHS {
             "}"
     );
 
+    // test sub-facets of  empty buckets with domain filter exclusions (canProduceFromEmpty) (see SOLR-9519)
+    client.testJQ(params(p, "q", "*:*", "fq","{!tag=doc3}id:non-exist", "fq","{!tag=CATA}${cat_s}:A"
+
+        , "json.facet", "{" +
+            "f1:{${terms} type:terms, field:${cat_s}, domain:{excludeTags:doc3} }  " +
+            ",q1 :{type:query, q:'*:*', facet:{ f1:{${terms} type:terms, field:${cat_s}, domain:{excludeTags:doc3} } }  }  " +  // nested under query
+            ",q1a:{type:query, q:'id:4', facet:{ f1:{${terms} type:terms, field:${cat_s}, domain:{excludeTags:doc3} } }  }  " +  // nested under query, make sure id:4 filter still applies
+            ",r1 :{type:range, field:${num_d}, start:0, gap:3, end:5,  facet:{ f1:{${terms} type:terms, field:${cat_s}, domain:{excludeTags:doc3} } }  }  " +  // nested under range, make sure range constraints still apply
+            ",f2:{${terms} type:terms, field:${cat_s}, domain:{filter:'*:*'} }  " + // domain filter doesn't widen, so f2 should not appear.
+            "}"
+    )
+    , "facets=={ count:0, " +
+        " f1:{ buckets:[ {val:A, count:2} ]  }" +
+        ",q1:{ count:0, f1:{buckets:[{val:A, count:2}]} }" +
+        ",q1a:{ count:0, f1:{buckets:[{val:A, count:1}]} }" +
+        ",r1:{ buckets:[ {val:0.0,count:0,f1:{buckets:[{val:A, count:1}]}}, {val:3.0,count:0,f1:{buckets:[{val:A, count:1}]}} ]  }" +
+        "}"
+    );
+
     // nested query facets on subset (with excludeTags)
     client.testJQ(params(p, "q", "*:*", "fq","{!tag=abc}id:(2 3)"
             , "json.facet", "{ processEmpty:true," +