You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by yo...@apache.org on 2018/05/28 01:03:56 UTC

lucene-solr:master: SOLR-12328: domain change using graph

Repository: lucene-solr
Updated Branches:
  refs/heads/master e5998fcb5 -> f7500a602


SOLR-12328: domain change using graph


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

Branch: refs/heads/master
Commit: f7500a6029fc8284b932635c868e75b3b88c1720
Parents: e5998fc
Author: yonik <yo...@apache.org>
Authored: Sun May 27 21:02:15 2018 -0400
Committer: yonik <yo...@apache.org>
Committed: Sun May 27 21:02:15 2018 -0400

----------------------------------------------------------------------
 solr/CHANGES.txt                                |   3 +
 .../solr/search/facet/FacetProcessor.java       |  13 +-
 .../apache/solr/search/facet/FacetRequest.java  | 119 ++++++++++++++-----
 .../solr/search/facet/TestJsonFacets.java       |  43 +++++++
 4 files changed, 145 insertions(+), 33 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/f7500a60/solr/CHANGES.txt
----------------------------------------------------------------------
diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index d1398bb..cbaf06c 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -134,6 +134,9 @@ New Features
   Equivalent JSON Example : { "#colorfilt" : "color:blue" } 
   (Dmitry Tikhonov, Mikhail Khludnev, yonik)
 
