lucene-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From sha...@apache.org
Subject svn commit: r1689802 [1/3] - in /lucene/dev/trunk/solr: ./ core/src/java/org/apache/solr/handler/ core/src/java/org/apache/solr/handler/component/ core/src/java/org/apache/solr/request/ core/src/java/org/apache/solr/util/ core/src/test/org/apache/solr/...
Date Wed, 08 Jul 2015 07:46:10 GMT
Author: shalin
Date: Wed Jul  8 07:46:09 2015
New Revision: 1689802

URL: http://svn.apache.org/r1689802
Log:
SOLR-4212: SOLR-6353: Let facet queries and facet ranges hang off of pivots

Added:
    lucene/dev/trunk/solr/core/src/java/org/apache/solr/handler/component/DateFacetProcessor.java   (with props)
    lucene/dev/trunk/solr/core/src/java/org/apache/solr/handler/component/RangeFacetProcessor.java   (with props)
    lucene/dev/trunk/solr/core/src/java/org/apache/solr/handler/component/RangeFacetRequest.java   (with props)
Modified:
    lucene/dev/trunk/solr/CHANGES.txt
    lucene/dev/trunk/solr/core/src/java/org/apache/solr/handler/MoreLikeThisHandler.java
    lucene/dev/trunk/solr/core/src/java/org/apache/solr/handler/component/FacetComponent.java
    lucene/dev/trunk/solr/core/src/java/org/apache/solr/handler/component/PivotFacetHelper.java
    lucene/dev/trunk/solr/core/src/java/org/apache/solr/handler/component/PivotFacetProcessor.java
    lucene/dev/trunk/solr/core/src/java/org/apache/solr/handler/component/PivotFacetValue.java
    lucene/dev/trunk/solr/core/src/java/org/apache/solr/request/IntervalFacets.java
    lucene/dev/trunk/solr/core/src/java/org/apache/solr/request/SimpleFacets.java
    lucene/dev/trunk/solr/core/src/java/org/apache/solr/util/PivotListEntry.java
    lucene/dev/trunk/solr/core/src/test/org/apache/solr/handler/component/DistributedFacetPivotLargeTest.java
    lucene/dev/trunk/solr/core/src/test/org/apache/solr/handler/component/DistributedFacetPivotSmallTest.java
    lucene/dev/trunk/solr/solrj/src/java/org/apache/solr/client/solrj/response/PivotField.java
    lucene/dev/trunk/solr/solrj/src/java/org/apache/solr/client/solrj/response/QueryResponse.java
    lucene/dev/trunk/solr/solrj/src/test/org/apache/solr/client/solrj/SolrExampleTests.java

Modified: lucene/dev/trunk/solr/CHANGES.txt
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/CHANGES.txt?rev=1689802&r1=1689801&r2=1689802&view=diff
==============================================================================
--- lucene/dev/trunk/solr/CHANGES.txt (original)
+++ lucene/dev/trunk/solr/CHANGES.txt Wed Jul  8 07:46:09 2015
@@ -155,6 +155,9 @@ New Features
 
 * SOLR-7651: New response format added wt=smile (noble)
 
+* SOLR-4212: SOLR-6353: Let facet queries and facet ranges hang off of pivots. Example:
+  facet.range={!tag=r1}price&facet.query={!tag=q1}somequery&facet.pivot={!range=r1 query=q1}category,manufacturer
+
 Bug Fixes
 ----------------------
 

Modified: lucene/dev/trunk/solr/core/src/java/org/apache/solr/handler/MoreLikeThisHandler.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/java/org/apache/solr/handler/MoreLikeThisHandler.java?rev=1689802&r1=1689801&r2=1689802&view=diff
==============================================================================
--- lucene/dev/trunk/solr/core/src/java/org/apache/solr/handler/MoreLikeThisHandler.java (original)
+++ lucene/dev/trunk/solr/core/src/java/org/apache/solr/handler/MoreLikeThisHandler.java Wed Jul  8 07:46:09 2015
@@ -37,6 +37,10 @@ import org.apache.solr.common.util.Conte
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.common.util.SimpleOrderedMap;
 import org.apache.solr.core.SolrCore;
+import org.apache.solr.handler.component.FacetComponent;
+import org.apache.solr.handler.component.SpatialHeatmapFacets;
+import org.apache.solr.handler.component.DateFacetProcessor;
+import org.apache.solr.handler.component.RangeFacetProcessor;
 import org.apache.solr.request.SimpleFacets;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
@@ -231,7 +235,7 @@ public class MoreLikeThisHandler extends
             rsp.add("facet_counts", null);
           } else {
             SimpleFacets f = new SimpleFacets(req, mltDocs.docSet, params);
-            rsp.add("facet_counts", f.getFacetCounts());
+            rsp.add("facet_counts", FacetComponent.getFacetCounts(f));
           }
         }
         boolean dbg = req.getParams().getBool(CommonParams.DEBUG_QUERY, false);

