You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by sh...@apache.org on 2015/07/08 13:05:28 UTC

svn commit: r1689839 [2/3] - in /lucene/dev/branches/branch_5x: ./ solr/ solr/core/ solr/core/src/java/org/apache/solr/handler/ solr/core/src/java/org/apache/solr/handler/component/ solr/core/src/java/org/apache/solr/request/ solr/core/src/java/org/apa...

Modified: lucene/dev/branches/branch_5x/solr/core/src/java/org/apache/solr/request/SimpleFacets.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_5x/solr/core/src/java/org/apache/solr/request/SimpleFacets.java?rev=1689839&r1=1689838&r2=1689839&view=diff
==============================================================================
--- lucene/dev/branches/branch_5x/solr/core/src/java/org/apache/solr/request/SimpleFacets.java (original)
+++ lucene/dev/branches/branch_5x/solr/core/src/java/org/apache/solr/request/SimpleFacets.java Wed Jul  8 11:05:27 2015
@@ -26,6 +26,8 @@ import org.apache.lucene.index.PostingsE
 import org.apache.lucene.index.Term;
 import org.apache.lucene.index.Terms;
 import org.apache.lucene.index.TermsEnum;
+import org.apache.lucene.search.BooleanClause.Occur;
+import org.apache.lucene.search.BooleanQuery;
 import org.apache.lucene.search.DocIdSetIterator;
 import org.apache.lucene.search.Filter;
 import org.apache.lucene.search.FilterCollector;
@@ -34,7 +36,6 @@ import org.apache.lucene.search.LeafColl
 import org.apache.lucene.search.MatchAllDocsQuery;
 import org.apache.lucene.search.Query;
 import org.apache.lucene.search.TermQuery;
-import org.apache.lucene.search.TermRangeQuery;
 import org.apache.lucene.search.grouping.AbstractAllGroupHeadsCollector;
 import org.apache.lucene.search.grouping.term.TermAllGroupsCollector;
 import org.apache.lucene.search.grouping.term.TermGroupFacetCollector;
@@ -45,9 +46,6 @@ import org.apache.solr.common.SolrExcept
 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.FacetRangeInclude;
-import org.apache.solr.common.params.FacetParams.FacetRangeMethod;
-import org.apache.solr.common.params.FacetParams.FacetRangeOther;
 import org.apache.solr.common.params.GroupParams;
 import org.apache.solr.common.params.RequiredSolrParams;
 import org.apache.solr.common.params.SolrParams;
@@ -55,15 +53,14 @@ import org.apache.solr.common.util.Execu
 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.handler.component.FacetComponent;
 import org.apache.solr.handler.component.ResponseBuilder;
 import org.apache.solr.handler.component.SpatialHeatmapFacets;
 import org.apache.solr.request.IntervalFacets.FacetInterval;
 import org.apache.solr.schema.BoolField;
-import org.apache.solr.schema.DateRangeField;
 import org.apache.solr.schema.FieldType;
 import org.apache.solr.schema.IndexSchema;
 import org.apache.solr.schema.SchemaField;
-import org.apache.solr.schema.TrieDateField;
 import org.apache.solr.schema.TrieField;
 import org.apache.solr.search.BitDocSet;
 import org.apache.solr.search.DocSet;
@@ -77,7 +74,6 @@ import org.apache.solr.search.SortedIntD
 import org.apache.solr.search.SyntaxError;
 import org.apache.solr.search.grouping.GroupingSpecification;
 import org.apache.solr.util.BoundedTreeSet;
-import org.apache.solr.util.DateMathParser;
 import org.apache.solr.util.DefaultSolrThreadFactory;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -85,12 +81,10 @@ import org.slf4j.LoggerFactory;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Date;
-import java.util.EnumSet;
+import java.util.Collections;
 import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
 import java.util.concurrent.Callable;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executor;