+* SOLR-12328: JSON Facet API: Domain change with graph query.
+  (Daniel Meehl, Kevin Watters, yonik)
+
 Bug Fixes
 ----------------------
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/f7500a60/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 c0625df..6b66cfd 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
@@ -185,7 +185,8 @@ public abstract class FacetProcessor<FacetRequestT extends FacetRequest>  {
     evalFilters();
 
     handleJoinField();
-    
+    handleGraphField();
+
     boolean appliedFilters = handleBlockJoin();
 
     if (this.filter != null && !appliedFilters) {
@@ -261,7 +262,15 @@ public abstract class FacetProcessor<FacetRequestT extends FacetRequest>  {
     final Query domainQuery = freq.domain.joinField.createDomainQuery(fcontext);
     fcontext.base = fcontext.searcher.getDocSet(domainQuery);
   }
-    
+
+  /** modifies the context base if there is a graph field domain change */
+  private void handleGraphField() throws IOException {
+    if (null == freq.domain.graphField) return;
+
+    final Query domainQuery = freq.domain.graphField.createDomainQuery(fcontext);
+    fcontext.base = fcontext.searcher.getDocSet(domainQuery);
+  }
+
   // returns "true" if filters were applied to fcontext.base already
   private boolean handleBlockJoin() throws IOException {
     boolean appliedFilters = false;

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/f7500a60/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 6337bcb..ddf2e98 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
@@ -16,28 +16,20 @@
  */
 package org.apache.solr.search.facet;
 
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.EnumSet;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-
 import org.apache.lucene.search.Query;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.params.FacetParams;
+import org.apache.solr.common.params.SolrParams;
+import org.apache.solr.common.util.NamedList;
 import org.apache.solr.common.util.StrUtils;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.schema.IndexSchema;
-import org.apache.solr.search.DocSet;
-import org.apache.solr.search.JoinQParserPlugin;
-import org.apache.solr.search.FunctionQParser;
-import org.apache.solr.search.FunctionQParserPlugin;
-import org.apache.solr.search.QParser;
-import org.apache.solr.search.QueryContext;
-import org.apache.solr.search.SolrConstantScoreQuery;
-import org.apache.solr.search.SolrIndexSearcher;
-import org.apache.solr.search.SyntaxError;
+import org.apache.solr.search.*;
+import org.apache.solr.search.join.GraphQuery;
+import org.apache.solr.search.join.GraphQueryParser;
+
+import java.io.IOException;
+import java.util.*;
 
 import static org.apache.solr.common.params.CommonParams.SORT;
 import static org.apache.solr.search.facet.FacetRequest.RefineMethod.NONE;
@@ -98,6 +90,7 @@ public abstract class FacetRequest {
      */
     public List<String> excludeTags;
     public JoinField joinField;
+    public GraphField graphField;
     public boolean toParent;
     public boolean toChildren;
     public String parents; // identifies the parent filter... the full set of parent documents for any block join operation
@@ -118,18 +111,18 @@ public abstract class FacetRequest {
     public static class JoinField {
       public final String from;
       public final String to;
-      
+
       private JoinField(String from, String to) {
         assert null != from;
         assert null != to;
-        
+
         this.from = from;
         this.to = to;
       }
 
       /**
        * Given a <code>Domain</code>, and a (JSON) map specifying the configuration for that Domain,
-       * validates if a '<code>join</code>' is specified, and if so creates a <code>JoinField</code> 
+       * validates if a '<code>join</code>' is specified, and if so creates a <code>JoinField</code>
        * and sets it on the <code>Domain</code>.
        *
        * (params must not be null)
@@ -137,46 +130,109 @@ public abstract class FacetRequest {
       public static void createJoinField(FacetRequest.Domain domain, Map<String,Object> domainMap) {
         assert null != domain;
         assert null != domainMap;
-        
+
         final Object queryJoin = domainMap.get("join");
         if (null != queryJoin) {
           // TODO: maybe allow simple string (instead of map) to mean "self join on this field name" ?
           if (! (queryJoin instanceof Map)) {
             throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
-                                    "'join' domain change requires a map containing the 'from' and 'to' fields");
+                "'join' domain change requires a map containing the 'from' and 'to' fields");
           }
           final Map<String,String> join = (Map<String,String>) queryJoin;
-          if (! (join.containsKey("from") && join.containsKey("to") && 
-                 null != join.get("from") && null != join.get("to")) ) {
+          if (! (join.containsKey("from") && join.containsKey("to") &&
+              null != join.get("from") && null != join.get("to")) ) {
             throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
-                                    "'join' domain change requires non-null 'from' and 'to' field names");
+                "'join' domain change requires non-null 'from' and 'to' field names");
           }
           if (2 != join.size()) {
             throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
-                                    "'join' domain change contains unexpected keys, only 'from' and 'to' supported: "
-                                    + join.toString());
+                "'join' domain change contains unexpected keys, only 'from' and 'to' supported: "
+                    + join.toString());
           }
           domain.joinField = new JoinField(join.get("from"), join.get("to"));
         }
       }
 
       /**
-       * Creates a Query that can be used to recompute the new "base" for this domain, realtive to the 
+       * Creates a Query that can be used to recompute the new "base" for this domain, relative to the
        * current base of the FacetContext.
        */
       public Query createDomainQuery(FacetContext fcontext) throws IOException {
         // NOTE: this code lives here, instead of in FacetProcessor.handleJoin, in order to minimize
         // the number of classes that have to know about the number of possible settings on the join
         // (ie: if we add a score mode, or some other modifier to how the joins are done)
-        
+
         final SolrConstantScoreQuery fromQuery = new SolrConstantScoreQuery(fcontext.base.getTopFilter());
         // this shouldn't matter once we're wrapped in a join query, but just in case it ever does...
-        fromQuery.setCache(false); 
+        fromQuery.setCache(false);
 
         return JoinQParserPlugin.createJoinQuery(fromQuery, this.from, this.to);
       }
-      
-      
+
+
+    }
+
+    /** Are we doing a query time graph across other documents */
+    public static class GraphField {
+      public final SolrParams localParams;
+
+      private GraphField(SolrParams localParams) {
+        assert null != localParams;
+
+        this.localParams = localParams;
+      }
+
+      /**
+       * Given a <code>Domain</code>, and a (JSON) map specifying the configuration for that Domain,
+       * validates if a '<code>graph</code>' is specified, and if so creates a <code>GraphField</code>
+       * and sets it on the <code>Domain</code>.
+       *
+       * (params must not be null)
+       */
+      public static void createGraphField(FacetRequest.Domain domain, Map<String,Object> domainMap) {
+        assert null != domain;
+        assert null != domainMap;
+        
+        final Object queryGraph = domainMap.get("graph");
+        if (null != queryGraph) {
+          if (! (queryGraph instanceof Map)) {
+            throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
+                                    "'graph' domain change requires a map containing the 'from' and 'to' fields");
+          }
+          final Map<String,String> graph = (Map<String,String>) queryGraph;
+          if (! (graph.containsKey("from") && graph.containsKey("to") &&
+                 null != graph.get("from") && null != graph.get("to")) ) {
+            throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
+                                    "'graph' domain change requires non-null 'from' and 'to' field names");
+          }
+
+          NamedList<String> graphParams = new NamedList<>();
+          graphParams.addAll(graph);
+          SolrParams localParams = SolrParams.toSolrParams(graphParams);
+          domain.graphField = new GraphField(localParams);
+        }
+      }
+
+      /**
+       * Creates a Query that can be used to recompute the new "base" for this domain, relative to the
+       * current base of the FacetContext.
+       */
+      public Query createDomainQuery(FacetContext fcontext) throws IOException {
+        final SolrConstantScoreQuery fromQuery = new SolrConstantScoreQuery(fcontext.base.getTopFilter());
+        // this shouldn't matter once we're wrapped in a join query, but just in case it ever does...
+        fromQuery.setCache(false);
+
+        GraphQueryParser graphParser = new GraphQueryParser(null, localParams, null, fcontext.req);
+        try {
+          GraphQuery graphQuery = (GraphQuery)graphParser.parse();
+          graphQuery.setQ(fromQuery);
+          return graphQuery;
+        } catch (SyntaxError syntaxError) {
+          throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, syntaxError);
+        }
+      }
+
+
     }
     
   }
@@ -494,6 +550,7 @@ abstract class FacetParser<FacetRequestT extends FacetRequest> {
         }
           
         FacetRequest.Domain.JoinField.createJoinField(domain, domainMap);
+        FacetRequest.Domain.GraphField.createGraphField(domain, domainMap);
 
         Object filterOrList = domainMap.get("filter");
         if (filterOrList != null) {

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/f7500a60/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 4402d78..e19bb93 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
@@ -496,6 +496,49 @@ public class TestJsonFacets extends SolrTestCaseHS {
              );
   }
   
+  public void testDomainGraph() throws Exception {
+    Client client = Client.localClient();
+    indexSimple(client);
+
+    // should be the same as join self
+    assertJQ(req("q", "*:*", "rows", "0",
+        "json.facet", ""
+            + "{x: { type: terms, field: 'num_i', "
+            + "      facet: { y: { domain: { graph: { from: 'cat_s', to: 'cat_s' } }, "
+            + "                    type: terms, field: 'where_s' "
+            + "                  } } } }")
+        , "facets=={count:6, x:{ buckets:["
+            + "   { val:-5, count:2, "
+            + "     y : { buckets:[{ val:'NJ', count:2 }, { val:'NY', count:1 } ] } }, "
+            + "   { val:2, count:1, "
+            + "     y : { buckets:[{ val:'NJ', count:1 }, { val:'NY', count:1 } ] } }, "
+            + "   { val:3, count:1, "
+            + "     y : { buckets:[{ val:'NJ', count:1 }, { val:'NY', count:1 } ] } }, "
+            + "   { val:7, count:1, "
+            + "     y : { buckets:[{ val:'NJ', count:2 }, { val:'NY', count:1 } ] } } ] } }"
+    );
+
+    // This time, test with a traversalFilter
+    // should be the same as join self
+    assertJQ(req("q", "*:*", "rows", "0",
+        "json.facet", ""
+            + "{x: { type: terms, field: 'num_i', "
+            + "      facet: { y: { domain: { graph: { from: 'cat_s', to: 'cat_s', traversalFilter: 'where_s:NY' } }, "
+            + "                    type: terms, field: 'where_s' "
+            + "                  } } } }")
+        , "facets=={count:6, x:{ buckets:["
+            + "   { val:-5, count:2, "
+            + "     y : { buckets:[{ val:'NJ', count:1 }, { val:'NY', count:1 } ] } }, "
+            + "   { val:2, count:1, "
+            + "     y : { buckets:[{ val:'NY', count:1 } ] } }, "
+            + "   { val:3, count:1, "
+            + "     y : { buckets:[{ val:'NJ', count:1 }, { val:'NY', count:1 } ] } }, "
+            + "   { val:7, count:1, "
+            + "     y : { buckets:[{ val:'NJ', count:1  }, { val:'NY', count:1 } ] } } ] } }"
+    );
+  }
+
+
   public void testNestedJoinDomain() throws Exception {
     Client client = Client.localClient();