Added: lucene/dev/trunk/solr/core/src/java/org/apache/solr/handler/component/DateFacetProcessor.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/java/org/apache/solr/handler/component/DateFacetProcessor.java?rev=1689802&view=auto
==============================================================================
--- lucene/dev/trunk/solr/core/src/java/org/apache/solr/handler/component/DateFacetProcessor.java (added)
+++ lucene/dev/trunk/solr/core/src/java/org/apache/solr/handler/component/DateFacetProcessor.java Wed Jul  8 07:46:09 2015
@@ -0,0 +1,253 @@
+package org.apache.solr.handler.component;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.io.IOException;
+import java.util.Date;
+import java.util.EnumSet;
+import java.util.Set;
+
+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.SimpleOrderedMap;
+import org.apache.solr.request.SimpleFacets;
+import org.apache.solr.request.SolrQueryRequest;
+import org.apache.solr.schema.IndexSchema;
+import org.apache.solr.schema.SchemaField;
+import org.apache.solr.schema.TrieDateField;
+import org.apache.solr.search.DocSet;
+import org.apache.solr.search.SyntaxError;
+import org.apache.solr.util.DateMathParser;
+
+/**
+ * Process date facets
+ *
+ * @deprecated the whole date faceting feature is deprecated. Use range facets instead which can
+ * already work with dates.
+ */
+@Deprecated
+public class DateFacetProcessor extends SimpleFacets {
+  public DateFacetProcessor(SolrQueryRequest req, DocSet docs, SolrParams params, ResponseBuilder rb) {
+    super(req, docs, params, rb);
+  }
+
+  /**
+   * @deprecated Use getFacetRangeCounts which is more generalized
+   */
+  @Deprecated
+  public void getFacetDateCounts(String dateFacet, NamedList<Object> resOuter)
+      throws IOException {
+
+    final IndexSchema schema = searcher.getSchema();
+
+    ParsedParams parsed = null;
+    try {
+      parsed = parseParams(FacetParams.FACET_DATE, dateFacet);
+    } catch (SyntaxError syntaxError) {
+      throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, syntaxError);
+    }
+
+    final SolrParams params = parsed.params;
+    final SolrParams required = parsed.required;
+    final String key = parsed.key;
+    final String f = parsed.facetValue;
+
+    final NamedList<Object> resInner = new SimpleOrderedMap<>();
+    resOuter.add(key, resInner);
+    final SchemaField sf = schema.getField(f);
+    if (!(sf.getType() instanceof TrieDateField)) {
+      throw new SolrException
+          (SolrException.ErrorCode.BAD_REQUEST,
+              "Can not date facet on a field which is not a TrieDateField: " + f);
+    }
+    final TrieDateField ft = (TrieDateField) sf.getType();
+    final String startS
+        = required.getFieldParam(f, FacetParams.FACET_DATE_START);
+    final Date start;
+    try {
+      start = ft.parseMath(null, startS);
+    } catch (SolrException e) {
+      throw new SolrException
+          (SolrException.ErrorCode.BAD_REQUEST,
+              "date facet 'start' is not a valid Date string: " + startS, e);
+    }
+    final String endS
+        = required.getFieldParam(f, FacetParams.FACET_DATE_END);
+    Date end; // not final, hardend may change this
+    try {
+      end = ft.parseMath(null, endS);
+    } catch (SolrException e) {
+      throw new SolrException
+          (SolrException.ErrorCode.BAD_REQUEST,
+              "date facet 'end' is not a valid Date string: " + endS, e);
+    }
+
+    if (end.before(start)) {
+      throw new SolrException
+          (SolrException.ErrorCode.BAD_REQUEST,
+              "date facet 'end' comes before 'start': " + endS + " < " + startS);
+    }
+
+    final String gap = required.getFieldParam(f, FacetParams.FACET_DATE_GAP);
+    final DateMathParser dmp = new DateMathParser();
+
+    final int minCount = params.getFieldInt(f, FacetParams.FACET_MINCOUNT, 0);
+
+    String[] iStrs = params.getFieldParams(f, FacetParams.FACET_DATE_INCLUDE);
+    // Legacy support for default of [lower,upper,edge] for date faceting
+    // this is not handled by FacetRangeInclude.parseParam because
+    // range faceting has differnet defaults
+    final EnumSet<FacetParams.FacetRangeInclude> include =
+        (null == iStrs || 0 == iStrs.length) ?
+            EnumSet.of(FacetParams.FacetRangeInclude.LOWER,
+                FacetParams.FacetRangeInclude.UPPER,
+                FacetParams.FacetRangeInclude.EDGE)
+            : FacetParams.FacetRangeInclude.parseParam(iStrs);
+
+    try {
+      Date low = start;
+      while (low.before(end)) {
+        dmp.setNow(low);
+        String label = ft.toExternal(low);
+
+        Date high = dmp.parseMath(gap);
+        if (end.before(high)) {
+          if (params.getFieldBool(f, FacetParams.FACET_DATE_HARD_END, false)) {
+            high = end;
+          } else {
+            end = high;
+          }
+        }
+        if (high.before(low)) {
+          throw new SolrException
+              (SolrException.ErrorCode.BAD_REQUEST,
+                  "date facet infinite loop (is gap negative?)");
+        }
+        if (high.equals(low)) {
+          throw new SolrException
+              (SolrException.ErrorCode.BAD_REQUEST,
+                  "date facet infinite loop: gap is effectively zero");
+        }
+        final boolean includeLower =
+            (include.contains(FacetParams.FacetRangeInclude.LOWER) ||
+                (include.contains(FacetParams.FacetRangeInclude.EDGE) && low.equals(start)));
+        final boolean includeUpper =
+            (include.contains(FacetParams.FacetRangeInclude.UPPER) ||
+                (include.contains(FacetParams.FacetRangeInclude.EDGE) && high.equals(end)));
+
+        final int count = rangeCount(parsed, sf, low, high, includeLower, includeUpper);
+        if (count >= minCount) {
+          resInner.add(label, count);
+        }
+        low = high;
+      }
+    } catch (java.text.ParseException e) {
+      throw new SolrException
+          (SolrException.ErrorCode.BAD_REQUEST,
+              "date facet 'gap' is not a valid Date Math string: " + gap, e);
+    }
+
+    // explicitly return the gap and end so all the counts
+    // (including before/after/between) are meaningful - even if mincount
+    // has removed the neighboring ranges
+    resInner.add("gap", gap);
+    resInner.add("start", start);
+    resInner.add("end", end);
+
+    final String[] othersP =
+        params.getFieldParams(f, FacetParams.FACET_DATE_OTHER);
+    if (null != othersP && 0 < othersP.length) {
+      final Set<FacetParams.FacetRangeOther> others = EnumSet.noneOf(FacetParams.FacetRangeOther.class);
+
+      for (final String o : othersP) {
+        others.add(FacetParams.FacetRangeOther.get(o));
+      }
+
+      // no matter what other values are listed, we don't do
+      // anything if "none" is specified.
+      if (!others.contains(FacetParams.FacetRangeOther.NONE)) {
+        boolean all = others.contains(FacetParams.FacetRangeOther.ALL);
+
+        if (all || others.contains(FacetParams.FacetRangeOther.BEFORE)) {
+          // include upper bound if "outer" or if first gap doesn't already include it
+          resInner.add(FacetParams.FacetRangeOther.BEFORE.toString(),
+              rangeCount(parsed, sf, null, start,
+                  false,
+                  (include.contains(FacetParams.FacetRangeInclude.OUTER) ||
+                      (!(include.contains(FacetParams.FacetRangeInclude.LOWER) ||
+                          include.contains(FacetParams.FacetRangeInclude.EDGE))))));
+        }
+        if (all || others.contains(FacetParams.FacetRangeOther.AFTER)) {
+          // include lower bound if "outer" or if last gap doesn't already include it
+          resInner.add(FacetParams.FacetRangeOther.AFTER.toString(),
+              rangeCount(parsed, sf, end, null,
+                  (include.contains(FacetParams.FacetRangeInclude.OUTER) ||
+                      (!(include.contains(FacetParams.FacetRangeInclude.UPPER) ||
+                          include.contains(FacetParams.FacetRangeInclude.EDGE)))),
+                  false));
+        }
+        if (all || others.contains(FacetParams.FacetRangeOther.BETWEEN)) {
+          resInner.add(FacetParams.FacetRangeOther.BETWEEN.toString(),
+              rangeCount(parsed, sf, start, end,
+                  (include.contains(FacetParams.FacetRangeInclude.LOWER) ||
+                      include.contains(FacetParams.FacetRangeInclude.EDGE)),
+                  (include.contains(FacetParams.FacetRangeInclude.UPPER) ||
+                      include.contains(FacetParams.FacetRangeInclude.EDGE))));
+        }
+      }
+    }
+  }
+
+  /**
+   * Returns a list of value constraints and the associated facet counts
+   * for each facet date field, range, and interval specified in the
+   * SolrParams
+   *
+   * @see FacetParams#FACET_DATE
+   * @deprecated Use getFacetRangeCounts which is more generalized
+   */
+  @Deprecated
+  public NamedList<Object> getFacetDateCounts()
+      throws IOException {
+
+    final NamedList<Object> resOuter = new SimpleOrderedMap<>();
+    final String[] fields = global.getParams(FacetParams.FACET_DATE);
+
+    if (null == fields || 0 == fields.length) return resOuter;
+
+    for (String f : fields) {
+      getFacetDateCounts(f, resOuter);
+    }
+
+    return resOuter;
+  }
+
+  /**
+   * @deprecated Use rangeCount(SchemaField,String,String,boolean,boolean) which is more generalized
+   */
+  @Deprecated
+  protected int rangeCount(ParsedParams parsed, SchemaField sf, Date low, Date high,
+                           boolean iLow, boolean iHigh) throws IOException {
+    Query rangeQ = ((TrieDateField) (sf.getType())).getRangeQuery(null, sf, low, high, iLow, iHigh);
+    return searcher.numDocs(rangeQ, parsed.docs);
+  }
+}
+

Modified: lucene/dev/trunk/solr/core/src/java/org/apache/solr/handler/component/FacetComponent.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/java/org/apache/solr/handler/component/FacetComponent.java?rev=1689802&r1=1689801&r2=1689802&view=diff
==============================================================================
--- lucene/dev/trunk/solr/core/src/java/org/apache/solr/handler/component/FacetComponent.java (original)
+++ lucene/dev/trunk/solr/core/src/java/org/apache/solr/handler/component/FacetComponent.java Wed Jul  8 07:46:09 2015
@@ -32,12 +32,13 @@ import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 
+import org.apache.commons.lang.ArrayUtils;
+import org.apache.commons.lang.StringUtils;
 import org.apache.lucene.util.FixedBitSet;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.SolrException.ErrorCode;
 import org.apache.solr.common.params.CommonParams;
 import org.apache.solr.common.params.FacetParams;
-import org.apache.solr.common.params.FacetParams.FacetRangeOther;
 import org.apache.solr.common.params.ModifiableSolrParams;
 import org.apache.solr.common.params.ShardParams;
 import org.apache.solr.common.params.SolrParams;
@@ -45,6 +46,7 @@ import org.apache.solr.common.util.Named
 import org.apache.solr.common.util.SimpleOrderedMap;
 import org.apache.solr.common.util.StrUtils;
 import org.apache.solr.request.SimpleFacets;
+import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.schema.FieldType;
 import org.apache.solr.search.QueryParsing;
 import org.apache.solr.search.SyntaxError;
@@ -61,47 +63,199 @@ public class FacetComponent extends Sear
   public static Logger log = LoggerFactory.getLogger(FacetComponent.class);
   
   public static final String COMPONENT_NAME = "facet";
-  
-  private static final String PIVOT_KEY = "facet_pivot";
-  private static final String PIVOT_REFINE_PREFIX = "{!"+PivotFacet.REFINE_PARAM+"=";
 
+  public static final String FACET_QUERY_KEY = "facet_queries";
+  public static final String FACET_FIELD_KEY = "facet_fields";
+  public static final String FACET_DATE_KEY = "facet_dates";
+  public static final String FACET_RANGES_KEY = "facet_ranges";
+  public static final String FACET_INTERVALS_KEY = "facet_intervals";
 