@@ -120,8 +114,6 @@ public class SimpleFacets {
   protected final SolrQueryRequest req;
   protected final ResponseBuilder rb;
 
-  protected SimpleOrderedMap<Object> facetResponse;
-
   // per-facet values
   protected final static class ParsedParams {
     final public SolrParams localParams; // localParams on this particular facet command
@@ -130,6 +122,7 @@ public class SimpleFacets {
     final public String facetValue;      // the field to or query to facet on (minus local params)
     final public DocSet docs;            // the base docset for this particular facet
     final public String key;             // what name should the results be stored under
+    final public List<String> tags;      // the tags applied to this facet value
     final public int threads;
     
     public ParsedParams(final SolrParams localParams, // localParams on this particular facet command
@@ -138,6 +131,7 @@ public class SimpleFacets {
                         final String facetValue,      // the field to or query to facet on (minus local params)
                         final DocSet docs,            // the base docset for this particular facet
                         final String key,             // what name should the results be stored under
+                        final List<String> tags,
                         final int threads) {
       this.localParams = localParams;
       this.params = params;
@@ -145,14 +139,15 @@ public class SimpleFacets {
       this.facetValue = facetValue;
       this.docs = docs;
       this.key = key;
+      this.tags = tags;
       this.threads = threads;
     }
     
     public ParsedParams withDocs(final DocSet docs) {
-      return new ParsedParams(localParams, params, required, facetValue, docs, key, threads);
+      return new ParsedParams(localParams, params, required, facetValue, docs, key, tags, threads);
     }
   }
-  
+
   public SimpleFacets(SolrQueryRequest req,
                       DocSet docs,
                       SolrParams params) {
@@ -195,12 +190,13 @@ public class SimpleFacets {
     DocSet docs = docsOrig;
     String facetValue = param;
     String key = param;
+    List<String> tags = Collections.emptyList();
     int threads = -1;
 
     if (localParams == null) {
       SolrParams params = global;
       SolrParams required = new RequiredSolrParams(params);
-      return new ParsedParams(localParams, params, required, facetValue, docs, key, threads);
+      return new ParsedParams(localParams, params, required, facetValue, docs, key, tags, threads);
     }
     
     SolrParams params = SolrParams.wrapDefaults(localParams, global);
@@ -217,6 +213,9 @@ public class SimpleFacets {
     // allow explicit set of the key
     key = localParams.get(CommonParams.OUTPUT_KEY, key);
 
+    String tagStr = localParams.get(CommonParams.TAG);
+    tags = tagStr == null ? Collections.<String>emptyList() : StrUtils.splitSmart(tagStr,',');
+
     String threadStr = localParams.get(CommonParams.THREADS);
     if (threadStr != null) {
       threads = Integer.parseInt(threadStr);
@@ -224,102 +223,83 @@ public class SimpleFacets {
 
     // figure out if we need a new base DocSet
     String excludeStr = localParams.get(CommonParams.EXCLUDE);
-    if (excludeStr == null) return new ParsedParams(localParams, params, required, facetValue, docs, key, threads);
+    if (excludeStr == null) return new ParsedParams(localParams, params, required, facetValue, docs, key, tags, threads);
 
+    List<String> excludeTagList = StrUtils.splitSmart(excludeStr,',');
+    docs = computeDocSet(docs, excludeTagList);
+    return new ParsedParams(localParams, params, required, facetValue, docs, key, tags, threads);
+  }
+
+  protected DocSet computeDocSet(DocSet baseDocSet, List<String> excludeTagList) throws SyntaxError, IOException {
     Map<?,?> tagMap = (Map<?,?>)req.getContext().get("tags");
-    if (tagMap != null && rb != null) {
-      List<String> excludeTagList = StrUtils.splitSmart(excludeStr,',');
+    // rb can be null if facets are being calculated from a RequestHandler e.g. MoreLikeThisHandler
+    if (tagMap == null || rb == null) {
+      return baseDocSet;
+    }
 
-      IdentityHashMap<Query,Boolean> excludeSet = new IdentityHashMap<>();
-      for (String excludeTag : excludeTagList) {
-        Object olst = tagMap.get(excludeTag);
-        // tagMap has entries of List<String,List<QParser>>, but subject to change in the future
-        if (!(olst instanceof Collection)) continue;
-        for (Object o : (Collection<?>)olst) {
-          if (!(o instanceof QParser)) continue;
-          QParser qp = (QParser)o;
-          excludeSet.put(qp.getQuery(), Boolean.TRUE);
-        }
+    IdentityHashMap<Query,Boolean> excludeSet = new IdentityHashMap<>();
+    for (String excludeTag : excludeTagList) {
+      Object olst = tagMap.get(excludeTag);
+      // tagMap has entries of List<String,List<QParser>>, but subject to change in the future
+      if (!(olst instanceof Collection)) continue;
+      for (Object o : (Collection<?>)olst) {
+        if (!(o instanceof QParser)) continue;
+        QParser qp = (QParser)o;
+        excludeSet.put(qp.getQuery(), Boolean.TRUE);
       }
-      if (excludeSet.size() == 0) return new ParsedParams(localParams, params, required, facetValue, docs, key, threads);
+    }
+    if (excludeSet.size() == 0) return baseDocSet;
 
-      List<Query> qlist = new ArrayList<>();
+    List<Query> qlist = new ArrayList<>();
 
-      // add the base query
-      if (!excludeSet.containsKey(rb.getQuery())) {
-        qlist.add(rb.getQuery());
-      }
+    // add the base query
+    if (!excludeSet.containsKey(rb.getQuery())) {
+      qlist.add(rb.getQuery());
+    }
 
-      // add the filters
-      if (rb.getFilters() != null) {
-        for (Query q : rb.getFilters()) {
-          if (!excludeSet.containsKey(q)) {
-            qlist.add(q);
-          }
+    // add the filters
+    if (rb.getFilters() != null) {
+      for (Query q : rb.getFilters()) {
+        if (!excludeSet.containsKey(q)) {
+          qlist.add(q);
         }
       }
+    }
 
-      // get the new base docset for this facet
-      DocSet base = searcher.getDocSet(qlist);
-      if (rb.grouping() && rb.getGroupingSpec().isTruncateGroups()) {
-        Grouping grouping = new Grouping(searcher, null, rb.getQueryCommand(), false, 0, false);
-        grouping.setGroupSort(rb.getGroupingSpec().getSortWithinGroup());
-        if (rb.getGroupingSpec().getFields().length > 0) {
-          grouping.addFieldCommand(rb.getGroupingSpec().getFields()[0], req);
-        } else if (rb.getGroupingSpec().getFunctions().length > 0) {
-          grouping.addFunctionCommand(rb.getGroupingSpec().getFunctions()[0], req);
-        } else {
-          docs = base;
-          return new ParsedParams(localParams, params, required, facetValue, docs, key, threads);
-        }
-        AbstractAllGroupHeadsCollector allGroupHeadsCollector = grouping.getCommands().get(0).createAllGroupCollector();
-        searcher.search(new FilteredQuery(new MatchAllDocsQuery(), base.getTopFilter()), allGroupHeadsCollector);
-        docs = new BitDocSet(allGroupHeadsCollector.retrieveGroupHeads(searcher.maxDoc()));
+    // get the new base docset for this facet
+    DocSet base = searcher.getDocSet(qlist);
+    if (rb.grouping() && rb.getGroupingSpec().isTruncateGroups()) {
+      Grouping grouping = new Grouping(searcher, null, rb.getQueryCommand(), false, 0, false);
+      grouping.setGroupSort(rb.getGroupingSpec().getSortWithinGroup());
+      if (rb.getGroupingSpec().getFields().length > 0) {
+        grouping.addFieldCommand(rb.getGroupingSpec().getFields()[0], req);
+      } else if (rb.getGroupingSpec().getFunctions().length > 0) {
+        grouping.addFunctionCommand(rb.getGroupingSpec().getFunctions()[0], req);
       } else {
-        docs = base;
+        return base;
       }
+      AbstractAllGroupHeadsCollector allGroupHeadsCollector = grouping.getCommands().get(0).createAllGroupCollector();
+      searcher.search(new FilteredQuery(new MatchAllDocsQuery(), base.getTopFilter()), allGroupHeadsCollector);
+      return new BitDocSet(allGroupHeadsCollector.retrieveGroupHeads(searcher.maxDoc()));
+    } else {
+      return base;
     }
-
-    return new ParsedParams(localParams, params, required, facetValue, docs, key, threads);
   }
 
-
   /**
    * Looks at various Params to determining if any simple Facet Constraint count
    * computations are desired.
    *
-   * @see #getFacetQueryCounts
-   * @see #getFacetFieldCounts
-   * @see #getFacetDateCounts
-   * @see #getFacetRangeCounts
-   * @see #getFacetIntervalCounts
-   * @see FacetParams#FACET
    * @return a NamedList of Facet Count info or null
+   * @deprecated use {@link org.apache.solr.handler.component.FacetComponent#getFacetCounts(SimpleFacets)} instead
    */
+  @Deprecated
   public NamedList<Object> getFacetCounts() {
-
-    // if someone called this method, benefit of the doubt: assume true
-    if (!global.getBool(FacetParams.FACET,true))
-      return null;
-
-    facetResponse = new SimpleOrderedMap<>();
-    try {
-      facetResponse.add("facet_queries", getFacetQueryCounts());
-      facetResponse.add("facet_fields", getFacetFieldCounts());
-      facetResponse.add("facet_dates", getFacetDateCounts());
-      facetResponse.add("facet_ranges", getFacetRangeCounts());
-      facetResponse.add("facet_intervals", getFacetIntervalCounts());
-      facetResponse.add(SpatialHeatmapFacets.RESPONSE_KEY, getHeatmapCounts());
-    } catch (IOException e) {
-      throw new SolrException(ErrorCode.SERVER_ERROR, e);
-    } catch (SyntaxError e) {
-      throw new SolrException(ErrorCode.BAD_REQUEST, e);
-    }
-    return facetResponse;
+    return FacetComponent.getFacetCounts(this);
   }
 
   /**
-   * Returns a list of facet counts for each of the facet queries 
+   * Returns a list of facet counts for each of the facet queries
    * specified in the params
    *
    * @see FacetParams#FACET_QUERY
@@ -340,39 +320,44 @@ public class SimpleFacets {
     if (null != facetQs && 0 != facetQs.length) {
       for (String q : facetQs) {
         final ParsedParams parsed = parseParams(FacetParams.FACET_QUERY, q);
-
-        // TODO: slight optimization would prevent double-parsing of any localParams
-        Query qobj = QParser.getParser(q, null, req).getQuery();
-
-        if (qobj == null) {
-          res.add(parsed.key, 0);
-        } else if (parsed.params.getBool(GroupParams.GROUP_FACET, false)) {
-          res.add(parsed.key, getGroupedFacetQueryCount(qobj, parsed));
-        } else {
-          res.add(parsed.key, searcher.numDocs(qobj, parsed.docs));
-        }
+        getFacetQueryCount(parsed, res);
       }
     }
 
     return res;
   }
-  
+
+  public void getFacetQueryCount(ParsedParams parsed, NamedList<Integer> res) throws SyntaxError, IOException {
+    // TODO: slight optimization would prevent double-parsing of any localParams
+    // TODO: SOLR-7753
+    Query qobj = QParser.getParser(parsed.facetValue, null, req).getQuery();
+
+    if (qobj == null) {
+      res.add(parsed.key, 0);
+    } else if (parsed.params.getBool(GroupParams.GROUP_FACET, false)) {
+      res.add(parsed.key, getGroupedFacetQueryCount(qobj, parsed.docs));
+    } else {
+      res.add(parsed.key, searcher.numDocs(qobj, parsed.docs));
+    }
+  }
+
   /**
    * Returns a grouped facet count for the facet query
    *
    * @see FacetParams#FACET_QUERY
    */
-  public int getGroupedFacetQueryCount(Query facetQuery, ParsedParams parsed) throws IOException {
-    String groupField = parsed.params.get(GroupParams.GROUP_FIELD);
+  public int getGroupedFacetQueryCount(Query facetQuery, DocSet docSet) throws IOException {
+    // It is okay to retrieve group.field from global because it is never a local param
+    String groupField = global.get(GroupParams.GROUP_FIELD);
     if (groupField == null) {
       throw new SolrException (
           SolrException.ErrorCode.BAD_REQUEST,
           "Specify the group.field as parameter or local parameter"
       );
     }
-    
+
     TermAllGroupsCollector collector = new TermAllGroupsCollector(groupField);
-    Filter mainQueryFilter = parsed.docs.getTopFilter(); // This returns a filter that only matches documents matching with q param and fq params
+    Filter mainQueryFilter = docSet.getTopFilter(); // This returns a filter that only matches documents matching with q param and fq params
     searcher.search(new FilteredQuery(facetQuery, mainQueryFilter), collector);
     return collector.getGroupCount();
   }
@@ -483,7 +468,7 @@ public class SimpleFacets {
       switch (method) {
         case ENUM:
           assert TrieField.getMainValuePrefix(ft) == null;
-          counts = getFacetTermEnumCounts(searcher, docs, field, offset, limit, mincount, missing, sort, prefix, contains, ignoreCase);
+          counts = getFacetTermEnumCounts(searcher, docs, field, offset, limit, mincount,missing,sort,prefix, contains, ignoreCase, params);
           break;
         case FCS:
           assert !multiToken;
@@ -543,7 +528,7 @@ public class SimpleFacets {
     if (sf != null && sf.hasDocValues() == false && sf.multiValued() == false && sf.getType().getNumericType() != null) {
       // it's a single-valued numeric field: we must currently create insanity :(
       // there isn't a GroupedFacetCollector that works on numerics right now...
-      searcher.search(new FilteredQuery(new MatchAllDocsQuery(), base.getTopFilter()), new FilterCollector(collector) {
+      searcher.search(base.getTopFilter(), new FilterCollector(collector) {
         @Override
         public LeafCollector getLeafCollector(LeafReaderContext context) throws IOException {
           LeafReader insane = Insanity.wrapInsanity(context.reader(), groupField);
@@ -551,7 +536,7 @@ public class SimpleFacets {
         }
       });
     } else {
-      searcher.search(new FilteredQuery(new MatchAllDocsQuery(), base.getTopFilter()), collector);
+      searcher.search(base.getTopFilter(), collector);
     }
     
     boolean orderByCount = sort.equals(FacetParams.FACET_SORT_COUNT) || sort.equals(FacetParams.FACET_SORT_COUNT_LEGACY);
@@ -722,7 +707,7 @@ public class SimpleFacets {
    * @see FacetParams#FACET_ZEROS
    * @see FacetParams#FACET_MISSING
    */
-  public NamedList<Integer> getFacetTermEnumCounts(SolrIndexSearcher searcher, DocSet docs, String field, int offset, int limit, int mincount, boolean missing, String sort, String prefix, String contains, boolean ignoreCase)
+  public NamedList<Integer> getFacetTermEnumCounts(SolrIndexSearcher searcher, DocSet docs, String field, int offset, int limit, int mincount, boolean missing, String sort, String prefix, String contains, boolean ignoreCase, SolrParams params)
     throws IOException {
 
     /* :TODO: potential optimization...
@@ -881,613 +866,6 @@ public class SimpleFacets {
   }
 
   /**
-   * 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, SyntaxError {
-
-    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 getFacetRangeCounts which is more generalized
-   */
-  @Deprecated
-  public void getFacetDateCounts(String dateFacet, NamedList<Object> resOuter)
-      throws IOException, SyntaxError {
-
-    final IndexSchema schema = searcher.getSchema();
-
-    final ParsedParams parsed = parseParams(FacetParams.FACET_DATE, dateFacet);
-    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<FacetRangeInclude> include = 
-      (null == iStrs || 0 == iStrs.length ) ?
-      EnumSet.of(FacetRangeInclude.LOWER, 
-                 FacetRangeInclude.UPPER, 
-                 FacetRangeInclude.EDGE)
-      : 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(FacetRangeInclude.LOWER) ||
-                (include.contains(FacetRangeInclude.EDGE) && low.equals(start)));
-        final boolean includeUpper =
-            (include.contains(FacetRangeInclude.UPPER) ||
-                (include.contains(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<FacetRangeOther> others = EnumSet.noneOf(FacetRangeOther.class);
-
-      for (final String o : othersP) {
-        others.add(FacetRangeOther.get(o));
-      }
-
-      // no matter what other values are listed, we don't do
-      // anything if "none" is specified.
-      if (! others.contains(FacetRangeOther.NONE) ) {
-        boolean all = others.contains(FacetRangeOther.ALL);
-
-        if (all || others.contains(FacetRangeOther.BEFORE)) {
-          // include upper bound if "outer" or if first gap doesn't already include it
-          resInner.add(FacetRangeOther.BEFORE.toString(),
-              rangeCount(parsed,sf,null,start,
-                  false,
-                  (include.contains(FacetRangeInclude.OUTER) ||
-                      (! (include.contains(FacetRangeInclude.LOWER) ||
-                          include.contains(FacetRangeInclude.EDGE))))));
-        }
-        if (all || others.contains(FacetRangeOther.AFTER)) {
-          // include lower bound if "outer" or if last gap doesn't already include it
-          resInner.add(FacetRangeOther.AFTER.toString(),
-              rangeCount(parsed,sf,end,null,
-                  (include.contains(FacetRangeInclude.OUTER) ||
-                      (! (include.contains(FacetRangeInclude.UPPER) ||
-                          include.contains(FacetRangeInclude.EDGE)))),
-                  false));
-        }
-        if (all || others.contains(FacetRangeOther.BETWEEN)) {
-          resInner.add(FacetRangeOther.BETWEEN.toString(),
-              rangeCount(parsed,sf,start,end,
-                  (include.contains(FacetRangeInclude.LOWER) ||
-                      include.contains(FacetRangeInclude.EDGE)),
-                  (include.contains(FacetRangeInclude.UPPER) ||
-                      include.contains(FacetRangeInclude.EDGE))));
-        }
-      }
-    }
-  }
-
-  
-  /**
-   * Returns a list of value constraints and the associated facet
-   * counts for each facet numerical field, range, and interval
-   * specified in the SolrParams
-   *
-   * @see FacetParams#FACET_RANGE
-   */
-
-  public NamedList<Object> getFacetRangeCounts() throws IOException, SyntaxError {
-    final NamedList<Object> resOuter = new SimpleOrderedMap<>();
-    final String[] fields = global.getParams(FacetParams.FACET_RANGE);
-
-    if (null == fields || 0 == fields.length) return resOuter;
-
-    for (String f : fields) {
-      getFacetRangeCounts(f, resOuter);
-    }
-
-    return resOuter;
-  }
-
-  void getFacetRangeCounts(String facetRange, NamedList<Object> resOuter)
-      throws IOException, SyntaxError {
-
-    final IndexSchema schema = searcher.getSchema();
-
-    final ParsedParams parsed = parseParams(FacetParams.FACET_RANGE, facetRange);
-    final String key = parsed.key;
-    final String f = parsed.facetValue;
-    String methodStr = parsed.params.get(FacetParams.FACET_RANGE_METHOD);
-    FacetRangeMethod method = (methodStr==null?FacetRangeMethod.getDefault():FacetRangeMethod.get(methodStr));
-    boolean groupFacet = parsed.params.getBool(GroupParams.GROUP_FACET, false);
-    if (groupFacet && method.equals(FacetRangeMethod.DV)) {
-      // the user has explicitly selected the FacetRangeMethod.DV method
-      log.warn("Range facet method '" + FacetRangeMethod.DV + "' is not supported together with '" + 
-              GroupParams.GROUP_FACET + "'. Will use method '" + FacetRangeMethod.FILTER + "' instead");
-      method = FacetRangeMethod.FILTER;
-    }
-
-    final SchemaField sf = schema.getField(f);
-    final FieldType ft = sf.getType();
-
-    RangeEndpointCalculator<?> calc = null;
-
-    if (ft instanceof TrieField) {
-      final TrieField trie = (TrieField)ft;
-
-      switch (trie.getType()) {
-        case FLOAT:
-          calc = new FloatRangeEndpointCalculator(sf);
-          break;
-        case DOUBLE:
-          calc = new DoubleRangeEndpointCalculator(sf);
-          break;
-        case INTEGER:
-          calc = new IntegerRangeEndpointCalculator(sf);
-          break;
-        case LONG:
-          calc = new LongRangeEndpointCalculator(sf);
-          break;
-        case DATE:
-          calc = new DateRangeEndpointCalculator(sf, null);
-          break;
-        default:
-          throw new SolrException
-              (SolrException.ErrorCode.BAD_REQUEST,
-                  "Unable to range facet on tried field of unexpected type:" + f);
-      }
-    } else if (ft instanceof DateRangeField) {
-      calc = new DateRangeFieldEndpointCalculator(sf, null);
-      if (method.equals(FacetRangeMethod.DV)) {
-        // the user has explicitly selected the FacetRangeMethod.DV method
-        log.warn("Range facet method '" + FacetRangeMethod.DV + "' is not supported together with field type '" + 
-            DateRangeField.class + "'. Will use method '" + FacetRangeMethod.FILTER + "' instead");
-        method = FacetRangeMethod.FILTER;
-      }
-    } else {
-      throw new SolrException
-          (SolrException.ErrorCode.BAD_REQUEST,
-              "Unable to range facet on field:" + sf);
-    }
-    if (method.equals(FacetRangeMethod.DV)) {
-      assert ft instanceof TrieField;
-      resOuter.add(key, getFacetRangeCountsDocValues(sf, calc, parsed));
-    } else {
-      resOuter.add(key, getFacetRangeCounts(sf, calc, parsed));
-    }
-  }
-
-  private <T extends Comparable<T>> NamedList<Object> getFacetRangeCounts
-    (final SchemaField sf,
-     final RangeEndpointCalculator<T> calc,
-     final ParsedParams parsed) throws IOException {
-    
-    final String f = sf.getName();
-    final SolrParams params = parsed.params;
-    final SolrParams required = parsed.required;
-    final NamedList<Object> res = new SimpleOrderedMap<>();
-    final NamedList<Integer> counts = new NamedList<>();
-    res.add("counts", counts);
-
-    final T start = calc.getValue(required.getFieldParam(f,FacetParams.FACET_RANGE_START));
-    // not final, hardend may change this
-    T end = calc.getValue(required.getFieldParam(f,FacetParams.FACET_RANGE_END));
-    if (end.compareTo(start) < 0) {
-      throw new SolrException
-        (SolrException.ErrorCode.BAD_REQUEST,
-         "range facet 'end' comes before 'start': "+end+" < "+start);
-    }
-    
-    final String gap = required.getFieldParam(f, FacetParams.FACET_RANGE_GAP);
-    // explicitly return the gap.  compute this early so we are more 
-    // likely to catch parse errors before attempting math
-    res.add("gap", calc.getGap(gap));
-    
-    final int minCount = params.getFieldInt(f,FacetParams.FACET_MINCOUNT, 0);
-    
-    final EnumSet<FacetRangeInclude> include = FacetRangeInclude.parseParam
-      (params.getFieldParams(f,FacetParams.FACET_RANGE_INCLUDE));
-    
-    T low = start;
-    
-    while (low.compareTo(end) < 0) {
-      T high = calc.addGap(low, gap);
-      if (end.compareTo(high) < 0) {
-        if (params.getFieldBool(f,FacetParams.FACET_RANGE_HARD_END,false)) {
-          high = end;
-        } else {
-          end = high;
-        }
-      }
-      if (high.compareTo(low) < 0) {
-        throw new SolrException
-          (SolrException.ErrorCode.BAD_REQUEST,
-           "range facet infinite loop (is gap negative? did the math overflow?)");
-      }
-      if (high.compareTo(low) == 0) {
-        throw new SolrException
-          (SolrException.ErrorCode.BAD_REQUEST,
-           "range facet infinite loop: gap is either zero, or too small relative start/end and caused underflow: " + low + " + " + gap + " = " + high );
-      }
-      
-      final boolean includeLower = 
-        (include.contains(FacetRangeInclude.LOWER) ||
-         (include.contains(FacetRangeInclude.EDGE) && 
-          0 == low.compareTo(start)));
-      final boolean includeUpper = 
-        (include.contains(FacetRangeInclude.UPPER) ||
-         (include.contains(FacetRangeInclude.EDGE) && 
-          0 == high.compareTo(end)));
-      
-      final String lowS = calc.formatValue(low);
-      final String highS = calc.formatValue(high);
-
-      final int count = rangeCount(parsed, sf, lowS, highS,
-                                   includeLower,includeUpper);
-      if (count >= minCount) {
-        counts.add(lowS, count);
-      }
-      
-      low = high;
-    }
-    
-    // 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", start);
-    res.add("end", end);
-    
-    final String[] othersP =
-      params.getFieldParams(f,FacetParams.FACET_RANGE_OTHER);
-    if (null != othersP && 0 < othersP.length ) {
-      Set<FacetRangeOther> others = EnumSet.noneOf(FacetRangeOther.class);
-      
-      for (final String o : othersP) {
-        others.add(FacetRangeOther.get(o));
-      }
-      
-      // no matter what other values are listed, we don't do
-      // anything if "none" is specified.
-      if (! others.contains(FacetRangeOther.NONE) ) {
-        
-        boolean all = others.contains(FacetRangeOther.ALL);
-        final String startS = calc.formatValue(start);
-        final String endS = calc.formatValue(end);
-
-        if (all || others.contains(FacetRangeOther.BEFORE)) {
-          // include upper bound if "outer" or if first gap doesn't already include it
-          res.add(FacetRangeOther.BEFORE.toString(),
-                  rangeCount(parsed,sf,null,startS,
-                             false,
-                             (include.contains(FacetRangeInclude.OUTER) ||
-                              (! (include.contains(FacetRangeInclude.LOWER) ||
-                                  include.contains(FacetRangeInclude.EDGE))))));
-          
-        }
-        if (all || others.contains(FacetRangeOther.AFTER)) {
-          // include lower bound if "outer" or if last gap doesn't already include it
-          res.add(FacetRangeOther.AFTER.toString(),
-                  rangeCount(parsed,sf,endS,null,
-                             (include.contains(FacetRangeInclude.OUTER) ||
-                              (! (include.contains(FacetRangeInclude.UPPER) ||
-                                  include.contains(FacetRangeInclude.EDGE)))),  
-                             false));
-        }
-        if (all || others.contains(FacetRangeOther.BETWEEN)) {
-         res.add(FacetRangeOther.BETWEEN.toString(),
-                 rangeCount(parsed,sf,startS,endS,
-                            (include.contains(FacetRangeInclude.LOWER) ||
-                             include.contains(FacetRangeInclude.EDGE)),
-                            (include.contains(FacetRangeInclude.UPPER) ||
-                             include.contains(FacetRangeInclude.EDGE))));
-         
-        }
-      }
-    }
-    return res;
-  }  
-  
-  private <T extends Comparable<T>> NamedList<Object> getFacetRangeCountsDocValues(final SchemaField sf,
-   final RangeEndpointCalculator<T> calc, ParsedParams parsed) throws IOException {
-  
-  final String f = sf.getName();
-  final NamedList<Object> res = new SimpleOrderedMap<>();
-  final NamedList<Integer> counts = new NamedList<>();
-  res.add("counts", counts);
-  
-  String globalStartS = parsed.required.getFieldParam(f,FacetParams.FACET_RANGE_START);
-  String globalEndS = parsed.required.getFieldParam(f,FacetParams.FACET_RANGE_END);
-
-  final T start = calc.getValue(globalStartS);
-  // not final, hardend may change this
-  T end = calc.getValue(globalEndS);
-  if (end.compareTo(start) < 0) {
-    throw new SolrException
-      (SolrException.ErrorCode.BAD_REQUEST,
-       "range facet 'end' comes before 'start': "+end+" < "+start);
-  }
-  
-  final String gap = parsed.required.getFieldParam(f, FacetParams.FACET_RANGE_GAP);
-  // explicitly return the gap.  compute this early so we are more 
-  // likely to catch parse errors before attempting math
-  res.add("gap", calc.getGap(gap));
-  
-  final int minCount = parsed.params.getFieldInt(f,FacetParams.FACET_MINCOUNT, 0);
-  
-  final EnumSet<FacetRangeInclude> include = FacetRangeInclude.parseParam
-    (parsed.params.getFieldParams(f,FacetParams.FACET_RANGE_INCLUDE));
-  ArrayList<IntervalFacets.FacetInterval> intervals = new ArrayList<>();
-  
-  final String[] othersP =
-      parsed.params.getFieldParams(f,FacetParams.FACET_RANGE_OTHER);
-  
-  boolean includeBefore = false;
-  boolean includeBetween = false;
-  boolean includeAfter = false;
-  
-  if (othersP != null && othersP.length > 0) {
-    Set<FacetRangeOther> others = EnumSet.noneOf(FacetRangeOther.class);
-    // Intervals must be in order (see IntervalFacets.getSortedIntervals), if "BEFORE" or
-    // "BETWEEN" are set, they must be added first
-    for (final String o : othersP) {
-      others.add(FacetRangeOther.get(o));
-    }
-    // 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;
-      }
-    }
-    
-  }
-  
-  
-  T low = start;
-  
-  while (low.compareTo(end) < 0) {
-    T high = calc.addGap(low, gap);
-    if (end.compareTo(high) < 0) {
-      if (parsed.params.getFieldBool(f,FacetParams.FACET_RANGE_HARD_END,false)) {
-        high = end;
-      } else {
-        end = high;
-      }
-    }
-    if (high.compareTo(low) < 0) {
-      throw new SolrException
-        (SolrException.ErrorCode.BAD_REQUEST,
-         "range facet infinite loop (is gap negative? did the math overflow?)");
-    }
-    if (high.compareTo(low) == 0) {
-      throw new SolrException
-        (SolrException.ErrorCode.BAD_REQUEST,
-         "range facet infinite loop: gap is either zero, or too small relative start/end and caused underflow: " + low + " + " + gap + " = " + high );
-    }
-    
-    final boolean includeLower = 
-      (include.contains(FacetRangeInclude.LOWER) ||
-       (include.contains(FacetRangeInclude.EDGE) && 
-        0 == low.compareTo(start)));
-    final boolean includeUpper = 
-      (include.contains(FacetRangeInclude.UPPER) ||
-       (include.contains(FacetRangeInclude.EDGE) && 
-        0 == high.compareTo(end)));
-    
-    final String lowS = calc.formatValue(low);
-    final String highS = calc.formatValue(high);
-    
-    intervals.add(new IntervalFacets.FacetInterval(sf, lowS, highS, includeLower, includeUpper, lowS));
-    
-    low = high;
-  }
-  
-  if (includeBefore) {
-    // include upper bound if "outer" or if first gap doesn't already include it
-    intervals.set(0, new IntervalFacets.FacetInterval(sf, "*", globalStartS, true, 
-        include.contains(FacetRangeInclude.OUTER) ||
-          (! (include.contains(FacetRangeInclude.LOWER) ||
-            include.contains(FacetRangeInclude.EDGE))), FacetRangeOther.BEFORE.toString()));
-  }
-  
-  if (includeBetween) {
-    int intervalIndex = (includeBefore?1:0);
-    intervals.set(intervalIndex, new IntervalFacets.FacetInterval(sf, globalStartS, calc.formatValue(end), 
-        include.contains(FacetRangeInclude.LOWER) ||
-        include.contains(FacetRangeInclude.EDGE), 
-        include.contains(FacetRangeInclude.UPPER) ||
-        include.contains(FacetRangeInclude.EDGE), 
-        FacetRangeOther.BETWEEN.toString()));
-   }
-  
-  if (includeAfter) {
-    // include lower bound if "outer" or if last gap doesn't already include it
-    intervals.add(new IntervalFacets.FacetInterval(sf, calc.formatValue(end), "*", 
-        (include.contains(FacetRangeInclude.OUTER) ||
-        (! (include.contains(FacetRangeInclude.UPPER) ||
-            include.contains(FacetRangeInclude.EDGE)))),  
-       false, FacetRangeOther.AFTER.toString()));
-  }
-  
-  IntervalFacets.FacetInterval[] intervalsArray = intervals.toArray(new IntervalFacets.FacetInterval[intervals.size()]);
-  // don't use the ArrayList anymore
-  intervals = null;
-  
-  new IntervalFacets(sf, searcher, parsed.docs, 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) {
-    FacetInterval interval = intervalsArray[intervalIndex];
-    if (interval.getCount() >= minCount) {
-      counts.add(interval.getKey(), interval.getCount());
-    }
-    intervalIndex++;
-  }
-  
-  res.add("start", start);
-  res.add("end", end);
-  return res;
-}  
-  
-  /**
-   * Macro for getting the numDocs of range over docs
-   * @see SolrIndexSearcher#numDocs
-   * @see TermRangeQuery
-   */
-  protected int rangeCount(ParsedParams parsed, SchemaField sf, String low, String high,
-                           boolean iLow, boolean iHigh) throws IOException {
-    Query rangeQ = sf.getType().getRangeQuery(null, sf, low, high, iLow, iHigh);
-    if (parsed.params.getBool(GroupParams.GROUP_FACET, false)) {
-      return getGroupedFacetQueryCount(rangeQ, parsed);
-    } else {
-      return searcher.numDocs(rangeQ , parsed.docs);
-    }
-  }
-
-  /**
-   * @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);
-  }
-  
-  /**
    * A simple key=&gt;val pair whose natural order is such that 
    * <b>higher</b> vals come before lower vals.
    * In case of tie vals, then <b>lower</b> keys come before higher keys.
@@ -1519,215 +897,6 @@ public class SimpleFacets {
 
 
   /**
-   * Perhaps someday instead of having a giant "instanceof" case 
-   * statement to pick an impl, we can add a "RangeFacetable" marker 
-   * interface to FieldTypes and they can return instances of these 
-   * directly from some method -- but until then, keep this locked down 
-   * and private.
-   */
-  private static abstract class RangeEndpointCalculator<T extends Comparable<T>> {
-    protected final SchemaField field;
-    public RangeEndpointCalculator(final SchemaField field) {
-      this.field = field;
-    }
-
-    /**
-     * Formats a Range endpoint for use as a range label name in the response.
-     * Default Impl just uses toString()
-     */
-    public String formatValue(final T val) {
-      return val.toString();
-    }
-    /**
-     * Parses a String param into an Range endpoint value throwing 
-     * a useful exception if not possible
-     */
-    public final T getValue(final String rawval) {
-      try {
-        return parseVal(rawval);
-      } catch (Exception e) {
-        throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
-                                "Can't parse value "+rawval+" for field: " + 
-                                field.getName(), e);
-      }
-    }
-    /**
-     * Parses a String param into an Range endpoint. 
-     * Can throw a low level format exception as needed.
-     */
-    protected abstract T parseVal(final String rawval) 
-      throws java.text.ParseException;
-
-    /** 
-     * Parses a String param into a value that represents the gap and 
-     * can be included in the response, throwing 
-     * a useful exception if not possible.
-     *
-     * Note: uses Object as the return type instead of T for things like 
-     * Date where gap is just a DateMathParser string 
-     */
-    public final Object getGap(final String gap) {
-      try {
-        return parseGap(gap);
-      } catch (Exception e) {
-        throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
-                                "Can't parse gap "+gap+" for field: " + 
-                                field.getName(), e);
-      }
-    }
-
-    /**
-     * Parses a String param into a value that represents the gap and 
-     * can be included in the response. 
-     * Can throw a low level format exception as needed.
-     *
-     * Default Impl calls parseVal
-     */
-    protected Object parseGap(final String rawval) 
-      throws java.text.ParseException {
-      return parseVal(rawval);
-    }
-
-    /**
-     * Adds the String gap param to a low Range endpoint value to determine 
-     * the corrisponding high Range endpoint value, throwing 
-     * a useful exception if not possible.
-     */
-    public final T addGap(T value, String gap) {
-      try {
-        return parseAndAddGap(value, gap);
-      } catch (Exception e) {
-        throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
-                                "Can't add gap "+gap+" to value " + value +
-                                " for field: " + field.getName(), e);
-      }
-    }
-    /**
-     * Adds the String gap param to a low Range endpoint value to determine 
-     * the corrisponding high Range endpoint value.
-     * Can throw a low level format exception as needed.
-     */
-    protected abstract T parseAndAddGap(T value, String gap) 
-      throws java.text.ParseException;
-
-  }
-
-  private static class FloatRangeEndpointCalculator 
-    extends RangeEndpointCalculator<Float> {
-
-    public FloatRangeEndpointCalculator(final SchemaField f) { super(f); }
-    @Override
-    protected Float parseVal(String rawval) {
-      return Float.valueOf(rawval);
-    }
-    @Override
-    public Float parseAndAddGap(Float value, String gap) {
-      return new Float(value.floatValue() + Float.valueOf(gap).floatValue());
-    }
-  }
-  private static class DoubleRangeEndpointCalculator 
-    extends RangeEndpointCalculator<Double> {
-
-    public DoubleRangeEndpointCalculator(final SchemaField f) { super(f); }
-    @Override
-    protected Double parseVal(String rawval) {
-      return Double.valueOf(rawval);
-    }
-    @Override
-    public Double parseAndAddGap(Double value, String gap) {
-      return new Double(value.doubleValue() + Double.valueOf(gap).doubleValue());
-    }
-  }
-  private static class IntegerRangeEndpointCalculator 
-    extends RangeEndpointCalculator<Integer> {
-
-    public IntegerRangeEndpointCalculator(final SchemaField f) { super(f); }
-    @Override
-    protected Integer parseVal(String rawval) {
-      return Integer.valueOf(rawval);
-    }
-    @Override
-    public Integer parseAndAddGap(Integer value, String gap) {
-      return new Integer(value.intValue() + Integer.valueOf(gap).intValue());
-    }
-  }
-  private static class LongRangeEndpointCalculator 
-    extends RangeEndpointCalculator<Long> {
-
-    public LongRangeEndpointCalculator(final SchemaField f) { super(f); }
-    @Override
-    protected Long parseVal(String rawval) {
-      return Long.valueOf(rawval);
-    }
-    @Override
-    public Long parseAndAddGap(Long value, String gap) {
-      return new Long(value.longValue() + Long.valueOf(gap).longValue());
-    }
-  }
-  private static class DateRangeEndpointCalculator 
-    extends RangeEndpointCalculator<Date> {
-    private static final String TYPE_ERR_MSG = "SchemaField must use field type extending TrieDateField or DateRangeField";
-    private final Date now;
-    public DateRangeEndpointCalculator(final SchemaField f, 
-                                       final Date now) { 
-      super(f); 
-      this.now = now;
-      if (! (field.getType() instanceof TrieDateField) ) {
-        throw new IllegalArgumentException
-          (TYPE_ERR_MSG);
-      }
-    }
-    @Override
-    public String formatValue(Date val) {
-      return ((TrieDateField)field.getType()).toExternal(val);
-    }
-    @Override
-    protected Date parseVal(String rawval) {
-      return ((TrieDateField)field.getType()).parseMath(now, rawval);
-    }
-    @Override
-    protected Object parseGap(final String rawval) {
-      return rawval;
-    }
-    @Override
-    public Date parseAndAddGap(Date value, String gap) throws java.text.ParseException {
-      final DateMathParser dmp = new DateMathParser();
-      dmp.setNow(value);
-      return dmp.parseMath(gap);
-    }
-  }
-  private static class DateRangeFieldEndpointCalculator
-      extends RangeEndpointCalculator<Date> {
-    private final Date now;
-    public DateRangeFieldEndpointCalculator(final SchemaField f,
-                                       final Date now) {
-      super(f);
-      this.now = now;
-      if (! (field.getType() instanceof DateRangeField) ) {
-        throw new IllegalArgumentException(DateRangeEndpointCalculator.TYPE_ERR_MSG);
-      }
-    }
-    @Override
-    public String formatValue(Date val) {
-      return TrieDateField.formatExternal(val);
-    }
-    @Override
-    protected Date parseVal(String rawval) {
-      return ((DateRangeField)field.getType()).parseMath(now, rawval);
-    }
-    @Override
-    protected Object parseGap(final String rawval) {
-      return rawval;
-    }
-    @Override
-    public Date parseAndAddGap(Date value, String gap) throws java.text.ParseException {
-      final DateMathParser dmp = new DateMathParser();
-      dmp.setNow(value);
-      return dmp.parseMath(gap);
-    }
-  }
-
-  /**
    * Returns a <code>NamedList</code> with each entry having the "key" of the interval as name and the count of docs 
    * in that interval as value. All intervals added in the request are included in the returned 
    * <code>NamedList</code> (included those with 0 count), and it's required that the order of the intervals
@@ -1759,7 +928,7 @@ public class SimpleFacets {
     return res;
   }
 
-  private NamedList getHeatmapCounts() throws IOException, SyntaxError {
+  public NamedList getHeatmapCounts() throws IOException, SyntaxError {
     final NamedList<Object> resOuter = new SimpleOrderedMap<>();
     String[] unparsedFields = rb.req.getParams().getParams(FacetParams.FACET_HEATMAP);
     if (unparsedFields == null || unparsedFields.length == 0) {
@@ -1776,5 +945,21 @@ public class SimpleFacets {
     }
     return resOuter;
   }
+
+  public SolrParams getGlobalParams() {
+    return global;
+  }
+
+  public DocSet getDocsOrig() {
+    return docsOrig;
+  }
+
+  public SolrQueryRequest getRequest() {
+    return req;
+  }
+
+  public ResponseBuilder getResponseBuilder() {
+    return rb;
+  }
 }
 

Modified: lucene/dev/branches/branch_5x/solr/core/src/java/org/apache/solr/util/PivotListEntry.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_5x/solr/core/src/java/org/apache/solr/util/PivotListEntry.java?rev=1689839&r1=1689838&r2=1689839&view=diff
==============================================================================
--- lucene/dev/branches/branch_5x/solr/core/src/java/org/apache/solr/util/PivotListEntry.java (original)
+++ lucene/dev/branches/branch_5x/solr/core/src/java/org/apache/solr/util/PivotListEntry.java Wed Jul  8 11:05:27 2015
@@ -34,7 +34,9 @@ public enum PivotListEntry {
   COUNT(2),
   // optional entries
   PIVOT,
-  STATS;
+  STATS,
+  QUERIES,
+  RANGES;
   
   private static final int MIN_INDEX_OF_OPTIONAL = 3;
 

Modified: lucene/dev/branches/branch_5x/solr/core/src/test/org/apache/solr/handler/component/DistributedFacetPivotLargeTest.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_5x/solr/core/src/test/org/apache/solr/handler/component/DistributedFacetPivotLargeTest.java?rev=1689839&r1=1689838&r2=1689839&view=diff
==============================================================================
--- lucene/dev/branches/branch_5x/solr/core/src/test/org/apache/solr/handler/component/DistributedFacetPivotLargeTest.java (original)
+++ lucene/dev/branches/branch_5x/solr/core/src/test/org/apache/solr/handler/component/DistributedFacetPivotLargeTest.java Wed Jul  8 11:05:27 2015
@@ -17,21 +17,21 @@ package org.apache.solr.handler.componen
  * limitations under the License.
  */
 
+import java.io.IOException;
 import java.util.Date;
 import java.util.List;
-import java.io.IOException;
 
+import junit.framework.AssertionFailedError;
 import org.apache.solr.BaseDistributedSearchTestCase;
 import org.apache.solr.client.solrj.SolrClient;
 import org.apache.solr.client.solrj.SolrServerException;
 import org.apache.solr.client.solrj.response.FieldStatsInfo;
 import org.apache.solr.client.solrj.response.PivotField;
 import org.apache.solr.client.solrj.response.QueryResponse;
+import org.apache.solr.client.solrj.response.RangeFacet;
 import org.apache.solr.common.SolrInputDocument;
 import org.apache.solr.common.params.FacetParams;
 import org.apache.solr.common.params.SolrParams;
-
-import junit.framework.AssertionFailedError;
 import org.junit.Test;
 
 public class DistributedFacetPivotLargeTest extends BaseDistributedSearchTestCase {
@@ -665,6 +665,7 @@ public class DistributedFacetPivotLargeT
                 FacetParams.FACET_OVERREQUEST_COUNT, "0");
 
     doTestDeepPivotStats();
+    doTestPivotRanges();
   }
 
   private void doTestDeepPivotStats() throws Exception {
@@ -740,6 +741,101 @@ public class DistributedFacetPivotLargeT
   }
 
   /**
+   * spot checks some pivot values and the ranges hanging on them
+   */
+  private void doTestPivotRanges() throws Exception {
+
+    // note: 'p0' is only a top level range, not included in per-pivot ranges
+    for (SolrParams p : new SolrParams[]{
+        // results should be identical for all of these
+        params("facet.range", "{!key=p0 facet.range.gap=500}pay_i",
+            "facet.range", "{!key=p1 tag=t1 facet.range.gap=100}pay_i",
+            "facet.range", "{!key=p2 tag=t1 facet.range.gap=200}pay_i",
+            "facet.range.start", "0",
+            "facet.range.end", "1000"),
+        params("facet.range", "{!key=p0 facet.range.gap=500}pay_i",
+            "facet.range", "{!key=p1 tag=t1 facet.range.gap=100}pay_i",
+            "facet.range", "{!key=p2 tag=t1 facet.range.gap=200}pay_i",
+            "f.pay_i.facet.range.start", "0",
+            "facet.range.end", "1000"),
+        params("facet.range", "{!key=p0 facet.range.gap=500 facet.range.start=0}pay_i",
+            "facet.range", "{!key=p1 tag=t1 facet.range.gap=100 facet.range.start=0}pay_i",
+            "facet.range", "{!key=p2 tag=t1 facet.range.gap=200 facet.range.start=0}pay_i",
+            "facet.range.end", "1000")}) {
+
+      QueryResponse rsp
+          = query(SolrParams.wrapDefaults(p, params("q", "*:*",
+          "rows", "0",
+          "facet", "true",
+          "facet.pivot", "{!range=t1}place_s,company_t")));
+
+      List<PivotField> pivots = rsp.getFacetPivot().get("place_s,company_t");
+      PivotField pf = null; // changes as we spot check
+      List<RangeFacet.Count> rfc = null; // changes as we spot check
+
+      // 1st sanity check top level ranges
+      assertEquals(3, rsp.getFacetRanges().size());
+      assertRange("p0", 0, 500, 1000, 2, rsp.getFacetRanges().get(0));
+      assertRange("p1", 0, 100, 1000, 10, rsp.getFacetRanges().get(1));
+      assertRange("p2", 0, 200, 1000, 5, rsp.getFacetRanges().get(2));
+
+      // check pivots...
+
+      // first top level pivot value
+      pf = pivots.get(0);
+      assertPivot("place_s", "cardiff", 257, pf);
+      assertRange("p1", 0, 100, 1000, 10, pf.getFacetRanges().get(0));
+      assertRange("p2", 0, 200, 1000, 5, pf.getFacetRanges().get(1));
+
+      rfc = pf.getFacetRanges().get(0).getCounts();
+      assertEquals("200", rfc.get(2).getValue());
+      assertEquals(14, rfc.get(2).getCount());
+      assertEquals("300", rfc.get(3).getValue());
+      assertEquals(15, rfc.get(3).getCount());
+
+      rfc = pf.getFacetRanges().get(1).getCounts();
+      assertEquals("200", rfc.get(1).getValue());
+      assertEquals(29, rfc.get(1).getCount());
+
+      // drill down one level of the pivot
+      pf = pf.getPivot().get(0);
+      assertPivot("company_t", "bbc", 101, pf);
+      assertRange("p1", 0, 100, 1000, 10, pf.getFacetRanges().get(0));
+      assertRange("p2", 0, 200, 1000, 5, pf.getFacetRanges().get(1));
+
+      rfc = pf.getFacetRanges().get(0).getCounts();
+      for (RangeFacet.Count c : rfc) {
+        assertEquals(0, c.getCount()); // no docs in our ranges for this pivot drill down
+      }
+
+      // pop back up and spot check a different top level pivot value
+      pf = pivots.get(53);
+      assertPivot("place_s", "placeholder0", 1, pf);
+      assertRange("p1", 0, 100, 1000, 10, pf.getFacetRanges().get(0));
+      assertRange("p2", 0, 200, 1000, 5, pf.getFacetRanges().get(1));
+
+      rfc = pf.getFacetRanges().get(0).getCounts();
+      assertEquals("0", rfc.get(0).getValue());
+      assertEquals(1, rfc.get(0).getCount());
+      assertEquals("100", rfc.get(1).getValue());
+      assertEquals(0, rfc.get(1).getCount());
+
+      // drill down one level of the pivot
+      pf = pf.getPivot().get(0);
+      assertPivot("company_t", "compholder0", 1, pf);
+      assertRange("p1", 0, 100, 1000, 10, pf.getFacetRanges().get(0));
+      assertRange("p2", 0, 200, 1000, 5, pf.getFacetRanges().get(1));
+
+      rfc = pf.getFacetRanges().get(0).getCounts();
+      assertEquals("0", rfc.get(0).getValue());
+      assertEquals(1, rfc.get(0).getCount());
+      assertEquals("100", rfc.get(1).getValue());
+      assertEquals(0, rfc.get(1).getCount());
+
+    }
+  }
+
+  /**
    * asserts that the actual PivotField matches the expected criteria
    */
   private void assertPivot(String field, Object value, int count, // int numKids,
@@ -751,8 +847,18 @@ public class DistributedFacetPivotLargeT
     //assertEquals("#KIDS: " + actual.toString(), numKids, actual.getPivot().size());
   }
 
+  /**
+   * asserts that the actual RangeFacet matches the expected criteria
+   */
+  private void assertRange(String name, Object start, Object gap, Object end, int numCount,
+                           RangeFacet actual) {
+    assertEquals("NAME: " + actual.toString(), name, actual.getName());
+    assertEquals("START: " + actual.toString(), start, actual.getStart());
+    assertEquals("GAP: " + actual.toString(), gap, actual.getGap());
+    assertEquals("END: " + actual.toString(), end, actual.getEnd());
+    assertEquals("#COUNT: " + actual.toString(), numCount, actual.getCounts().size());
+  }
 
-  
   private void setupDistributedPivotFacetDocuments() throws Exception{
     
     //Clear docs