+  private static final String PIVOT_KEY = "facet_pivot";
+  private static final String PIVOT_REFINE_PREFIX = "{!"+PivotFacet.REFINE_PARAM+"=";
 
   @Override
   public void prepare(ResponseBuilder rb) throws IOException {
     if (rb.req.getParams().getBool(FacetParams.FACET, false)) {
       rb.setNeedDocSet(true);
       rb.doFacets = true;
-    }
-  }
-  
-  /**
-   * Actually run the query
-   */
-  @Override
-  public void process(ResponseBuilder rb) throws IOException {
 
-    //SolrParams params = rb.req.getParams();
-    if (rb.doFacets) {
+      // Deduplicate facet params
       ModifiableSolrParams params = new ModifiableSolrParams();
       SolrParams origParams = rb.req.getParams();
       Iterator<String> iter = origParams.getParameterNamesIterator();
       while (iter.hasNext()) {
         String paramName = iter.next();
         // Deduplicate the list with LinkedHashSet, but _only_ for facet params.
-        if (paramName.startsWith(FacetParams.FACET) == false) {
+        if (!paramName.startsWith(FacetParams.FACET)) {
           params.add(paramName, origParams.getParams(paramName));
           continue;
         }
         HashSet<String> deDupe = new LinkedHashSet<>(Arrays.asList(origParams.getParams(paramName)));
         params.add(paramName, deDupe.toArray(new String[deDupe.size()]));
+
+      }
+      rb.req.setParams(params);
+
+      // Initialize context
+      FacetContext.initContext(rb);
+    }
+  }
+
+  /**
+   * Encapsulates facet ranges and facet queries such that their parameters
+   * are parsed and cached for efficient re-use.
+   * <p>
+   * An instance of this class is initialized and kept in the request context via the static
+   * method {@link org.apache.solr.handler.component.FacetComponent.FacetContext#initContext(ResponseBuilder)} and
+   * can be retrieved via {@link org.apache.solr.handler.component.FacetComponent.FacetContext#getFacetContext(SolrQueryRequest)}
+   * <p>
+   * This class is used exclusively in a single-node context (i.e. non distributed requests or an individual shard
+   * request). Also see {@link org.apache.solr.handler.component.FacetComponent.FacetInfo} which is
+   * dedicated exclusively for merging responses from multiple shards and plays no role during computation of facet
+   * counts in a single node request.
+   *
+   * <b>This API is experimental and subject to change</b>
+   *
+   * @see org.apache.solr.handler.component.FacetComponent.FacetInfo
+   */
+  public static class FacetContext {
+    private static final String FACET_CONTEXT_KEY = "_facet.context";
+
+    private final List<RangeFacetRequest> allRangeFacets; // init in constructor
+    private final List<FacetBase> allQueryFacets; // init in constructor
+
+    private final Map<String, List<RangeFacetRequest>> taggedRangeFacets;
+    private final Map<String, List<FacetBase>> taggedQueryFacets;
+
+    /**
+     * Initializes FacetContext using request parameters and saves it in the request
+     * context which can be retrieved via {@link #getFacetContext(SolrQueryRequest)}
+     *
+     * @param rb the ResponseBuilder object from which the request parameters are read
+     *           and to which the FacetContext object is saved.
+     */
+    public static void initContext(ResponseBuilder rb)  {
+      // Parse facet queries and ranges and put them in the request
+      // context so that they can be hung under pivots if needed without re-parsing
+      List<RangeFacetRequest> facetRanges = null;
+      List<FacetBase> facetQueries = null;
+
+      String[] ranges = rb.req.getParams().getParams(FacetParams.FACET_RANGE);
+      if (ranges != null) {
+        facetRanges = new ArrayList<>(ranges.length);
+        for (String range : ranges) {
+          RangeFacetRequest rangeFacetRequest = new RangeFacetRequest(rb, range);
+          facetRanges.add(rangeFacetRequest);
+        }
       }
 
+      String[] queries = rb.req.getParams().getParams(FacetParams.FACET_QUERY);
+      if (queries != null)  {
+        facetQueries = new ArrayList<>();
+        for (String query : queries) {
+          facetQueries.add(new FacetBase(rb, FacetParams.FACET_QUERY, query));
+        }
+      }
+
+      rb.req.getContext().put(FACET_CONTEXT_KEY, new FacetContext(facetRanges, facetQueries));
+    }
+
+    private FacetContext(List<RangeFacetRequest> allRangeFacets, List<FacetBase> allQueryFacets) {
+      // avoid NPEs, set to empty list if parameters are null
+      this.allRangeFacets = allRangeFacets == null ? Collections.emptyList() : allRangeFacets;
+      this.allQueryFacets = allQueryFacets == null ? Collections.emptyList() : allQueryFacets;
+
+      taggedRangeFacets = new HashMap<>();
+      for (RangeFacetRequest rf : this.allRangeFacets) {
+        for (String tag : rf.getTags()) {
+          List<RangeFacetRequest> list = taggedRangeFacets.get(tag);
+          if (list == null) {
+            list = new ArrayList<>(1); // typically just one object
+            taggedRangeFacets.put(tag, list);
+          }
+          list.add(rf);
+        }
+      }
+
+      taggedQueryFacets = new HashMap<>();
+      for (FacetBase qf : this.allQueryFacets) {
+        for (String tag : qf.getTags()) {
+          List<FacetBase> list = taggedQueryFacets.get(tag);
+          if (list == null) {
+            list = new ArrayList<>(1);
+            taggedQueryFacets.put(tag, list);
+          }
+          list.add(qf);
+        }
+      }
+    }
+
+    /**
+     * Return the {@link org.apache.solr.handler.component.FacetComponent.FacetContext} instance
+     * cached in the request context.
+     *
+     * @param req the {@link SolrQueryRequest}
+     * @return the cached FacetContext instance
+     * @throws IllegalStateException if no cached FacetContext instance is found in the request context
+     */
+    public static FacetContext getFacetContext(SolrQueryRequest req) throws IllegalStateException {
+      FacetContext result = (FacetContext) req.getContext().get(FACET_CONTEXT_KEY);
+      if (null == result) {
+        throw new IllegalStateException("FacetContext can't be accessed before it's initialized in request context");
+      }
+      return result;
+    }
+
+    /**
+     * @return a {@link List} of {@link RangeFacetRequest} objects each representing a facet.range to be
+     * computed. Returns an empty list if no facet.range were requested.
+     */
+    public List<RangeFacetRequest> getAllRangeFacetRequests() {
+      return allRangeFacets;
+    }
+
+    /**
+     * @return a {@link List} of {@link org.apache.solr.handler.component.FacetComponent.FacetBase} objects
+     * each representing a facet.query to be computed. Returns an empty list of no facet.query were requested.
+     */
+    public List<FacetBase> getAllQueryFacets() {
+      return allQueryFacets;
+    }
+
+    /**
+     * @param tag a String tag usually specified via local param on a facet.pivot
+     * @return a list of {@link RangeFacetRequest} objects which have been tagged with the given tag.
+     * Returns an empty list if none found.
+     */
+    public List<RangeFacetRequest> getRangeFacetRequestsForTag(String tag) {
+      List<RangeFacetRequest> list = taggedRangeFacets.get(tag);
+      return list == null ? Collections.emptyList() : list;
+    }
+
+    /**
+     * @param tag a String tag usually specified via local param on a facet.pivot
+     * @return a list of {@link org.apache.solr.handler.component.FacetComponent.FacetBase} objects which have been
+     * tagged with the given tag. Returns and empty List if none found.
+     */
+    public List<FacetBase> getQueryFacetsForTag(String tag) {
+      List<FacetBase> list = taggedQueryFacets.get(tag);
+      return list == null ? Collections.emptyList() : list;
+    }
+  }
+  
+  /**
+   * Actually run the query
+   */
+  @Override
+  public void process(ResponseBuilder rb) throws IOException {
+
+    if (rb.doFacets) {
+      SolrParams params = rb.req.getParams();
       SimpleFacets f = new SimpleFacets(rb.req, rb.getResults().docSet, params, rb);
-      
-      NamedList<Object> counts = f.getFacetCounts();
+
+      NamedList<Object> counts = FacetComponent.getFacetCounts(f);
       String[] pivots = params.getParams(FacetParams.FACET_PIVOT);
-      if (pivots != null && pivots.length > 0) {
+      if (!ArrayUtils.isEmpty(pivots)) {
         PivotFacetProcessor pivotProcessor 
           = new PivotFacetProcessor(rb.req, rb.getResults().docSet, params, rb);
         SimpleOrderedMap<List<NamedList<Object>>> v 
@@ -110,11 +264,46 @@ public class FacetComponent extends Sear
           counts.add(PIVOT_KEY, v);
         }
       }
-      
+
       rb.rsp.add("facet_counts", counts);
     }
   }
-  
+
+  /**
+   * Looks at various Params to determining if any simple Facet Constraint count
+   * computations are desired.
+   *
+   * @see SimpleFacets#getFacetQueryCounts
+   * @see SimpleFacets#getFacetFieldCounts
+   * @see DateFacetProcessor#getFacetDateCounts
+   * @see RangeFacetProcessor#getFacetRangeCounts
+   * @see RangeFacetProcessor#getFacetIntervalCounts
+   * @see FacetParams#FACET
+   * @return a NamedList of Facet Count info or null
+   */
+  public static NamedList<Object> getFacetCounts(SimpleFacets simpleFacets) {
+    // if someone called this method, benefit of the doubt: assume true
+    if (!simpleFacets.getGlobalParams().getBool(FacetParams.FACET, true))
+      return null;
+
+    DateFacetProcessor dateFacetProcessor = new DateFacetProcessor(simpleFacets.getRequest(), simpleFacets.getDocsOrig(), simpleFacets.getGlobalParams(), simpleFacets.getResponseBuilder());
+    RangeFacetProcessor rangeFacetProcessor = new RangeFacetProcessor(simpleFacets.getRequest(), simpleFacets.getDocsOrig(), simpleFacets.getGlobalParams(), simpleFacets.getResponseBuilder());
+    NamedList<Object> counts = new SimpleOrderedMap<>();
+    try {
+      counts.add(FACET_QUERY_KEY, simpleFacets.getFacetQueryCounts());
+      counts.add(FACET_FIELD_KEY, simpleFacets.getFacetFieldCounts());
+      counts.add(FACET_DATE_KEY, dateFacetProcessor.getFacetDateCounts());
+      counts.add(FACET_RANGES_KEY, rangeFacetProcessor.getFacetRangeCounts());
+      counts.add(FACET_INTERVALS_KEY, simpleFacets.getFacetIntervalCounts());
+      counts.add(SpatialHeatmapFacets.RESPONSE_KEY, simpleFacets.getHeatmapCounts());
+    } catch (IOException e) {
+      throw new SolrException(ErrorCode.SERVER_ERROR, e);
+    } catch (SyntaxError e) {
+      throw new SolrException(ErrorCode.BAD_REQUEST, e);
+    }
+    return counts;
+  }
+
   private static final String commandPrefix = "{!" + CommonParams.TERMS + "=$";
   
   @Override
@@ -514,7 +703,7 @@ public class FacetComponent extends Sear
           qf.count += count;
         }
       }
-      
+
       // step through each facet.field, adding results from this shard
       NamedList facet_fields = (NamedList) facet_counts.get("facet_fields");
       
@@ -528,7 +717,12 @@ public class FacetComponent extends Sear
       doDistribDates(fi, facet_counts);
 
       // Distributed facet_ranges
-      doDistribRanges(fi, facet_counts);
+      @SuppressWarnings("unchecked")
+      SimpleOrderedMap<SimpleOrderedMap<Object>> rangesFromShard = (SimpleOrderedMap<SimpleOrderedMap<Object>>)
+          facet_counts.get("facet_ranges");
+      if (rangesFromShard != null)  {
+        RangeFacetRequest.DistribRangeFacet.mergeFacetRangesFromShardResponse(fi.rangeFacets, rangesFromShard);
+      }
 
       // Distributed facet_intervals
       doDistribIntervals(fi, facet_counts);
@@ -655,43 +849,19 @@ public class FacetComponent extends Sear
     }
 
     FacetInfo fi = rb._facetInfo;
-
-    @SuppressWarnings("unchecked")
-    SimpleOrderedMap<SimpleOrderedMap<Object>> facet_ranges =
-        (SimpleOrderedMap<SimpleOrderedMap<Object>>)
-            fi.rangeFacets;
-
-    if (facet_ranges == null) {
-      return;
-    }
-
-    // go through each facet_range
-    for (Map.Entry<String, SimpleOrderedMap<Object>> entry : facet_ranges) {
-      boolean replace = false;
+    for (Map.Entry<String, RangeFacetRequest.DistribRangeFacet> entry : fi.rangeFacets.entrySet()) {
       final String field = entry.getKey();
+      final RangeFacetRequest.DistribRangeFacet rangeFacet = entry.getValue();
+
       int minCount = rb.req.getParams().getFieldInt(field, FacetParams.FACET_MINCOUNT, 0);
       if (minCount == 0) {
         continue;
       }
 
-      @SuppressWarnings("unchecked")
-      NamedList<Integer> vals
-          = (NamedList<Integer>) facet_ranges.get(field).get("counts");
-      NamedList newList = new NamedList();
-      for (Map.Entry<String, Integer> pair : vals) {
-        if (pair.getValue() >= minCount) {
-          newList.add(pair.getKey(), pair.getValue());
-        } else {
-          log.trace("Removing facet/key: " + pair.getKey() + "/" + pair.getValue().toString() + " mincount=" + minCount);
-          replace = true;
-        }
-      }
-      if (replace) {
-        vals.clear();
-        vals.addAll(newList);
-      }
+      rangeFacet.removeRangeFacetsUnderLimits(minCount);
     }
   }
+
   private void removeFieldFacetsUnderLimits(ResponseBuilder rb) {
     if (rb.stage != ResponseBuilder.STAGE_DONE) {
       return;
@@ -759,62 +929,6 @@ public class FacetComponent extends Sear
     }
   }
 
-  private final static String[] OTHER_KEYS = new String[]{FacetRangeOther.BEFORE.toString(), FacetRangeOther.BETWEEN.toString(), FacetRangeOther.AFTER.toString()};
-  // The implementation below uses the first encountered shard's
-  // facet_ranges as the basis for subsequent shards' data to be merged.
-  private void doDistribRanges(FacetInfo fi, NamedList facet_counts) {
-    @SuppressWarnings("unchecked")
-    SimpleOrderedMap<SimpleOrderedMap<Object>> facet_ranges =
-      (SimpleOrderedMap<SimpleOrderedMap<Object>>)
-      facet_counts.get("facet_ranges");
-
-    if (facet_ranges != null) {
-
-      // go through each facet_range
-      for (Map.Entry<String,SimpleOrderedMap<Object>> entry : facet_ranges) {
-        final String field = entry.getKey();
-        SimpleOrderedMap<Object> fieldMap = fi.rangeFacets.get(field); 
-        if (fieldMap == null) {
-          // first time we've seen this field, no merging
-          fi.rangeFacets.add(field, entry.getValue());
-
-        } else {
-          // not the first time, merge current field counts
-
-          @SuppressWarnings("unchecked")
-          NamedList<Integer> shardFieldValues
-            = (NamedList<Integer>) entry.getValue().get("counts");
-
-          @SuppressWarnings("unchecked")
-          NamedList<Integer> existFieldValues
-            = (NamedList<Integer>) fieldMap.get("counts");
-
-          for (Map.Entry<String,Integer> existPair : existFieldValues) {
-            final String key = existPair.getKey();
-            // can be null if inconsistencies in shards responses
-            Integer newValue = shardFieldValues.get(key);
-            if (null != newValue) {
-              Integer oldValue = existPair.getValue();
-              existPair.setValue(oldValue + newValue);
-            }
-          }
-          
-          // merge before/between/after if they exist
-          for (String otherKey:OTHER_KEYS) {
-            Integer shardValue = (Integer)entry.getValue().get(otherKey);
-            if (shardValue != null && shardValue > 0) {
-              Integer existingValue = (Integer)fieldMap.get(otherKey);
-              // shouldn't be null
-              int idx = fieldMap.indexOf(otherKey, 0);
-              fieldMap.setVal(idx, existingValue + shardValue);
-            }
-          }
-          
-        }
-      }
-    }
-  }
-
   //
   // The implementation below uses the first encountered shard's
   // facet_dates as the basis for subsequent shards' data to be merged.
@@ -1046,7 +1160,15 @@ public class FacetComponent extends Sear
     }
 
     facet_counts.add("facet_dates", fi.dateFacets);
-    facet_counts.add("facet_ranges", fi.rangeFacets);
+
+    SimpleOrderedMap<SimpleOrderedMap<Object>> rangeFacetOutput = new SimpleOrderedMap<>();
+    for (Map.Entry<String, RangeFacetRequest.DistribRangeFacet> entry : fi.rangeFacets.entrySet()) {
+      String key = entry.getKey();
+      RangeFacetRequest.DistribRangeFacet value = entry.getValue();
+      rangeFacetOutput.add(key, value.rangeFacet);
+    }
+    facet_counts.add("facet_ranges", rangeFacetOutput);
+
     facet_counts.add("facet_intervals", fi.intervalFacets);
     facet_counts.add(SpatialHeatmapFacets.RESPONSE_KEY,
         SpatialHeatmapFacets.distribFinish(fi.heatmapFacets, rb));
@@ -1077,11 +1199,23 @@ public class FacetComponent extends Sear
   }
 
   // use <int> tags for smaller facet counts (better back compatibility)
-  private Number num(long val) {
+
+  /**
+   * @param val a primitive long value
+   * @return an {@link Integer} if the value of the argument is less than {@link Integer#MAX_VALUE}
+   * else a @{link java.lang.Long}
+   */
+  static Number num(long val) {
    if (val < Integer.MAX_VALUE) return (int)val;
    else return val;
   }
-  private Number num(Long val) {
+
+  /**
+   * @param val a {@link java.lang.Long} value
+   * @return an {@link Integer} if the value of the argument is less than {@link Integer#MAX_VALUE}
+   * else a @{link java.lang.Long}
+   */
+  static Number num(Long val) {
     if (val.longValue() < Integer.MAX_VALUE) return val.intValue();
     else return val;
   }
@@ -1102,7 +1236,16 @@ public class FacetComponent extends Sear
   }
 
   /**
+   * This class is used exclusively for merging results from each shard
+   * in a distributed facet request. It plays no role in the computation
+   * of facet counts inside a single node.
+   *
+   * A related class {@link org.apache.solr.handler.component.FacetComponent.FacetContext}
+   * exists for assisting computation inside a single node.
+   *
    * <b>This API is experimental and subject to change</b>
+   *
+   * @see org.apache.solr.handler.component.FacetComponent.FacetContext
    */
   public static class FacetInfo {
     /**
@@ -1116,8 +1259,8 @@ public class FacetComponent extends Sear
     public LinkedHashMap<String,DistribFieldFacet> facets;
     public SimpleOrderedMap<SimpleOrderedMap<Object>> dateFacets
       = new SimpleOrderedMap<>();
-    public SimpleOrderedMap<SimpleOrderedMap<Object>> rangeFacets
-      = new SimpleOrderedMap<>();
+    public LinkedHashMap<String, RangeFacetRequest.DistribRangeFacet> rangeFacets
+            = new LinkedHashMap<>();
     public SimpleOrderedMap<SimpleOrderedMap<Integer>> intervalFacets
       = new SimpleOrderedMap<>();
     public SimpleOrderedMap<PivotFacet> pivotFacets
@@ -1157,7 +1300,7 @@ public class FacetComponent extends Sear
       heatmapFacets = SpatialHeatmapFacets.distribParse(params, rb);
     }
   }
-  
+
   /**
    * <b>This API is experimental and subject to change</b>
    */
@@ -1168,6 +1311,9 @@ public class FacetComponent extends Sear
     private String key; // label in the response for the result... 
                         // "foo" for {!key=foo}myfield
     SolrParams localParams; // any local params for the facet
+    private List<String> tags = Collections.emptyList();
+    private List<String> excludeTags = Collections.emptyList();
+    private int threadCount = -1;
     
     public FacetBase(ResponseBuilder rb, String facetType, String facetStr) {
       this.facetType = facetType;
@@ -1189,12 +1335,28 @@ public class FacetComponent extends Sear
         }
         
         key = localParams.get(CommonParams.OUTPUT_KEY, key);
+
+        String tagStr = localParams.get(CommonParams.TAG);
+        this.tags = tagStr == null ? Collections.<String>emptyList() : StrUtils.splitSmart(tagStr,',');
+
+        String threadStr = localParams.get(CommonParams.THREADS);
+        this.threadCount = threadStr != null ? Integer.parseInt(threadStr) : -1;
+
+        String excludeStr = localParams.get(CommonParams.EXCLUDE);
+        if (StringUtils.isEmpty(excludeStr))  {
+          this.excludeTags = Collections.emptyList();
+        } else {
+          this.excludeTags = StrUtils.splitSmart(excludeStr,',');
+        }
       }
     }
     
     /** returns the key in the response that this facet will be under */
     public String getKey() { return key; }
     public String getType() { return facetType; }
+    public List<String> getTags() { return tags; }
+    public List<String> getExcludeTags() { return excludeTags; }
+    public int getThreadCount() { return threadCount; }
   }
   
   /**
@@ -1407,4 +1569,5 @@ public class FacetComponent extends Sear
       return "{term=" + name + ",termNum=" + termNum + ",count=" + count + "}";
     }
   }
+
 }

Modified: lucene/dev/trunk/solr/core/src/java/org/apache/solr/handler/component/PivotFacetHelper.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/java/org/apache/solr/handler/component/PivotFacetHelper.java?rev=1689802&r1=1689801&r2=1689802&view=diff
==============================================================================
--- lucene/dev/trunk/solr/core/src/java/org/apache/solr/handler/component/PivotFacetHelper.java (original)
+++ lucene/dev/trunk/solr/core/src/java/org/apache/solr/handler/component/PivotFacetHelper.java Wed Jul  8 07:46:09 2015
@@ -17,23 +17,19 @@
 
 package org.apache.solr.handler.component;
 
-import org.apache.solr.util.PivotListEntry;
-import org.apache.solr.common.SolrException;
-import org.apache.solr.common.SolrException.ErrorCode;
-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.SimpleOrderedMap;
-import org.apache.solr.common.util.StrUtils;
-
 import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
-import java.util.Collections;
 import java.util.Map;
 import java.util.Map.Entry;
 
+import org.apache.solr.common.SolrException;
+import org.apache.solr.common.SolrException.ErrorCode;
+import org.apache.solr.common.util.NamedList;
+import org.apache.solr.common.util.SimpleOrderedMap;
+import org.apache.solr.common.util.StrUtils;
+import org.apache.solr.util.PivotListEntry;
+
 public class PivotFacetHelper {
 
   /**
@@ -52,8 +48,9 @@ public class PivotFacetHelper {
     assert null != values;
 
     // special case: empty list => empty string
-    if (values.isEmpty()) { return ""; }
-
+    if (values.isEmpty()) {
+      return "";
+    }
     
     StringBuilder out = new StringBuilder();
     for (String val : values) {
@@ -76,7 +73,7 @@ public class PivotFacetHelper {
    * @see #encodeRefinementValuePath
    */
   public static List<String> decodeRefinementValuePath(String valuePath) {
-    List <String> rawvals = StrUtils.splitSmart(valuePath, ",", true);
+    List<String> rawvals = StrUtils.splitSmart(valuePath, ",", true);
     // special case: empty list => empty string
     if (rawvals.isEmpty()) return rawvals;
 
@@ -120,6 +117,16 @@ public class PivotFacetHelper {
     return (NamedList<NamedList<NamedList<?>>>) PivotListEntry.STATS.extract(pivotList);
   }
 
+  /** @see PivotListEntry#QUERIES */
+  public static NamedList<Number> getQueryCounts(NamedList<Object> pivotList) {
+    return (NamedList<Number>) PivotListEntry.QUERIES.extract(pivotList);
+  }
+  
+  /** @see PivotListEntry#RANGES */
+  public static SimpleOrderedMap<SimpleOrderedMap<Object>> getRanges(NamedList<Object> pivotList) {
+    return (SimpleOrderedMap<SimpleOrderedMap<Object>>) PivotListEntry.RANGES.extract(pivotList);
+  }
+  
   /**
    * Given a mapping of keys to {@link StatsValues} representing the currently 
    * known "merged" stats (which may be null if none exist yet), and a 
@@ -156,4 +163,28 @@ public class PivotFacetHelper {
     return merged;
   }
 
+  /**
+   * Merges query counts returned by a shard into global query counts.
+   * Entries found only in shard's query counts will be added to global counts.
+   * Entries found in both shard and global query counts will be summed.
+   *
+   * @param globalQueryCounts The global query counts (across all shards) in which to merge the shard query counts
+   * @param shardQueryCounts  Named list from a shard response to be merged into the global counts.
+   * @return NamedList containing merged values
+   */
+  static NamedList<Number> mergeQueryCounts(
+      NamedList<Number> globalQueryCounts, NamedList<Number> shardQueryCounts) {
+    if (globalQueryCounts == null) {
+      return shardQueryCounts;
+    }
+    for (Entry<String, Number> entry : shardQueryCounts) {
+      int idx = globalQueryCounts.indexOf(entry.getKey(), 0);
+      if (idx == -1) {
+        globalQueryCounts.add(entry.getKey(), entry.getValue());
+      } else {
+        globalQueryCounts.setVal(idx, FacetComponent.num(globalQueryCounts.getVal(idx).longValue() + entry.getValue().longValue()));
+      }
+    }
+    return globalQueryCounts;
+  }
 }

Modified: lucene/dev/trunk/solr/core/src/java/org/apache/solr/handler/component/PivotFacetProcessor.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/java/org/apache/solr/handler/component/PivotFacetProcessor.java?rev=1689802&r1=1689801&r2=1689802&view=diff
==============================================================================
--- lucene/dev/trunk/solr/core/src/java/org/apache/solr/handler/component/PivotFacetProcessor.java (original)
+++ lucene/dev/trunk/solr/core/src/java/org/apache/solr/handler/component/PivotFacetProcessor.java Wed Jul  8 07:46:09 2015
@@ -18,6 +18,8 @@ package org.apache.solr.handler.componen
  */
 
 import org.apache.lucene.util.BytesRefBuilder;
+import org.apache.solr.common.StringUtils;
+import org.apache.solr.common.params.RequiredSolrParams;
 import org.apache.solr.schema.SchemaField;
 import org.apache.solr.schema.FieldType;
 import org.apache.solr.search.SolrIndexSearcher;
@@ -41,7 +43,6 @@ import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Deque;
-import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.LinkedList;
 import java.util.List;
@@ -52,6 +53,8 @@ import java.util.Map;
  */
 public class PivotFacetProcessor extends SimpleFacets
 {
+  public static final String QUERY = "query";
+  public static final String RANGE = "range";
   protected SolrParams params;
     
   public PivotFacetProcessor(SolrQueryRequest req, DocSet docs, SolrParams params, ResponseBuilder rb) {
@@ -101,7 +104,8 @@ public class PivotFacetProcessor extends
 
       String refineKey = null; // no local => no refinement
       List<StatsField> statsFields = Collections.emptyList(); // no local => no stats
-      
+      List<FacetComponent.FacetBase> facetQueries = Collections.emptyList();
+      List<RangeFacetRequest> facetRanges = Collections.emptyList();
       if (null != parsed.localParams) {
         // we might be refining..
         refineKey = parsed.localParams.get(PivotFacet.REFINE_PARAM);
@@ -117,6 +121,42 @@ public class PivotFacetProcessor extends
           statsInfo = new StatsInfo(rb);
         }
         statsFields = getTaggedStatsFields(statsInfo, statsLocalParam);
+
+        try {
+          FacetComponent.FacetContext facetContext = FacetComponent.FacetContext.getFacetContext(req);
+
+          String taggedQueries = parsed.localParams.get(QUERY);
+          if (StringUtils.isEmpty(taggedQueries))  {
+            facetQueries = Collections.emptyList();
+          } else  {
+            List<String> localParamValue = StrUtils.splitSmart(taggedQueries, ',');
+            if (localParamValue.size() > 1) {
+              String msg = QUERY + " local param of " + FacetParams.FACET_PIVOT +
+                  "may not include tags separated by a comma - please use a common tag on all " +
+                  FacetParams.FACET_QUERY + " params you wish to compute under this pivot";
+              throw new SolrException(ErrorCode.BAD_REQUEST, msg);
+            }
+            taggedQueries = localParamValue.get(0);
+            facetQueries = facetContext.getQueryFacetsForTag(taggedQueries);
+          }
+
+          String taggedRanges = parsed.localParams.get(RANGE);
+          if (StringUtils.isEmpty(taggedRanges)) {
+            facetRanges = Collections.emptyList();
+          } else  {
+            List<String> localParamValue = StrUtils.splitSmart(taggedRanges, ',');
+            if (localParamValue.size() > 1) {
+              String msg = RANGE + " local param of " + FacetParams.FACET_PIVOT +
+                  "may not include tags separated by a comma - please use a common tag on all " +
+                  FacetParams.FACET_RANGE + " params you wish to compute under this pivot";
+              throw new SolrException(ErrorCode.BAD_REQUEST, msg);
+            }
+            taggedRanges = localParamValue.get(0);
+            facetRanges = facetContext.getRangeFacetRequestsForTag(taggedRanges);
+          }
+        } catch (IllegalStateException e) {
+          throw new SolrException(ErrorCode.SERVER_ERROR, "Faceting context not set, cannot calculate pivot values");
+        }
       }
 
       if (null != refineKey) {
@@ -124,10 +164,10 @@ public class PivotFacetProcessor extends
           = params.getParams(PivotFacet.REFINE_PARAM + refineKey);
 
         for(String refinements : refinementValuesByField){
-          pivotResponse.addAll(processSingle(pivotFields, refinements, statsFields, parsed));
+          pivotResponse.addAll(processSingle(pivotFields, refinements, statsFields, parsed, facetQueries, facetRanges));
         }
       } else{
-        pivotResponse.addAll(processSingle(pivotFields, null, statsFields, parsed));
+        pivotResponse.addAll(processSingle(pivotFields, null, statsFields, parsed, facetQueries, facetRanges));
       }
     }
     return pivotResponse;
@@ -138,12 +178,16 @@ public class PivotFacetProcessor extends
    * @param pivotFields the ordered list of fields in this pivot
    * @param refinements the comma separate list of refinement values corresponding to each field in the pivot, or null if there are no refinements
    * @param statsFields List of {@link StatsField} instances to compute for each pivot value
+   * @param facetQueries the list of facet queries hung under this pivot
+   * @param facetRanges the list of facet ranges hung under this pivot
    */
   private SimpleOrderedMap<List<NamedList<Object>>> processSingle
-    (List<String> pivotFields,
-     String refinements,
-     List<StatsField> statsFields,
-     final ParsedParams parsed) throws IOException {
+  (List<String> pivotFields,
+   String refinements,
+   List<StatsField> statsFields,
+   final ParsedParams parsed,
+   List<FacetComponent.FacetBase> facetQueries,
+   List<RangeFacetRequest> facetRanges) throws IOException {
 
     SolrIndexSearcher searcher = rb.req.getSearcher();
     SimpleOrderedMap<List<NamedList<Object>>> pivotResponse = new SimpleOrderedMap<>();
@@ -181,9 +225,9 @@ public class PivotFacetProcessor extends
     if(pivotFields.size() > 1) {
       String subField = pivotFields.get(1);
       pivotResponse.add(parsed.key,
-                        doPivots(facetCounts, field, subField, fnames, vnames, parsed, statsFields));
+                        doPivots(facetCounts, field, subField, fnames, vnames, parsed, statsFields, facetQueries, facetRanges));
     } else {
-      pivotResponse.add(parsed.key, doPivots(facetCounts, field, null, fnames, vnames, parsed, statsFields));
+      pivotResponse.add(parsed.key, doPivots(facetCounts, field, null, fnames, vnames, parsed, statsFields, facetQueries, facetRanges));
     }
     return pivotResponse;
   }
@@ -223,10 +267,11 @@ public class PivotFacetProcessor extends
    * Recursive function to compute all the pivot counts for the values under the specified field
    */
   protected List<NamedList<Object>> doPivots(NamedList<Integer> superFacets,
-                                             String field, String subField, 
-                                             Deque<String> fnames, Deque<String> vnames, 
-                                             ParsedParams parsed, List<StatsField> statsFields) 
-    throws IOException {
+                                             String field, String subField,
+                                             Deque<String> fnames, Deque<String> vnames,
+                                             ParsedParams parsed, List<StatsField> statsFields,
+                                             List<FacetComponent.FacetBase> facetQueries, List<RangeFacetRequest> facetRanges)
+      throws IOException {
 
     boolean isShard = rb.req.getParams().getBool(ShardParams.IS_SHARD, false);
 
@@ -259,6 +304,8 @@ public class PivotFacetProcessor extends
 
         final DocSet subset = getSubset(parsed.docs, sfield, fieldValue);
         
+        addPivotQueriesAndRanges(pivot, params, subset, facetQueries, facetRanges);
+
         if( subField != null )  {
           NamedList<Integer> facetCounts;
           if(!vnames.isEmpty()){
@@ -272,7 +319,7 @@ public class PivotFacetProcessor extends
           }
 
           if (facetCounts.size() >= 1) {
-            pivot.add( "pivot", doPivots( facetCounts, subField, nextField, fnames, vnames, parsed.withDocs(subset), statsFields ) );
+            pivot.add( "pivot", doPivots( facetCounts, subField, nextField, fnames, vnames, parsed.withDocs(subset), statsFields, facetQueries, facetRanges) );
           }
         }
         if ((isShard || 0 < pivotCount) && ! statsFields.isEmpty()) {
@@ -329,6 +376,65 @@ public class PivotFacetProcessor extends
     }
   }
 
+  /**
+   * Add facet.queries and facet.ranges to the pivot response if needed
+   * 
+   * @param pivot
+   *          Pivot in which to inject additional data
+   * @param params
+   *          Query parameters.
+   * @param docs
+   *          DocSet of the current pivot to use for computing sub-counts
+   * @param facetQueries
+   *          Tagged facet queries should have to be included, must not be null
+   * @param facetRanges
+   *          Taged facet ranges should have to be included, must not be null
+   * @throws IOException
+   *           If searcher has issues finding numDocs.
+   */
+  protected void addPivotQueriesAndRanges(NamedList<Object> pivot, SolrParams params, DocSet docs,
+                                          List<FacetComponent.FacetBase> facetQueries,
+                                          List<RangeFacetRequest> facetRanges) throws IOException {
+    assert null != facetQueries;
+    assert null != facetRanges;
+    
+    if ( ! facetQueries.isEmpty()) {
+      SimpleFacets facets = new SimpleFacets(req, docs, params);
+      NamedList<Integer> res = new SimpleOrderedMap<>();
+      for (FacetComponent.FacetBase facetQuery : facetQueries) {
+        try {
+          ParsedParams parsed = getParsedParams(params, docs, facetQuery);
+          facets.getFacetQueryCount(parsed, res);
+        } catch (SyntaxError e) {
+          throw new SolrException(ErrorCode.BAD_REQUEST,
+                                  "Invalid " + FacetParams.FACET_QUERY + " (" + facetQuery.facetStr +
+                                  ") cause: " + e.getMessage(), e);
+        }
+      }
+      pivot.add(PivotListEntry.QUERIES.getName(), res);
+    }
+    if ( ! facetRanges.isEmpty()) {
+      RangeFacetProcessor rangeFacetProcessor = new RangeFacetProcessor(req, docs, params, null);
+      NamedList<Object> resOuter = new SimpleOrderedMap<>();
+      for (RangeFacetRequest rangeFacet : facetRanges) {
+        try {
+          rangeFacetProcessor.getFacetRangeCounts(rangeFacet, resOuter);
+        } catch (SyntaxError e) {
+          throw new SolrException(ErrorCode.BAD_REQUEST,
+                                  "Invalid " + FacetParams.FACET_RANGE + " (" + rangeFacet.facetStr +
+                                  ") cause: " + e.getMessage(), e);
+        }
+      }
+      pivot.add(PivotListEntry.RANGES.getName(), resOuter);
+    }
+  }
+
+  private ParsedParams getParsedParams(SolrParams params, DocSet docs, FacetComponent.FacetBase facet) {
+    SolrParams wrapped = SolrParams.wrapDefaults(facet.localParams, global);
+    SolrParams required = new RequiredSolrParams(params);
+    return new ParsedParams(facet.localParams, wrapped, required, facet.facetOn, docs, facet.getKey(), facet.getTags(), -1);
+  }
+
   private int getMinCountForField(String fieldname){
     return params.getFieldInt(fieldname, FacetParams.FACET_PIVOT_MINCOUNT, 1);
   }

Modified: lucene/dev/trunk/solr/core/src/java/org/apache/solr/handler/component/PivotFacetValue.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/java/org/apache/solr/handler/component/PivotFacetValue.java?rev=1689802&r1=1689801&r2=1689802&view=diff
==============================================================================
--- lucene/dev/trunk/solr/core/src/java/org/apache/solr/handler/component/PivotFacetValue.java (original)
+++ lucene/dev/trunk/solr/core/src/java/org/apache/solr/handler/component/PivotFacetValue.java Wed Jul  8 07:46:09 2015
@@ -19,6 +19,7 @@ package org.apache.solr.handler.componen
 
 import java.util.BitSet;
 import java.util.Date;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
@@ -27,7 +28,6 @@ import org.apache.solr.common.params.Fac
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.common.util.SimpleOrderedMap;
 import org.apache.solr.schema.TrieDateField;
-import org.apache.solr.search.QueryParsing;
 import org.apache.solr.util.PivotListEntry;
 
 /**
@@ -48,7 +48,10 @@ public class PivotFacetValue {
   private PivotFacetField childPivot = null; 
   private int count; // mutable
   private Map<String, StatsValues> statsValues = null;
-  
+  // named list with objects because depending on how big the counts are we may get either a long or an int
+  private NamedList<Number> queryCounts;
+  private LinkedHashMap<String, RangeFacetRequest.DistribRangeFacet> rangeCounts;
+
   private PivotFacetValue(PivotFacetField parent, Comparable val) { 
     this.parentPivot = parent;
     this.value = val;
@@ -118,6 +121,8 @@ public class PivotFacetValue {
     int pivotCount = 0;
     List<NamedList<Object>> childPivotData = null;
     NamedList<NamedList<NamedList<?>>> statsValues = null;
+    NamedList<Number> queryCounts = null;
+    SimpleOrderedMap<SimpleOrderedMap<Object>> ranges = null;
     
     for (int i = 0; i < pivotData.size(); i++) {
       String key = pivotData.getName(i);
@@ -142,6 +147,12 @@ public class PivotFacetValue {
       case STATS:
         statsValues = (NamedList<NamedList<NamedList<?>>>) value;
         break;
+      case QUERIES:
+        queryCounts = (NamedList<Number>) value;
+        break;
+      case RANGES:
+        ranges = (SimpleOrderedMap<SimpleOrderedMap<Object>>) value;
+        break;
       default:
         throw new RuntimeException("PivotListEntry contains unaccounted for item: " + entry);
       }
@@ -153,6 +164,13 @@ public class PivotFacetValue {
     if(statsValues != null) {
       newPivotFacet.statsValues = PivotFacetHelper.mergeStats(null, statsValues, rb._statsInfo);
     }
+    if(queryCounts != null) {
+      newPivotFacet.queryCounts = PivotFacetHelper.mergeQueryCounts(null, queryCounts);
+    }
+    if(ranges != null) {
+      newPivotFacet.rangeCounts = new LinkedHashMap<>();
+      RangeFacetRequest.DistribRangeFacet.mergeFacetRangesFromShardResponse(newPivotFacet.rangeCounts, ranges);
+    }
     
     newPivotFacet.childPivot = PivotFacetField.createFromListOfNamedLists(shardNumber, rb, newPivotFacet, childPivotData);
     
@@ -178,6 +196,18 @@ public class PivotFacetValue {
     newList.add(PivotListEntry.FIELD.getName(), parentPivot.field);
     newList.add(PivotListEntry.VALUE.getName(), value);    
     newList.add(PivotListEntry.COUNT.getName(), count);      
+    if(queryCounts != null) {
+      newList.add(PivotListEntry.QUERIES.getName(), queryCounts);
+    }
+    if(rangeCounts != null) {
+      SimpleOrderedMap<SimpleOrderedMap<Object>> rangeFacetOutput = new SimpleOrderedMap<>();
+      for (Map.Entry<String, RangeFacetRequest.DistribRangeFacet> entry : rangeCounts.entrySet()) {
+        String key = entry.getKey();
+        RangeFacetRequest.DistribRangeFacet value = entry.getValue();
+        rangeFacetOutput.add(key, value.rangeFacet);
+      }
+      newList.add(PivotListEntry.RANGES.getName(), rangeFacetOutput);
+    }
     if (childPivot != null && childPivot.convertToListOfNamedLists() != null) {
       newList.add(PivotListEntry.PIVOT.getName(), childPivot.convertToListOfNamedLists());
     }
@@ -205,6 +235,17 @@ public class PivotFacetValue {
       if (stats != null) {
         statsValues = PivotFacetHelper.mergeStats(statsValues, stats, rb._statsInfo);
       }
+      NamedList<Number> shardQueryCounts = PivotFacetHelper.getQueryCounts(value);
+      if(shardQueryCounts != null) {
+        queryCounts = PivotFacetHelper.mergeQueryCounts(queryCounts, shardQueryCounts);
+      }
+      SimpleOrderedMap<SimpleOrderedMap<Object>> shardRanges = PivotFacetHelper.getRanges(value);
+      if (shardRanges != null)  {
+        if (rangeCounts == null)  {
+          rangeCounts = new LinkedHashMap<>(shardRanges.size() / 2);
+        }
+        RangeFacetRequest.DistribRangeFacet.mergeFacetRangesFromShardResponse(rangeCounts, shardRanges);
+      }
     }
     
     List<NamedList<Object>> shardChildPivots = PivotFacetHelper.getPivots(value);

Added: lucene/dev/trunk/solr/core/src/java/org/apache/solr/handler/component/RangeFacetProcessor.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/java/org/apache/solr/handler/component/RangeFacetProcessor.java?rev=1689802&view=auto
==============================================================================
--- lucene/dev/trunk/solr/core/src/java/org/apache/solr/handler/component/RangeFacetProcessor.java (added)
+++ lucene/dev/trunk/solr/core/src/java/org/apache/solr/handler/component/RangeFacetProcessor.java Wed Jul  8 07:46:09 2015
@@ -0,0 +1,277 @@
+package org.apache.solr.handler.component;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.log4j.Logger;
+import org.apache.lucene.search.Query;
+import org.apache.solr.common.SolrException;
+import org.apache.solr.common.params.FacetParams.FacetRangeMethod;
+import org.apache.solr.common.params.FacetParams.FacetRangeOther;
+import org.apache.solr.common.params.SolrParams;
+import org.apache.solr.common.util.NamedList;
+import org.apache.solr.common.util.SimpleOrderedMap;
+import org.apache.solr.request.IntervalFacets;
+import org.apache.solr.request.SimpleFacets;
+import org.apache.solr.request.SolrQueryRequest;
+import org.apache.solr.schema.FieldType;
+import org.apache.solr.schema.IndexSchema;
+import org.apache.solr.schema.SchemaField;
+import org.apache.solr.schema.TrieField;
+import org.apache.solr.search.DocSet;
+import org.apache.solr.search.SyntaxError;
+
+/**
+ * Processor for Range Facets
+ */
+public class RangeFacetProcessor extends SimpleFacets {
+  private final static Logger log = Logger.getLogger(RangeFacetProcessor.class);
+
+  public RangeFacetProcessor(SolrQueryRequest req, DocSet docs, SolrParams params, ResponseBuilder rb) {
+    super(req, docs, params, rb);
+  }
+
+  /**
+   * Returns a list of value constraints and the associated facet
+   * counts for each facet numerical field, range, and interval
+   * specified in the SolrParams
+   *
+   * @see org.apache.solr.common.params.FacetParams#FACET_RANGE
+   */
+  @SuppressWarnings("unchecked")
+  public NamedList<Object> getFacetRangeCounts() throws IOException, SyntaxError {
+    final NamedList<Object> resOuter = new SimpleOrderedMap<>();
+
+    List<RangeFacetRequest> rangeFacetRequests = Collections.emptyList();
+    try {
+      FacetComponent.FacetContext facetContext = FacetComponent.FacetContext.getFacetContext(req);
+      rangeFacetRequests = facetContext.getAllRangeFacetRequests();
+    } catch (IllegalStateException e) {
+      throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Unable to compute facet ranges, facet context is not set");
+    }
+
+    if (rangeFacetRequests.isEmpty()) return resOuter;
+    for (RangeFacetRequest rangeFacetRequest : rangeFacetRequests) {
+      getFacetRangeCounts(rangeFacetRequest, resOuter);
+    }
+
+    return resOuter;
+  }
+
+  /**
+   * Returns a list of value constraints and the associated facet counts
+   * for each facet range specified by the given {@link RangeFacetRequest}
+   */
+  public void getFacetRangeCounts(RangeFacetRequest rangeFacetRequest, NamedList<Object> resOuter)
+      throws IOException, SyntaxError {
+
+    final IndexSchema schema = searcher.getSchema();
+
+    final String key = rangeFacetRequest.getKey();
+    final String f = rangeFacetRequest.facetOn;
+    FacetRangeMethod method = rangeFacetRequest.getMethod();
+
+    final SchemaField sf = schema.getField(f);
+    final FieldType ft = sf.getType();
+
+    if (method.equals(FacetRangeMethod.DV)) {
+      assert ft instanceof TrieField;
+      resOuter.add(key, getFacetRangeCountsDocValues(rangeFacetRequest));
+    } else {
+      resOuter.add(key, getFacetRangeCounts(rangeFacetRequest));
+    }
+  }
+
+  private <T extends Comparable<T>> NamedList getFacetRangeCounts(final RangeFacetRequest rfr)
+      throws IOException, SyntaxError {
+
+    final NamedList<Object> res = new SimpleOrderedMap<>();
+    final NamedList<Integer> counts = new NamedList<>();
+    res.add("counts", counts);
+
+    // explicitly return the gap.
+    res.add("gap", rfr.getGapObj());
+
+    DocSet docSet = computeDocSet(docsOrig, rfr.getExcludeTags());
+
+    for (RangeFacetRequest.FacetRange range : rfr.getFacetRanges()) {
+      if (range.other != null) {
+        // these are added to top-level NamedList
+        // and we always include them regardless of mincount
+        res.add(range.other.toString(), rangeCount(docSet, rfr, range));
+      } else {
+        final int count = rangeCount(docSet, rfr, range);
+        if (count >= rfr.getMinCount()) {
+          counts.add(range.lower, count);
+        }
+      }
+    }
+
+    // explicitly return the start and end so all the counts
+    // (including before/after/between) are meaningful - even if mincount
+    // has removed the neighboring ranges
+    res.add("start", rfr.getStartObj());
+    res.add("end", rfr.getEndObj());
+
+    return res;
+  }
+
+  private <T extends Comparable<T>> NamedList<Object> getFacetRangeCountsDocValues(RangeFacetRequest rfr)
+      throws IOException, SyntaxError {
+
+    SchemaField sf = rfr.getSchemaField();
+    final NamedList<Object> res = new SimpleOrderedMap<>();
+    final NamedList<Integer> counts = new NamedList<>();
+    res.add("counts", counts);
+
+    ArrayList<IntervalFacets.FacetInterval> intervals = new ArrayList<>();
+
+    // explicitly return the gap.  compute this early so we are more
+    // likely to catch parse errors before attempting math
+    res.add("gap", rfr.getGapObj());
+
+    final int minCount = rfr.getMinCount();
+
+    boolean includeBefore = false;
+    boolean includeBetween = false;
+    boolean includeAfter = false;
+
+    Set<FacetRangeOther> others = rfr.getOthers();
+    // Intervals must be in order (see IntervalFacets.getSortedIntervals), if "BEFORE" or
+    // "BETWEEN" are set, they must be added first
+    // no matter what other values are listed, we don't do
+    // anything if "none" is specified.
+    if (!others.contains(FacetRangeOther.NONE)) {
+      if (others.contains(FacetRangeOther.ALL) || others.contains(FacetRangeOther.BEFORE)) {
+        // We'll add an interval later in this position
+        intervals.add(null);
+        includeBefore = true;
+      }
+
+      if (others.contains(FacetRangeOther.ALL) || others.contains(FacetRangeOther.BETWEEN)) {
+        // We'll add an interval later in this position
+        intervals.add(null);
+        includeBetween = true;
+      }
+
+      if (others.contains(FacetRangeOther.ALL) || others.contains(FacetRangeOther.AFTER)) {
+        includeAfter = true;
+      }
+    }
+
+    IntervalFacets.FacetInterval after = null;
+
+    for (RangeFacetRequest.FacetRange range : rfr.getFacetRanges()) {
+      try {
+        FacetRangeOther other = FacetRangeOther.get(range.name);
+        if (other != null) {
+          switch (other) {
+            case BEFORE:
+              assert range.lower == null;
+              intervals.set(0, new IntervalFacets.FacetInterval(sf, "*", range.upper, range.includeLower,
+                  range.includeUpper, FacetRangeOther.BEFORE.toString()));
+              break;
+            case AFTER:
+              assert range.upper == null;
+              after = new IntervalFacets.FacetInterval(sf, range.lower, "*",
+                  range.includeLower, range.includeUpper, FacetRangeOther.AFTER.toString());
+              break;
+            case BETWEEN:
+              intervals.set(includeBefore ? 1 : 0, new IntervalFacets.FacetInterval(sf, range.lower, range.upper,
+                  range.includeLower, range.includeUpper, FacetRangeOther.BETWEEN.toString()));
+              break;
+          }
+        }
+        continue;
+      } catch (SolrException e) {
+        // safe to ignore
+      }
+
+      intervals.add(new IntervalFacets.FacetInterval(sf, range.lower, range.upper, range.includeLower, range.includeUpper, range.lower));
+    }
+
+    if (includeAfter) {
+      assert after != null;
+      intervals.add(after);
+    }
+
+    IntervalFacets.FacetInterval[] intervalsArray = intervals.toArray(new IntervalFacets.FacetInterval[intervals.size()]);
+    // don't use the ArrayList anymore
+    intervals = null;
+
+    new IntervalFacets(sf, searcher, computeDocSet(docsOrig, rfr.getExcludeTags()), intervalsArray);
+
+    int intervalIndex = 0;
+    int lastIntervalIndex = intervalsArray.length - 1;
+    // if the user requested "BEFORE", it will be the first of the intervals. Needs to be added to the
+    // response named list instead of with the counts
+    if (includeBefore) {
+      res.add(intervalsArray[intervalIndex].getKey(), intervalsArray[intervalIndex].getCount());
+      intervalIndex++;
+    }
+
+    // if the user requested "BETWEEN", it will be the first or second of the intervals (depending on if
+    // "BEFORE" was also requested). Needs to be added to the response named list instead of with the counts
+    if (includeBetween) {
+      res.add(intervalsArray[intervalIndex].getKey(), intervalsArray[intervalIndex].getCount());
+      intervalIndex++;
+    }
+
+    // if the user requested "AFTER", it will be the last of the intervals.
+    // Needs to be added to the response named list instead of with the counts
+    if (includeAfter) {
+      res.add(intervalsArray[lastIntervalIndex].getKey(), intervalsArray[lastIntervalIndex].getCount());
+      lastIntervalIndex--;
+    }
+    // now add all other intervals to the counts NL
+    while (intervalIndex <= lastIntervalIndex) {
+      IntervalFacets.FacetInterval interval = intervalsArray[intervalIndex];
+      if (interval.getCount() >= minCount) {
+        counts.add(interval.getKey(), interval.getCount());
+      }
+      intervalIndex++;
+    }
+
+    res.add("start", rfr.getStartObj());
+    res.add("end", rfr.getEndObj());
+    return res;
+  }
+
+  /**
+   * Macro for getting the numDocs of range over docs
+   *
+   * @see org.apache.solr.search.SolrIndexSearcher#numDocs
+   * @see org.apache.lucene.search.TermRangeQuery
+   */
+  protected int rangeCount(DocSet subset, RangeFacetRequest rfr, RangeFacetRequest.FacetRange fr) throws IOException, SyntaxError {
+    SchemaField schemaField = rfr.getSchemaField();
+    Query rangeQ = schemaField.getType().getRangeQuery(null, schemaField, fr.lower, fr.upper, fr.includeLower, fr.includeUpper);
+    if (rfr.isGroupFacet()) {
+      return getGroupedFacetQueryCount(rangeQ, subset);
+    } else {
+      return searcher.numDocs(rangeQ, subset);
+    }
+  }
+
+}
+



Mime
View raw message