You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by ho...@apache.org on 2010/07/29 22:05:41 UTC

svn commit: r980555 - in /lucene/dev/trunk/solr: ./ src/common/org/apache/solr/common/params/ src/java/org/apache/solr/handler/component/ src/java/org/apache/solr/request/ src/test/org/apache/solr/ src/test/org/apache/solr/request/ src/test/test-files/...

Author: hossman
Date: Thu Jul 29 20:05:41 2010
New Revision: 980555

URL: http://svn.apache.org/viewvc?rev=980555&view=rev
Log:
SOLR-1240: Added support for generalized Numerical Range faceting on any numeric (or date) field.  Note: this required modifying ConvertedLegacyTest to deal with an expectation about how many fields a specific document has (there are more now because of a copyField i added to the schema)

Modified:
    lucene/dev/trunk/solr/CHANGES.txt
    lucene/dev/trunk/solr/src/common/org/apache/solr/common/params/FacetParams.java
    lucene/dev/trunk/solr/src/java/org/apache/solr/handler/component/FacetComponent.java
    lucene/dev/trunk/solr/src/java/org/apache/solr/request/SimpleFacets.java
    lucene/dev/trunk/solr/src/test/org/apache/solr/ConvertedLegacyTest.java
    lucene/dev/trunk/solr/src/test/org/apache/solr/request/SimpleFacetsTest.java
    lucene/dev/trunk/solr/src/test/test-files/solr/conf/schema.xml

Modified: lucene/dev/trunk/solr/CHANGES.txt
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/CHANGES.txt?rev=980555&r1=980554&r2=980555&view=diff
==============================================================================
--- lucene/dev/trunk/solr/CHANGES.txt (original)
+++ lucene/dev/trunk/solr/CHANGES.txt Thu Jul 29 20:05:41 2010
@@ -214,6 +214,11 @@ New Features
 * SOLR-1925: Add CSVResponseWriter (use wt=csv) that returns the list of documents
   in CSV format. (Chris Mattmann, yonik)
 
+* SOLR-1240: "Rnage Faceting has been added.  This is a generalization
+  of the existing "Date Faceting" logic so that it now supports any
+  all stock numeric field types that support range queries in addition
+  to dates.  
+  (Gijs Kunze, hossman)
 
 Optimizations
 ----------------------

Modified: lucene/dev/trunk/solr/src/common/org/apache/solr/common/params/FacetParams.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/src/common/org/apache/solr/common/params/FacetParams.java?rev=980555&r1=980554&r2=980555&view=diff
==============================================================================
--- lucene/dev/trunk/solr/src/common/org/apache/solr/common/params/FacetParams.java (original)
+++ lucene/dev/trunk/solr/src/common/org/apache/solr/common/params/FacetParams.java Thu Jul 29 20:05:41 2010
@@ -153,54 +153,140 @@ public interface FacetParams {
    * String indicating what "other" ranges should be computed for a
    * date facet range (multi-value).
    * Can be overriden on a per field basis.
-   * @see FacetDateOther
+   * @see FacetRangeOther
    */
   public static final String FACET_DATE_OTHER = FACET_DATE + ".other";
 
-    /**
-   * An enumeration of the legal values for FACET_DATE_OTHER...
+  /**
+   * <p>
+   * Multivalued string indicating what rules should be applied to determine 
+   * when the the ranges generated for date faceting should be inclusive or 
+   * exclusive of their end points.
+   * </p>
+   * <p>
+   * The default value if none are specified is: [lower,upper,edge]
+   * </p>
+   * <p>
+   * Can be overriden on a per field basis.
+   * </p>
+   * @see FacetRangeInclude
+   */
+  public static final String FACET_DATE_INCLUDE = FACET_DATE + ".include";
+
+  /**
+   * Any numerical field whose terms the user wants to enumerate over
+   * Facet Contraint Counts for selected ranges.
+   */
+  public static final String FACET_RANGE = FACET + ".range";
+  /**
+   * Number indicating the starting point for a numerical range facet.
+   * Can be overriden on a per field basis.
+   */
+  public static final String FACET_RANGE_START = FACET_RANGE + ".start";
+  /**
+   * Number indicating the ending point for a numerical range facet.
+   * Can be overriden on a per field basis.
+   */
+  public static final String FACET_RANGE_END = FACET_RANGE + ".end";
+  /**
+   * Number indicating the interval of sub-ranges for a numerical
+   * facet range.
+   * Can be overriden on a per field basis.
+   */
+  public static final String FACET_RANGE_GAP = FACET_RANGE + ".gap";
+  /**
+   * Boolean indicating how counts should be computed if the range
+   * between 'start' and 'end' is not evenly divisible by 'gap'.  If
+   * this value is true, then all counts of ranges involving the 'end'
+   * point will use the exact endpoint specified -- this includes the
+   * 'between' and 'after' counts as well as the last range computed
+   * using the 'gap'.  If the value is false, then 'gap' is used to
+   * compute the effective endpoint closest to the 'end' param which
+   * results in the range between 'start' and 'end' being evenly
+   * divisible by 'gap'.
+   * The default is false.
+   * Can be overriden on a per field basis.
+   */
+  public static final String FACET_RANGE_HARD_END = FACET_RANGE + ".hardend";
+  /**
+   * String indicating what "other" ranges should be computed for a
+   * numerical range facet (multi-value).
+   * Can be overriden on a per field basis.
+   * @see FacetNumberOther
+   */
+  public static final String FACET_RANGE_OTHER = FACET_RANGE + ".other";
+  /**
+   * String indicating whether ranges for numerical range faceting 
+   * should be exclusive or inclusive. By default both the start and
+   * end point are inclusive.
+   * Can be overriden on a per field basis.
+   * @see FacetNumberExclusive
+   */
+
+  /**
+   * <p>
+   * Multivalued string indicating what rules should be applied to determine 
+   * when the the ranges generated for numeric faceting should be inclusive or 
+   * exclusive of their end points.
+   * </p>
+   * <p>
+   * The default value if none are specified is: [lower,upper,edge]
+   * </p>
+   * <p>
+   * Can be overriden on a per field basis.
+   * </p>
+   * @see FacetRangeInclude
+   */
+  public static final String FACET_RANGE_INCLUDE = FACET_RANGE + ".include";
+
+
+  /**
+   * An enumeration of the legal values for {@link #FACET_RANGE_OTHER} and {@link #FACET_DATE_OTHER} ...
    * <ul>
-   * <li>before = the count of matches before the start date</li>
-   * <li>after = the count of matches after the end date</li>
+   * <li>before = the count of matches before the start</li>
+   * <li>after = the count of matches after the end</li>
    * <li>between = the count of all matches between start and end</li>
    * <li>all = all of the above (default value)</li>
    * <li>none = no additional info requested</li>
    * </ul>
+   * @see #FACET_RANGE_OTHER
    * @see #FACET_DATE_OTHER
-   * @see #FACET_DATE_INCLUDE
    */
-  public enum FacetDateOther {
+  public enum FacetRangeOther {
     BEFORE, AFTER, BETWEEN, ALL, NONE;
-    public String toString() { return super.toString().toLowerCase(Locale.ENGLISH); }
-    public static FacetDateOther get(String label) {
+    public String toString() { return super.toString().toLowerCase(); }
+    public static FacetRangeOther get(String label) {
       try {
-        return valueOf(label.toUpperCase(Locale.ENGLISH));
+        return valueOf(label.toUpperCase());
       } catch (IllegalArgumentException e) {
         throw new SolrException
           (SolrException.ErrorCode.BAD_REQUEST,
-           label+" is not a valid type of 'other' date facet information",e);
+           label+" is not a valid type of 'other' range facet information",e);
       }
     }
   }
   
   /**
-   * <p>
-   * Multivalued string indicating what rules should be applied to determine 
-   * when the the ranges generated for date faceting should be inclusive or 
-   * exclusive of their end points.
-   * </p>
-   * <p>
-   * The default value if none are specified is: [lower,upper,edge]
-   * </p>
-   * <p>
-   * Can be overriden on a per field basis.
-   * </p>
-   * @see FacetDateInclude
+   * @deprecated Use {@link FacetRangeOther}
    */
-  public static final String FACET_DATE_INCLUDE = FACET_DATE + ".include";
-
+  @Deprecated
+  public enum FacetDateOther {
+    BEFORE, AFTER, BETWEEN, ALL, NONE;
+    public String toString() { return super.toString().toLowerCase(); }
+    public static FacetDateOther get(String label) {
+      try {
+        return valueOf(label.toUpperCase());
+      } catch (IllegalArgumentException e) {
+        throw new SolrException
+          (SolrException.ErrorCode.BAD_REQUEST,
+           label+" is not a valid type of 'other' range facet information",e);
+      }
+    }
+  }
+  
   /**
-   * An enumeration of the legal values for FACET_DATE_INCLUDE...
+   * An enumeration of the legal values for {@link #FACET_DATE_INCLUDE} and {@link #FACET_RANGE_INCLUDE}
+   *
    * <ul>
    * <li>lower = all gap based ranges include their lower bound</li>
    * <li>upper = all gap based ranges include their upper bound</li>
@@ -208,43 +294,45 @@ public interface FacetParams {
    *     for the first one, upper for the last one) even if the corresponding 
    *     upper/lower option is not specified
    * </li>
-   * <li>outer = the FacetDateOther.BEFORE and FacetDateOther.AFTER ranges 
+   * <li>outer = the BEFORE and AFTER ranges 
    *     should be inclusive of their bounds, even if the first or last ranges 
    *     already include those boundaries.
    * </li>
    * <li>all = shorthand for lower, upper, edge, and outer</li>
    * </ul>
    * @see #FACET_DATE_INCLUDE
+   * @see #FACET_RANGE_INCLUDE
    */
-  public enum FacetDateInclude {
+  public enum FacetRangeInclude {
     ALL, LOWER, UPPER, EDGE, OUTER;
     public String toString() { return super.toString().toLowerCase(Locale.ENGLISH); }
-    public static FacetDateInclude get(String label) {
+    public static FacetRangeInclude get(String label) {
       try {
         return valueOf(label.toUpperCase(Locale.ENGLISH));
       } catch (IllegalArgumentException e) {
         throw new SolrException
           (SolrException.ErrorCode.BAD_REQUEST,
-           label+" is not a valid type of for "+FACET_DATE_INCLUDE+" information",e);
+           label+" is not a valid type of for range 'include' information",e);
       }
     }
     /**
-     * Convinience method for parsing the param value according to the correct semantics.
+     * Convinience method for parsing the param value according to the 
+     * correct semantics.
      */
-    public static EnumSet<FacetDateInclude> parseParam(final String[] param) {
+    public static EnumSet<FacetRangeInclude> parseParam(final String[] param) {
       // short circut for default behavior
       if (null == param || 0 == param.length ) 
         return EnumSet.of(LOWER, UPPER, EDGE);
 
       // build up set containing whatever is specified
-      final EnumSet<FacetDateInclude> include = EnumSet.noneOf(FacetDateInclude.class);
+      final EnumSet<FacetRangeInclude> include = EnumSet.noneOf(FacetRangeInclude.class);
       for (final String o : param) {
-        include.add(FacetDateInclude.get(o));
+        include.add(FacetRangeInclude.get(o));
       }
 
       // if set contains all, then we're back to short circuting
-      if (include.contains(FacetDateInclude.ALL)) 
-        return EnumSet.allOf(FacetDateInclude.class);
+      if (include.contains(FacetRangeInclude.ALL)) 
+        return EnumSet.allOf(FacetRangeInclude.class);
 
       // use whatever we've got.
       return include;

Modified: lucene/dev/trunk/solr/src/java/org/apache/solr/handler/component/FacetComponent.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/src/java/org/apache/solr/handler/component/FacetComponent.java?rev=980555&r1=980554&r2=980555&view=diff
==============================================================================
--- lucene/dev/trunk/solr/src/java/org/apache/solr/handler/component/FacetComponent.java (original)
+++ lucene/dev/trunk/solr/src/java/org/apache/solr/handler/component/FacetComponent.java Thu Jul 29 20:05:41 2010
@@ -404,8 +404,9 @@ public class  FacetComponent extends Sea
       }
     }
 
-    // TODO: facet dates
+    // TODO: facet dates & numbers
     facet_counts.add("facet_dates", new SimpleOrderedMap());
+    facet_counts.add("facet_ranges", new SimpleOrderedMap());
 
     rb.rsp.add("facet_counts", facet_counts);
 
@@ -688,4 +689,4 @@ public class  FacetComponent extends Sea
       return "{term="+name+",termNum="+termNum+",count="+count+"}";
     }
   }
-}
\ No newline at end of file
+}

Modified: lucene/dev/trunk/solr/src/java/org/apache/solr/request/SimpleFacets.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/src/java/org/apache/solr/request/SimpleFacets.java?rev=980555&r1=980554&r2=980555&view=diff
==============================================================================
--- lucene/dev/trunk/solr/src/java/org/apache/solr/request/SimpleFacets.java (original)
+++ lucene/dev/trunk/solr/src/java/org/apache/solr/request/SimpleFacets.java Thu Jul 29 20:05:41 2010
@@ -31,13 +31,14 @@ import org.apache.solr.common.params.Fac
 import org.apache.solr.common.params.RequiredSolrParams;
 import org.apache.solr.common.params.SolrParams;
 import org.apache.solr.common.params.CommonParams;
-import org.apache.solr.common.params.FacetParams.FacetDateOther;
-import org.apache.solr.common.params.FacetParams.FacetDateInclude;
+import org.apache.solr.common.params.FacetParams.FacetRangeOther;
+import org.apache.solr.common.params.FacetParams.FacetRangeInclude;
 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.core.SolrCore;
 import org.apache.solr.schema.*;
+import org.apache.solr.schema.TrieField.TrieTypes;
 import org.apache.solr.search.*;
 import org.apache.solr.util.BoundedTreeSet;
 import org.apache.solr.util.ByteUtils;
@@ -63,11 +64,14 @@ public class SimpleFacets {
   protected DocSet docs;
   /** Configuration params behavior should be driven by */
   protected SolrParams params;
+  protected SolrParams required;
   /** Searcher to use for all calculations */
   protected SolrIndexSearcher searcher;
   protected SolrQueryRequest req;
   protected ResponseBuilder rb;
 
+  public final Date NOW = new Date();
+
   // per-facet values
   SolrParams localParams; // localParams on this particular facet command
   String facetValue;      // the field to or query to facet on (minus local params)
@@ -89,6 +93,7 @@ public class SimpleFacets {
     this.searcher = req.getSearcher();
     this.base = this.docs = docs;
     this.params = params;
+    this.required = new RequiredSolrParams(params);
     this.rb = rb;
   }
 
@@ -166,6 +171,7 @@ public class SimpleFacets {
    * @see #getFacetQueryCounts
    * @see #getFacetFieldCounts
    * @see #getFacetDateCounts
+   * @see #getFacetRangeCounts
    * @see FacetParams#FACET
    * @return a NamedList of Facet Count info or null
    */
@@ -181,7 +187,8 @@ public class SimpleFacets {
       res.add("facet_queries", getFacetQueryCounts());
       res.add("facet_fields", getFacetFieldCounts());
       res.add("facet_dates", getFacetDateCounts());
-      
+      res.add("facet_ranges", getFacetRangeCounts());
+
     } catch (Exception e) {
       SolrException.logOnce(SolrCore.log, "Exception during facet counts", e);
       res.add("exception", SolrException.toStr(e));
@@ -723,12 +730,10 @@ public class SimpleFacets {
    * @see FacetParams#FACET_DATE
    */
   public NamedList getFacetDateCounts()
-          throws IOException, ParseException {
+    throws IOException, ParseException {
 
-    final SolrParams required = new RequiredSolrParams(params);
     final NamedList resOuter = new SimpleOrderedMap();
     final String[] fields = params.getParams(FacetParams.FACET_DATE);
-    final Date NOW = new Date();
     
     if (null == fields || 0 == fields.length) return resOuter;
     
@@ -778,9 +783,9 @@ public class SimpleFacets {
       final DateMathParser dmp = new DateMathParser(ft.UTC, Locale.US);
       dmp.setNow(NOW);
 
-      int minCount = params.getFieldInt(f,FacetParams.FACET_MINCOUNT, 0);
+      final int minCount = params.getFieldInt(f,FacetParams.FACET_MINCOUNT, 0);
 
-      final EnumSet<FacetDateInclude> include = FacetDateInclude.parseParam
+      final EnumSet<FacetRangeInclude> include = FacetRangeInclude.parseParam
         (params.getFieldParams(f,FacetParams.FACET_DATE_INCLUDE));
 
       try {
@@ -802,14 +807,14 @@ public class SimpleFacets {
               (SolrException.ErrorCode.BAD_REQUEST,
                "date facet infinite loop (is gap negative?)");
           }
-          boolean includeLower = 
-            (include.contains(FacetDateInclude.LOWER) ||
-             (include.contains(FacetDateInclude.EDGE) && low.equals(start)));
-          boolean includeUpper = 
-            (include.contains(FacetDateInclude.UPPER) ||
-             (include.contains(FacetDateInclude.EDGE) && high.equals(end)));
+          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)));
 
-          int count = rangeCount(sf,low,high,includeLower,includeUpper);
+          final int count = rangeCount(sf,low,high,includeLower,includeUpper);
           if (count >= minCount) {
             resInner.add(label, count);
           }
@@ -821,49 +826,52 @@ public class SimpleFacets {
            "date facet 'gap' is not a valid Date Math string: " + gap, e);
       }
       
-      // explicitly return the gap and end so all the counts are meaningful
+      // 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 ) {
-        Set<FacetDateOther> others = EnumSet.noneOf(FacetDateOther.class);
+        final Set<FacetRangeOther> others = EnumSet.noneOf(FacetRangeOther.class);
 
         for (final String o : othersP) {
-          others.add(FacetDateOther.get(o));
+          others.add(FacetRangeOther.get(o));
         }
 
         // no matter what other values are listed, we don't do
         // anything if "none" is specified.
-        if (! others.contains(FacetDateOther.NONE) ) {          
-          boolean all = others.contains(FacetDateOther.ALL);
+        if (! others.contains(FacetRangeOther.NONE) ) {          
+          boolean all = others.contains(FacetRangeOther.ALL);
         
-          if (all || others.contains(FacetDateOther.BEFORE)) {
+          if (all || others.contains(FacetRangeOther.BEFORE)) {
             // include upper bound if "outer" or if first gap doesn't already include it
-            resInner.add(FacetDateOther.BEFORE.toString(),
+            resInner.add(FacetRangeOther.BEFORE.toString(),
                          rangeCount(sf,null,start,
                                     false,
-                                    (include.contains(FacetDateInclude.OUTER) ||
-                                     (! (include.contains(FacetDateInclude.LOWER) ||
-                                         include.contains(FacetDateInclude.EDGE))))));
+                                    (include.contains(FacetRangeInclude.OUTER) ||
+                                     (! (include.contains(FacetRangeInclude.LOWER) ||
+                                         include.contains(FacetRangeInclude.EDGE))))));
           }
-          if (all || others.contains(FacetDateOther.AFTER)) {
+          if (all || others.contains(FacetRangeOther.AFTER)) {
             // include lower bound if "outer" or if last gap doesn't already include it
-            resInner.add(FacetDateOther.AFTER.toString(),
+            resInner.add(FacetRangeOther.AFTER.toString(),
                          rangeCount(sf,end,null,
-                                    (include.contains(FacetDateInclude.OUTER) ||
-                                     (! (include.contains(FacetDateInclude.UPPER) ||
-                                         include.contains(FacetDateInclude.EDGE)))),
+                                    (include.contains(FacetRangeInclude.OUTER) ||
+                                     (! (include.contains(FacetRangeInclude.UPPER) ||
+                                         include.contains(FacetRangeInclude.EDGE)))),
                                     false));
           }
-          if (all || others.contains(FacetDateOther.BETWEEN)) {
-            resInner.add(FacetDateOther.BETWEEN.toString(),
+          if (all || others.contains(FacetRangeOther.BETWEEN)) {
+            resInner.add(FacetRangeOther.BETWEEN.toString(),
                          rangeCount(sf,start,end,
-                                    (include.contains(FacetDateInclude.LOWER) ||
-                                     include.contains(FacetDateInclude.EDGE)),
-                                    (include.contains(FacetDateInclude.UPPER) ||
-                                     include.contains(FacetDateInclude.EDGE))));
+                                    (include.contains(FacetRangeInclude.LOWER) ||
+                                     include.contains(FacetRangeInclude.EDGE)),
+                                    (include.contains(FacetRangeInclude.UPPER) ||
+                                     include.contains(FacetRangeInclude.EDGE))));
           }
         }
       }
@@ -872,6 +880,197 @@ public class SimpleFacets {
     return resOuter;
   }
 
+  
+  /**
+   * 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 getFacetRangeCounts()
+    throws IOException, ParseException {
+    
+    final NamedList resOuter = new SimpleOrderedMap();
+    final String[] fields = params.getParams(FacetParams.FACET_RANGE);
+    
+    if (null == fields || 0 == fields.length) return resOuter;
+    
+    final IndexSchema schema = searcher.getSchema();
+    for (String f : fields) {
+      parseParams(FacetParams.FACET_RANGE, f);
+      f = facetValue;
+      
+      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;
+        default:
+          throw new SolrException
+            (SolrException.ErrorCode.BAD_REQUEST,
+             "Unable to range facet on tried field of unexpected type:" + f);
+        }
+      } else if (ft instanceof DateField) {
+        calc = new DateRangeEndpointCalculator(sf, NOW);
+      } else if (ft instanceof SortableIntField) {
+        calc = new IntegerRangeEndpointCalculator(sf);
+      } else if (ft instanceof SortableLongField) {
+        calc = new LongRangeEndpointCalculator(sf);
+      } else if (ft instanceof SortableFloatField) {
+        calc = new FloatRangeEndpointCalculator(sf);
+      } else if (ft instanceof SortableDoubleField) {
+        calc = new DoubleRangeEndpointCalculator(sf);
+      } else {
+        throw new SolrException
+          (SolrException.ErrorCode.BAD_REQUEST,
+           "Unable to range facet on field:" + sf);
+      }
+
+      resOuter.add(key, getFacetRangeCounts(sf, calc));
+    }
+    
+    return resOuter;
+  }
+
+  private <T extends Comparable<T>> NamedList getFacetRangeCounts
+    (final SchemaField sf, 
+     final RangeEndpointCalculator<T> calc) throws IOException {
+    
+    final String f = sf.getName();
+    final NamedList res = new SimpleOrderedMap();
+    final NamedList counts = new SimpleOrderedMap();
+    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?)");
+      }
+      
+      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(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(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(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(sf,startS,endS,
+                            (include.contains(FacetRangeInclude.LOWER) ||
+                             include.contains(FacetRangeInclude.EDGE)),
+                            (include.contains(FacetRangeInclude.UPPER) ||
+                             include.contains(FacetRangeInclude.EDGE))));
+         
+        }
+      }
+    }
+    return res;
+  }  
+  
   /**
    * Macro for getting the numDocs of range over docs
    * @see SolrIndexSearcher#numDocs
@@ -914,5 +1113,173 @@ public class SimpleFacets {
       return (0 != vc ? vc : key.compareTo(o.key));
     }
   }
+
+
+  /**
+   * 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); }
+    protected Float parseVal(String rawval) {
+      return Float.valueOf(rawval);
+    }
+    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); }
+    protected Double parseVal(String rawval) {
+      return Double.valueOf(rawval);
+    }
+    public Double parseAndAddGap(Double value, String gap) {
+      return new Double(value.floatValue() + Double.valueOf(gap).floatValue());
+    }
+  }
+  private static class IntegerRangeEndpointCalculator 
+    extends RangeEndpointCalculator<Integer> {
+
+    public IntegerRangeEndpointCalculator(final SchemaField f) { super(f); }
+    protected Integer parseVal(String rawval) {
+      return Integer.valueOf(rawval);
+    }
+    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); }
+    protected Long parseVal(String rawval) {
+      return Long.valueOf(rawval);
+    }
+    public Long parseAndAddGap(Long value, String gap) {
+      return new Long(value.intValue() + Long.valueOf(gap).intValue());
+    }
+  }
+  private static class DateRangeEndpointCalculator 
+    extends RangeEndpointCalculator<Date> {
+    private final Date now;
+    public DateRangeEndpointCalculator(final SchemaField f, 
+                                       final Date now) { 
+      super(f); 
+      this.now = now;
+      if (! (field.getType() instanceof DateField) ) {
+        throw new IllegalArgumentException
+          ("SchemaField must use filed type extending DateField");
+      }
+    }
+    public String formatValue(Date val) {
+      return ((DateField)field.getType()).toExternal(val);
+    }
+    protected Date parseVal(String rawval) {
+      return ((DateField)field.getType()).parseMath(now, rawval);
+    }
+    protected Object parseGap(final String rawval) {
+      return rawval;
+    }
+    public Date parseAndAddGap(Date value, String gap) throws java.text.ParseException {
+      final DateMathParser dmp = new DateMathParser(DateField.UTC, Locale.US);
+      dmp.setNow(value);
+      return dmp.parseMath(gap);
+    }
+  }
+  
 }
 

Modified: lucene/dev/trunk/solr/src/test/org/apache/solr/ConvertedLegacyTest.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/src/test/org/apache/solr/ConvertedLegacyTest.java?rev=980555&r1=980554&r2=980555&view=diff
==============================================================================
--- lucene/dev/trunk/solr/src/test/org/apache/solr/ConvertedLegacyTest.java (original)
+++ lucene/dev/trunk/solr/src/test/org/apache/solr/ConvertedLegacyTest.java Thu Jul 29 20:05:41 2010
@@ -1150,7 +1150,7 @@ public class ConvertedLegacyTest extends
             ,"//str[.='Yonik']  "
             ,"//float[.='1.4142135'] "
             ,"//float[@name='score'] "
-            ,"*[count(//doc/*)=13]"
+            ,"*[count(//doc/*)>=13]"
             );
     args = new HashMap<String,String>();
     args.put("version","2.0");
@@ -1161,7 +1161,7 @@ public class ConvertedLegacyTest extends
             ,"//str[.='Yonik']  "
             ,"//float[.='1.4142135'] "
             ,"//float[@name='score'] "
-            ,"*[count(//doc/*)=13]"
+            ,"*[count(//doc/*)>=13]"
             );
     args = new HashMap<String,String>();
     args.put("version","2.0");

Modified: lucene/dev/trunk/solr/src/test/org/apache/solr/request/SimpleFacetsTest.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/src/test/org/apache/solr/request/SimpleFacetsTest.java?rev=980555&r1=980554&r2=980555&view=diff
==============================================================================
--- lucene/dev/trunk/solr/src/test/org/apache/solr/request/SimpleFacetsTest.java (original)
+++ lucene/dev/trunk/solr/src/test/org/apache/solr/request/SimpleFacetsTest.java Thu Jul 29 20:05:41 2010
@@ -72,18 +72,29 @@ public class SimpleFacetsTest extends So
   }
 
   static void indexSimpleFacetCounts() {
-    add_doc("id", "42", "trait_s", "Tool", "trait_s", "Obnoxious",
-                 "name", "Zapp Brannigan");
+    add_doc("id", "42", 
+            "range_facet_f", "35.3", 
+            "trait_s", "Tool", "trait_s", "Obnoxious",
+            "name", "Zapp Brannigan");
     add_doc("id", "43" ,
-                 "title", "Democratic Order of Planets");
-    add_doc("id", "44", "trait_s", "Tool",
-                 "name", "The Zapper");
-    add_doc("id", "45", "trait_s", "Chauvinist",
-                 "title", "25 star General");
-    add_doc("id", "46", "trait_s", "Obnoxious",
-                 "subject", "Defeated the pacifists of the Gandhi nebula");
-    add_doc("id", "47", "trait_s", "Pig",
-                 "text", "line up and fly directly at the enemy death cannons, clogging them with wreckage!");   
+            "range_facet_f", "28.789", 
+            "title", "Democratic Order of Planets");
+    add_doc("id", "44", 
+            "range_facet_f", "15.97", 
+            "trait_s", "Tool",
+            "name", "The Zapper");
+    add_doc("id", "45", 
+            "range_facet_f", "30.0", 
+            "trait_s", "Chauvinist",
+            "title", "25 star General");
+    add_doc("id", "46", 
+            "range_facet_f", "20.0", 
+            "trait_s", "Obnoxious",
+            "subject", "Defeated the pacifists of the Gandhi nebula");
+    add_doc("id", "47", 
+            "range_facet_f", "28.62", 
+            "trait_s", "Pig",
+            "text", "line up and fly directly at the enemy death cannons, clogging them with wreckage!");   
   }
 
   @Test
@@ -282,22 +293,43 @@ public class SimpleFacetsTest extends So
   }
 
   @Test
+  public void testTrieDateFacets() {
+    helpTestDateFacets("bday", false);
+  }
+  @Test
   public void testDateFacets() {
-    final String f = "bday";
-    final String pre = "//lst[@name='facet_dates']/lst[@name='"+f+"']";
+    helpTestDateFacets("bday_pdt", false);
+  }
+
+  @Test
+  public void testTrieDateRangeFacets() {
+    helpTestDateFacets("bday", true);
+  }
+  @Test
+  public void testDateRangeFacets() {
+    helpTestDateFacets("bday_pdt", true);
+  }
+
+  private void helpTestDateFacets(final String fieldName, 
+                                  final boolean rangeMode) {
+    final String p = rangeMode ? "facet.range" : "facet.date";
+    final String b = rangeMode ? "facet_ranges" : "facet_dates";
+    final String f = fieldName;
+    final String c = (rangeMode ? "/lst[@name='counts']" : "");
+    final String pre = "//lst[@name='"+b+"']/lst[@name='"+f+"']" + c;
+    final String meta = pre + (rangeMode ? "/../" : "");
 
     assertQ("check counts for month of facet by day",
             req( "q", "*:*"
                 ,"rows", "0"
                 ,"facet", "true"
-                ,"facet.date", f
-                ,"facet.date.start", "1976-07-01T00:00:00.000Z"
-                ,"facet.date.end",   "1976-07-01T00:00:00.000Z+1MONTH"
-                ,"facet.date.gap",   "+1DAY"
-                ,"facet.date.other", "all"
+                ,p, f
+                ,p+".start", "1976-07-01T00:00:00.000Z"
+                ,p+".end",   "1976-07-01T00:00:00.000Z+1MONTH"
+                ,p+".gap",   "+1DAY"
+                ,p+".other", "all"
                 )
-            // 31 days + pre+post+inner = 34
-            ,"*[count("+pre+"/int)=34]"
+            ,"*[count("+pre+"/int)="+(rangeMode ? 31 : 34)+"]"
             ,pre+"/int[@name='1976-07-01T00:00:00Z'][.='0'  ]"
             ,pre+"/int[@name='1976-07-02T00:00:00Z'][.='0'  ]"
             ,pre+"/int[@name='1976-07-03T00:00:00Z'][.='2'  ]"
@@ -331,9 +363,9 @@ public class SimpleFacetsTest extends So
             ,pre+"/int[@name='1976-07-30T00:00:00Z'][.='1'  ]"
             ,pre+"/int[@name='1976-07-31T00:00:00Z'][.='0']"
             
-            ,pre+"/int[@name='before' ][.='2']"
-            ,pre+"/int[@name='after'  ][.='1']"
-            ,pre+"/int[@name='between'][.='11']"
+            ,meta+"/int[@name='before' ][.='2']"
+            ,meta+"/int[@name='after'  ][.='1']"
+            ,meta+"/int[@name='between'][.='11']"
             
             );
 
@@ -341,15 +373,14 @@ public class SimpleFacetsTest extends So
             req( "q", "*:*"
                 ,"rows", "0"
                 ,"facet", "true"
-                ,"facet.date", f
-                ,"facet.date.start", "1976-07-01T00:00:00.000Z"
-                ,"facet.date.end",   "1976-07-01T00:00:00.000Z+1MONTH"
-                ,"facet.date.gap",   "+1DAY"
-                ,"facet.date.other", "all"
+                ,p, f
+                ,p+".start", "1976-07-01T00:00:00.000Z"
+                ,p+".end",   "1976-07-01T00:00:00.000Z+1MONTH"
+                ,p+".gap",   "+1DAY"
+                ,p+".other", "all"
                 ,"facet.mincount", "1"
                 )
-            // 31 days + pre+post+inner = 34
-            ,"*[count("+pre+"/int)=11]"
+            ,"*[count("+pre+"/int)="+(rangeMode ? 8 : 11)+"]"
             ,pre+"/int[@name='1976-07-03T00:00:00Z'][.='2'  ]"
             // july4th = 2 because exists doc @ 00:00:00.000 on July5
             // (date faceting is inclusive)
@@ -360,68 +391,65 @@ public class SimpleFacetsTest extends So
             ,pre+"/int[@name='1976-07-15T00:00:00Z'][.='2'  ]"
             ,pre+"/int[@name='1976-07-21T00:00:00Z'][.='1'  ]"
             ,pre+"/int[@name='1976-07-30T00:00:00Z'][.='1'  ]"
-            ,pre+"/int[@name='before' ][.='2']"
-            ,pre+"/int[@name='after'  ][.='1']"
-            ,pre+"/int[@name='between'][.='11']"
+            ,meta+"/int[@name='before' ][.='2']"
+            ,meta+"/int[@name='after'  ][.='1']"
+            ,meta+"/int[@name='between'][.='11']"
             );
 
     assertQ("check counts for month of facet by day with field mincount = 1",
             req( "q", "*:*"
                 ,"rows", "0"
                 ,"facet", "true"
-                ,"facet.date", f
-                ,"facet.date.start", "1976-07-01T00:00:00.000Z"
-                ,"facet.date.end",   "1976-07-01T00:00:00.000Z+1MONTH"
-                ,"facet.date.gap",   "+1DAY"
-                ,"facet.date.other", "all"
+                ,p, f
+                ,p+".start", "1976-07-01T00:00:00.000Z"
+                ,p+".end",   "1976-07-01T00:00:00.000Z+1MONTH"
+                ,p+".gap",   "+1DAY"
+                ,p+".other", "all"
                 ,"f." + f + ".facet.mincount", "2"
                 )
-            // 31 days + pre+post+inner = 34
-            ,"*[count("+pre+"/int)=7]"
+            ,"*[count("+pre+"/int)="+(rangeMode ? 4 : 7)+"]"
             ,pre+"/int[@name='1976-07-03T00:00:00Z'][.='2'  ]"
             // july4th = 2 because exists doc @ 00:00:00.000 on July5
             // (date faceting is inclusive)
             ,pre+"/int[@name='1976-07-04T00:00:00Z'][.='2'  ]"
             ,pre+"/int[@name='1976-07-05T00:00:00Z'][.='2'  ]"
             ,pre+"/int[@name='1976-07-15T00:00:00Z'][.='2'  ]"
-            ,pre+"/int[@name='before' ][.='2']"
-            ,pre+"/int[@name='after'  ][.='1']"
-            ,pre+"/int[@name='between'][.='11']"
+            ,meta+"/int[@name='before' ][.='2']"
+            ,meta+"/int[@name='after'  ][.='1']"
+            ,meta+"/int[@name='between'][.='11']"
             );
 
     assertQ("check before is not inclusive of upper bound by default",
             req("q", "*:*"
                 ,"rows", "0"
                 ,"facet", "true"
-                ,"facet.date", f
-                ,"facet.date.start",  "1976-07-05T00:00:00.000Z"
-                ,"facet.date.end",    "1976-07-07T00:00:00.000Z"
-                ,"facet.date.gap",    "+1DAY"
-                ,"facet.date.other",  "all"
+                ,p, f
+                ,p+".start",  "1976-07-05T00:00:00.000Z"
+                ,p+".end",    "1976-07-07T00:00:00.000Z"
+                ,p+".gap",    "+1DAY"
+                ,p+".other",  "all"
                 )
-            // 2 gaps + pre+post+inner = 5
-            ,"*[count("+pre+"/int)=5]"
+            ,"*[count("+pre+"/int)="+(rangeMode ? 2 : 5)+"]"
             ,pre+"/int[@name='1976-07-05T00:00:00Z'][.='2'  ]"
             ,pre+"/int[@name='1976-07-06T00:00:00Z'][.='0'  ]"
             
-            ,pre+"/int[@name='before' ][.='5']"
+            ,meta+"/int[@name='before' ][.='5']"
             );
     assertQ("check after is not inclusive of lower bound by default",
             req("q", "*:*"
                 ,"rows", "0"
                 ,"facet", "true"
-                ,"facet.date", f
-                ,"facet.date.start",  "1976-07-03T00:00:00.000Z"
-                ,"facet.date.end",    "1976-07-05T00:00:00.000Z"
-                ,"facet.date.gap",    "+1DAY"
-                ,"facet.date.other",  "all"
+                ,p, f
+                ,p+".start",  "1976-07-03T00:00:00.000Z"
+                ,p+".end",    "1976-07-05T00:00:00.000Z"
+                ,p+".gap",    "+1DAY"
+                ,p+".other",  "all"
                 )
-            // 2 gaps + pre+post+inner = 5
-            ,"*[count("+pre+"/int)=5]"
+            ,"*[count("+pre+"/int)="+(rangeMode ? 2 : 5)+"]"
             ,pre+"/int[@name='1976-07-03T00:00:00Z'][.='2'  ]"
             ,pre+"/int[@name='1976-07-04T00:00:00Z'][.='2'  ]"
             
-            ,pre+"/int[@name='after' ][.='8']"
+            ,meta+"/int[@name='after' ][.='8']"
             );
             
 
@@ -429,68 +457,88 @@ public class SimpleFacetsTest extends So
             req( "q", "*:*"
                 ,"rows", "0"
                 ,"facet", "true"
-                ,"facet.date", f
-                ,"facet.date.start",  "1976-07-01T00:00:00.000Z"
-                ,"facet.date.end",    "1976-07-13T00:00:00.000Z"
-                ,"facet.date.gap",    "+5DAYS"
-                ,"facet.date.other",  "all"
-                ,"facet.date.hardend","false"
+                ,p, f
+                ,p+".start",  "1976-07-01T00:00:00.000Z"
+                ,p+".end",    "1976-07-13T00:00:00.000Z"
+                ,p+".gap",    "+5DAYS"
+                ,p+".other",  "all"
+                ,p+".hardend","false"
                 )
-            // 3 gaps + pre+post+inner = 6
-            ,"*[count("+pre+"/int)=6]"
+            ,"*[count("+pre+"/int)="+(rangeMode ? 3 : 6)+"]"
             ,pre+"/int[@name='1976-07-01T00:00:00Z'][.='5'  ]"
             ,pre+"/int[@name='1976-07-06T00:00:00Z'][.='0'  ]"
             ,pre+"/int[@name='1976-07-11T00:00:00Z'][.='4'  ]"
             
-            ,pre+"/int[@name='before' ][.='2']"
-            ,pre+"/int[@name='after'  ][.='3']"
-            ,pre+"/int[@name='between'][.='9']"
+            ,meta+"/int[@name='before' ][.='2']"
+            ,meta+"/int[@name='after'  ][.='3']"
+            ,meta+"/int[@name='between'][.='9']"
             );
 
     assertQ("check hardend=true",
             req( "q", "*:*"
                 ,"rows", "0"
                 ,"facet", "true"
-                ,"facet.date", f
-                ,"facet.date.start",  "1976-07-01T00:00:00.000Z"
-                ,"facet.date.end",    "1976-07-13T00:00:00.000Z"
-                ,"facet.date.gap",    "+5DAYS"
-                ,"facet.date.other",  "all"
-                ,"facet.date.hardend","true"
+                ,p, f
+                ,p+".start",  "1976-07-01T00:00:00.000Z"
+                ,p+".end",    "1976-07-13T00:00:00.000Z"
+                ,p+".gap",    "+5DAYS"
+                ,p+".other",  "all"
+                ,p+".hardend","true"
                 )
-            // 3 gaps + pre+post+inner = 6
-            ,"*[count("+pre+"/int)=6]"
+            ,"*[count("+pre+"/int)="+(rangeMode ? 3 : 6)+"]"
             ,pre+"/int[@name='1976-07-01T00:00:00Z'][.='5'  ]"
             ,pre+"/int[@name='1976-07-06T00:00:00Z'][.='0'  ]"
             ,pre+"/int[@name='1976-07-11T00:00:00Z'][.='1'  ]"
             
-            ,pre+"/int[@name='before' ][.='2']"
-            ,pre+"/int[@name='after'  ][.='6']"
-            ,pre+"/int[@name='between'][.='6']"
+            ,meta+"/int[@name='before' ][.='2']"
+            ,meta+"/int[@name='after'  ][.='6']"
+            ,meta+"/int[@name='between'][.='6']"
             );
     
   }
 
-  /** similar to testDateFacets, but a differnet field with test data 
-      exactly on on boundary marks */
+  @Test
+  public void testTrieDateFacetsWithIncludeOption() {
+    helpTestDateFacetsWithIncludeOption("a_tdt", false);
+  }
   @Test
   public void testDateFacetsWithIncludeOption() {
-    final String f = "a_tdt";
-    final String pre = "//lst[@name='facet_dates']/lst[@name='"+f+"']";
+    helpTestDateFacetsWithIncludeOption("a_pdt", false);
+  }
+
+  @Test
+  public void testTrieDateRangeFacetsWithIncludeOption() {
+    helpTestDateFacetsWithIncludeOption("a_tdt", true);
+  }
+  @Test
+  public void testDateRangeFacetsWithIncludeOption() {
+    helpTestDateFacetsWithIncludeOption("a_pdt", true);
+  }
+
+  /** similar to helpTestDateFacets, but for differnet fields with test data 
+      exactly on on boundary marks */
+  private void helpTestDateFacetsWithIncludeOption(final String fieldName,
+                                                   final boolean rangeMode) {
+    final String p = rangeMode ? "facet.range" : "facet.date";
+    final String b = rangeMode ? "facet_ranges" : "facet_dates";
+    final String f = fieldName;
+    final String c = (rangeMode ? "/lst[@name='counts']" : "");
+    final String pre = "//lst[@name='"+b+"']/lst[@name='"+f+"']" + c;
+    final String meta = pre + (rangeMode ? "/../" : "");
 
     assertQ("checking counts for lower",
             req( "q", "*:*"
                 ,"rows", "0"
                 ,"facet", "true"
-                ,"facet.date", f
-                ,"facet.date.start", "1976-07-01T00:00:00.000Z"
-                ,"facet.date.end",   "1976-07-16T00:00:00.000Z"
-                ,"facet.date.gap",   "+1DAY"
-                ,"facet.date.other", "all"
-                ,"facet.date.include", "lower"
+                ,p, f
+                ,p+".start", "1976-07-01T00:00:00.000Z"
+                ,p+".end",   "1976-07-16T00:00:00.000Z"
+                ,p+".gap",   "+1DAY"
+                ,p+".other", "all"
+                ,p+".include", "lower"
                 )
             // 15 days + pre+post+inner = 18
-            ,"*[count("+pre+"/int)=18]"
+            ,"*[count("+pre+"/int)="+(rangeMode ? 15 : 18)+"]"
             ,pre+"/int[@name='1976-07-01T00:00:00Z'][.='1'  ]"
             ,pre+"/int[@name='1976-07-02T00:00:00Z'][.='0']"
             ,pre+"/int[@name='1976-07-03T00:00:00Z'][.='0']"
@@ -506,24 +554,25 @@ public class SimpleFacetsTest extends So
             ,pre+"/int[@name='1976-07-13T00:00:00Z'][.='2'  ]"
             ,pre+"/int[@name='1976-07-14T00:00:00Z'][.='0']"
             ,pre+"/int[@name='1976-07-15T00:00:00Z'][.='1'  ]"
-            ,pre+"/int[@name='before' ][.='1']"
-            ,pre+"/int[@name='after'  ][.='1']"
-            ,pre+"/int[@name='between'][.='8']"
+            //
+            ,meta+"/int[@name='before' ][.='1']"
+            ,meta+"/int[@name='after'  ][.='1']"
+            ,meta+"/int[@name='between'][.='8']"
             );
 
     assertQ("checking counts for upper",
             req( "q", "*:*"
                 ,"rows", "0"
                 ,"facet", "true"
-                ,"facet.date", f
-                ,"facet.date.start", "1976-07-01T00:00:00.000Z"
-                ,"facet.date.end",   "1976-07-16T00:00:00.000Z"
-                ,"facet.date.gap",   "+1DAY"
-                ,"facet.date.other", "all"
-                ,"facet.date.include", "upper"
+                ,p, f
+                ,p+".start", "1976-07-01T00:00:00.000Z"
+                ,p+".end",   "1976-07-16T00:00:00.000Z"
+                ,p+".gap",   "+1DAY"
+                ,p+".other", "all"
+                ,p+".include", "upper"
                 )
             // 15 days + pre+post+inner = 18
-            ,"*[count("+pre+"/int)=18]"
+            ,"*[count("+pre+"/int)="+(rangeMode ? 15 : 18)+"]"
             ,pre+"/int[@name='1976-07-01T00:00:00Z'][.='0']"
             ,pre+"/int[@name='1976-07-02T00:00:00Z'][.='0']"
             ,pre+"/int[@name='1976-07-03T00:00:00Z'][.='1'  ]"
@@ -539,25 +588,26 @@ public class SimpleFacetsTest extends So
             ,pre+"/int[@name='1976-07-13T00:00:00Z'][.='1'  ]"
             ,pre+"/int[@name='1976-07-14T00:00:00Z'][.='0']"
             ,pre+"/int[@name='1976-07-15T00:00:00Z'][.='1'  ]"
-            ,pre+"/int[@name='before' ][.='2']"
-            ,pre+"/int[@name='after'  ][.='1']"
-            ,pre+"/int[@name='between'][.='7']"
+            //
+            ,meta+"/int[@name='before' ][.='2']"
+            ,meta+"/int[@name='after'  ][.='1']"
+            ,meta+"/int[@name='between'][.='7']"
             );
 
     assertQ("checking counts for lower & upper",
             req( "q", "*:*"
                 ,"rows", "0"
                 ,"facet", "true"
-                ,"facet.date", f
-                ,"facet.date.start", "1976-07-01T00:00:00.000Z"
-                ,"facet.date.end",   "1976-07-16T00:00:00.000Z"
-                ,"facet.date.gap",   "+1DAY"
-                ,"facet.date.other", "all"
-                ,"facet.date.include", "lower"
-                ,"facet.date.include", "upper"
+                ,p, f
+                ,p+".start", "1976-07-01T00:00:00.000Z"
+                ,p+".end",   "1976-07-16T00:00:00.000Z"
+                ,p+".gap",   "+1DAY"
+                ,p+".other", "all"
+                ,p+".include", "lower"
+                ,p+".include", "upper"
                 )
             // 15 days + pre+post+inner = 18
-            ,"*[count("+pre+"/int)=18]"
+            ,"*[count("+pre+"/int)="+(rangeMode ? 15 : 18)+"]"
             ,pre+"/int[@name='1976-07-01T00:00:00Z'][.='1'  ]"
             ,pre+"/int[@name='1976-07-02T00:00:00Z'][.='0']"
             ,pre+"/int[@name='1976-07-03T00:00:00Z'][.='1'  ]"
@@ -573,25 +623,26 @@ public class SimpleFacetsTest extends So
             ,pre+"/int[@name='1976-07-13T00:00:00Z'][.='2'  ]"
             ,pre+"/int[@name='1976-07-14T00:00:00Z'][.='0']"
             ,pre+"/int[@name='1976-07-15T00:00:00Z'][.='1'  ]"
-            ,pre+"/int[@name='before' ][.='1']"
-            ,pre+"/int[@name='after'  ][.='1']"
-            ,pre+"/int[@name='between'][.='8']"
+            //
+            ,meta+"/int[@name='before' ][.='1']"
+            ,meta+"/int[@name='after'  ][.='1']"
+            ,meta+"/int[@name='between'][.='8']"
             );
 
     assertQ("checking counts for upper & edge",
             req( "q", "*:*"
                 ,"rows", "0"
                 ,"facet", "true"
-                ,"facet.date", f
-                ,"facet.date.start", "1976-07-01T00:00:00.000Z"
-                ,"facet.date.end",   "1976-07-16T00:00:00.000Z"
-                ,"facet.date.gap",   "+1DAY"
-                ,"facet.date.other", "all"
-                ,"facet.date.include", "upper"
-                ,"facet.date.include", "edge"
+                ,p, f
+                ,p+".start", "1976-07-01T00:00:00.000Z"
+                ,p+".end",   "1976-07-16T00:00:00.000Z"
+                ,p+".gap",   "+1DAY"
+                ,p+".other", "all"
+                ,p+".include", "upper"
+                ,p+".include", "edge"
                 )
             // 15 days + pre+post+inner = 18
-            ,"*[count("+pre+"/int)=18]"
+            ,"*[count("+pre+"/int)="+(rangeMode ? 15 : 18)+"]"
             ,pre+"/int[@name='1976-07-01T00:00:00Z'][.='1'  ]"
             ,pre+"/int[@name='1976-07-02T00:00:00Z'][.='0']"
             ,pre+"/int[@name='1976-07-03T00:00:00Z'][.='1'  ]"
@@ -607,25 +658,26 @@ public class SimpleFacetsTest extends So
             ,pre+"/int[@name='1976-07-13T00:00:00Z'][.='1'  ]"
             ,pre+"/int[@name='1976-07-14T00:00:00Z'][.='0']"
             ,pre+"/int[@name='1976-07-15T00:00:00Z'][.='1'  ]"
-            ,pre+"/int[@name='before' ][.='1']"
-            ,pre+"/int[@name='after'  ][.='1']"
-            ,pre+"/int[@name='between'][.='8']"
+            //
+            ,meta+"/int[@name='before' ][.='1']"
+            ,meta+"/int[@name='after'  ][.='1']"
+            ,meta+"/int[@name='between'][.='8']"
             );
 
     assertQ("checking counts for upper & outer",
             req( "q", "*:*"
                 ,"rows", "0"
                 ,"facet", "true"
-                ,"facet.date", f
-                ,"facet.date.start", "1976-07-01T00:00:00.000Z"
-                ,"facet.date.end",   "1976-07-13T00:00:00.000Z" // smaller now
-                ,"facet.date.gap",   "+1DAY"
-                ,"facet.date.other", "all"
-                ,"facet.date.include", "upper"
-                ,"facet.date.include", "outer"
+                ,p, f
+                ,p+".start", "1976-07-01T00:00:00.000Z"
+                ,p+".end",   "1976-07-13T00:00:00.000Z" // smaller now
+                ,p+".gap",   "+1DAY"
+                ,p+".other", "all"
+                ,p+".include", "upper"
+                ,p+".include", "outer"
                 )
             // 12 days + pre+post+inner = 15
-            ,"*[count("+pre+"/int)=15]"
+            ,"*[count("+pre+"/int)="+(rangeMode ? 12 : 15)+"]"
             ,pre+"/int[@name='1976-07-01T00:00:00Z'][.='0']"
             ,pre+"/int[@name='1976-07-02T00:00:00Z'][.='0']"
             ,pre+"/int[@name='1976-07-03T00:00:00Z'][.='1'  ]"
@@ -638,25 +690,26 @@ public class SimpleFacetsTest extends So
             ,pre+"/int[@name='1976-07-10T00:00:00Z'][.='0']"
             ,pre+"/int[@name='1976-07-11T00:00:00Z'][.='0']"
             ,pre+"/int[@name='1976-07-12T00:00:00Z'][.='1'  ]"
-            ,pre+"/int[@name='before' ][.='2']"
-            ,pre+"/int[@name='after'  ][.='4']"
-            ,pre+"/int[@name='between'][.='5']"
+            //
+            ,meta+"/int[@name='before' ][.='2']"
+            ,meta+"/int[@name='after'  ][.='4']"
+            ,meta+"/int[@name='between'][.='5']"
             );
 
     assertQ("checking counts for lower & edge",
             req( "q", "*:*"
                 ,"rows", "0"
                 ,"facet", "true"
-                ,"facet.date", f
-                ,"facet.date.start", "1976-07-01T00:00:00.000Z"
-                ,"facet.date.end",   "1976-07-13T00:00:00.000Z" // smaller now
-                ,"facet.date.gap",   "+1DAY"
-                ,"facet.date.other", "all"
-                ,"facet.date.include", "lower"
-                ,"facet.date.include", "edge"
+                ,p, f
+                ,p+".start", "1976-07-01T00:00:00.000Z"
+                ,p+".end",   "1976-07-13T00:00:00.000Z" // smaller now
+                ,p+".gap",   "+1DAY"
+                ,p+".other", "all"
+                ,p+".include", "lower"
+                ,p+".include", "edge"
                 )
             // 12 days + pre+post+inner = 15
-            ,"*[count("+pre+"/int)=15]"
+            ,"*[count("+pre+"/int)="+(rangeMode ? 12 : 15)+"]"
             ,pre+"/int[@name='1976-07-01T00:00:00Z'][.='1'  ]"
             ,pre+"/int[@name='1976-07-02T00:00:00Z'][.='0']"
             ,pre+"/int[@name='1976-07-03T00:00:00Z'][.='0']"
@@ -669,25 +722,26 @@ public class SimpleFacetsTest extends So
             ,pre+"/int[@name='1976-07-10T00:00:00Z'][.='0']"
             ,pre+"/int[@name='1976-07-11T00:00:00Z'][.='0']"
             ,pre+"/int[@name='1976-07-12T00:00:00Z'][.='1'  ]"
-            ,pre+"/int[@name='before' ][.='1']"
-            ,pre+"/int[@name='after'  ][.='3']"
-            ,pre+"/int[@name='between'][.='6']"
+            //
+            ,meta+"/int[@name='before' ][.='1']"
+            ,meta+"/int[@name='after'  ][.='3']"
+            ,meta+"/int[@name='between'][.='6']"
             );
 
     assertQ("checking counts for lower & outer",
             req( "q", "*:*"
                 ,"rows", "0"
                 ,"facet", "true"
-                ,"facet.date", f
-                ,"facet.date.start", "1976-07-01T00:00:00.000Z"
-                ,"facet.date.end",   "1976-07-13T00:00:00.000Z" // smaller now
-                ,"facet.date.gap",   "+1DAY"
-                ,"facet.date.other", "all"
-                ,"facet.date.include", "lower"
-                ,"facet.date.include", "outer"
+                ,p, f
+                ,p+".start", "1976-07-01T00:00:00.000Z"
+                ,p+".end",   "1976-07-13T00:00:00.000Z" // smaller now
+                ,p+".gap",   "+1DAY"
+                ,p+".other", "all"
+                ,p+".include", "lower"
+                ,p+".include", "outer"
                 )
             // 12 days + pre+post+inner = 15
-            ,"*[count("+pre+"/int)=15]"
+            ,"*[count("+pre+"/int)="+(rangeMode ? 12 : 15)+"]"
             ,pre+"/int[@name='1976-07-01T00:00:00Z'][.='1'  ]"
             ,pre+"/int[@name='1976-07-02T00:00:00Z'][.='0']"
             ,pre+"/int[@name='1976-07-03T00:00:00Z'][.='0']"
@@ -700,26 +754,27 @@ public class SimpleFacetsTest extends So
             ,pre+"/int[@name='1976-07-10T00:00:00Z'][.='0']"
             ,pre+"/int[@name='1976-07-11T00:00:00Z'][.='0']"
             ,pre+"/int[@name='1976-07-12T00:00:00Z'][.='0']"
-            ,pre+"/int[@name='before' ][.='2']"
-            ,pre+"/int[@name='after'  ][.='4']"
-            ,pre+"/int[@name='between'][.='5']"
+            //
+            ,meta+"/int[@name='before' ][.='2']"
+            ,meta+"/int[@name='after'  ][.='4']"
+            ,meta+"/int[@name='between'][.='5']"
             );
 
     assertQ("checking counts for lower & edge & outer",
             req( "q", "*:*"
                 ,"rows", "0"
                 ,"facet", "true"
-                ,"facet.date", f
-                ,"facet.date.start", "1976-07-01T00:00:00.000Z"
-                ,"facet.date.end",   "1976-07-13T00:00:00.000Z" // smaller now
-                ,"facet.date.gap",   "+1DAY"
-                ,"facet.date.other", "all"
-                ,"facet.date.include", "lower"
-                ,"facet.date.include", "edge"
-                ,"facet.date.include", "outer"
+                ,p, f
+                ,p+".start", "1976-07-01T00:00:00.000Z"
+                ,p+".end",   "1976-07-13T00:00:00.000Z" // smaller now
+                ,p+".gap",   "+1DAY"
+                ,p+".other", "all"
+                ,p+".include", "lower"
+                ,p+".include", "edge"
+                ,p+".include", "outer"
                 )
             // 12 days + pre+post+inner = 15
-            ,"*[count("+pre+"/int)=15]"
+            ,"*[count("+pre+"/int)="+(rangeMode ? 12 : 15)+"]"
             ,pre+"/int[@name='1976-07-01T00:00:00Z'][.='1'  ]"
             ,pre+"/int[@name='1976-07-02T00:00:00Z'][.='0']"
             ,pre+"/int[@name='1976-07-03T00:00:00Z'][.='0']"
@@ -732,24 +787,25 @@ public class SimpleFacetsTest extends So
             ,pre+"/int[@name='1976-07-10T00:00:00Z'][.='0']"
             ,pre+"/int[@name='1976-07-11T00:00:00Z'][.='0']"
             ,pre+"/int[@name='1976-07-12T00:00:00Z'][.='1'  ]"
-            ,pre+"/int[@name='before' ][.='2']"
-            ,pre+"/int[@name='after'  ][.='4']"
-            ,pre+"/int[@name='between'][.='6']"
+            //
+            ,meta+"/int[@name='before' ][.='2']"
+            ,meta+"/int[@name='after'  ][.='4']"
+            ,meta+"/int[@name='between'][.='6']"
             );
 
     assertQ("checking counts for all",
             req( "q", "*:*"
                 ,"rows", "0"
                 ,"facet", "true"
-                ,"facet.date", f
-                ,"facet.date.start", "1976-07-01T00:00:00.000Z"
-                ,"facet.date.end",   "1976-07-13T00:00:00.000Z" // smaller now
-                ,"facet.date.gap",   "+1DAY"
-                ,"facet.date.other", "all"
-                ,"facet.date.include", "all"
+                ,p, f
+                ,p+".start", "1976-07-01T00:00:00.000Z"
+                ,p+".end",   "1976-07-13T00:00:00.000Z" // smaller now
+                ,p+".gap",   "+1DAY"
+                ,p+".other", "all"
+                ,p+".include", "all"
                 )
             // 12 days + pre+post+inner = 15
-            ,"*[count("+pre+"/int)=15]"
+            ,"*[count("+pre+"/int)="+(rangeMode ? 12 : 15)+"]"
             ,pre+"/int[@name='1976-07-01T00:00:00Z'][.='1'  ]"
             ,pre+"/int[@name='1976-07-02T00:00:00Z'][.='0']"
             ,pre+"/int[@name='1976-07-03T00:00:00Z'][.='1'  ]"
@@ -762,10 +818,300 @@ public class SimpleFacetsTest extends So
             ,pre+"/int[@name='1976-07-10T00:00:00Z'][.='0']"
             ,pre+"/int[@name='1976-07-11T00:00:00Z'][.='0']"
             ,pre+"/int[@name='1976-07-12T00:00:00Z'][.='1'  ]"
-            ,pre+"/int[@name='before' ][.='2']"
-            ,pre+"/int[@name='after'  ][.='4']"
-            ,pre+"/int[@name='between'][.='6']"
+            //
+            ,meta+"/int[@name='before' ][.='2']"
+            ,meta+"/int[@name='after'  ][.='4']"
+            ,meta+"/int[@name='between'][.='6']"
+            );
+  }
+
+  @Test
+  public void testNumericRangeFacetsTrieFloat() {
+    helpTestFractionalNumberRangeFacets("range_facet_f");
+  }
+  @Test
+  public void testNumericRangeFacetsTrieDouble() {
+    helpTestFractionalNumberRangeFacets("range_facet_d");
+  }
+  @Test
+  public void testNumericRangeFacetsSortableFloat() {
+    helpTestFractionalNumberRangeFacets("range_facet_sf");
+  }
+  @Test
+  public void testNumericRangeFacetsSortableDouble() {
+    helpTestFractionalNumberRangeFacets("range_facet_sd");
+  }
+  private void helpTestFractionalNumberRangeFacets(final String fieldName) {
+
+    final String f = fieldName;
+    final String pre = "//lst[@name='facet_ranges']/lst[@name='"+f+"']/lst[@name='counts']";
+    final String meta = pre + "/../";
+
+    assertQ(f+": checking counts for lower",
+            req( "q", "*:*"
+                ,"rows", "0"
+                ,"facet", "true"
+                ,"facet.range", f
+                ,"facet.range.start", "10"
+                ,"facet.range.end",   "50"
+                ,"facet.range.gap",   "10"
+                ,"facet.range.other", "all"
+                ,"facet.range.include", "lower"
+                )
+            ,"*[count("+pre+"/int)=4]"
+            ,pre+"/int[@name='10.0'][.='1'  ]"
+            ,pre+"/int[@name='20.0'][.='3'  ]"
+            ,pre+"/int[@name='30.0'][.='2'  ]"
+            ,pre+"/int[@name='40.0'][.='0'  ]"
+            //
+            ,meta+"/int[@name='before' ][.='0']"
+            ,meta+"/int[@name='after'  ][.='0']"
+            ,meta+"/int[@name='between'][.='6']"
             );
+
+    assertQ(f + ":checking counts for upper",
+            req( "q", "*:*"
+                ,"rows", "0"
+                ,"facet", "true"
+                ,"facet.range", f
+                ,"facet.range.start", "10"
+                ,"facet.range.end",   "50"
+                ,"facet.range.gap",   "10"
+                ,"facet.range.other", "all"
+                ,"facet.range.include", "upper"
+                )
+            ,"*[count("+pre+"/int)=4]"
+            ,pre+"/int[@name='10.0'][.='2'  ]"
+            ,pre+"/int[@name='20.0'][.='3'  ]"
+            ,pre+"/int[@name='30.0'][.='1'  ]"
+            ,pre+"/int[@name='40.0'][.='0'  ]"
+            //
+            ,meta+"/int[@name='before' ][.='0']"
+            ,meta+"/int[@name='after'  ][.='0']"
+            ,meta+"/int[@name='between'][.='6']"
+            );
+
+    assertQ(f + ":checking counts for lower & upper",
+            req( "q", "*:*"
+                ,"rows", "0"
+                ,"facet", "true"
+                ,"facet.range", f
+                ,"facet.range.start", "10"
+                ,"facet.range.end",   "50"
+                ,"facet.range.gap",   "10"
+                ,"facet.range.other", "all"
+                ,"facet.range.include", "upper"
+                ,"facet.range.include", "lower"
+                )
+            ,"*[count("+pre+"/int)=4]"
+            ,pre+"/int[@name='10.0'][.='2'  ]"
+            ,pre+"/int[@name='20.0'][.='4'  ]"
+            ,pre+"/int[@name='30.0'][.='2'  ]"
+            ,pre+"/int[@name='40.0'][.='0'  ]"
+            //
+            ,meta+"/int[@name='before' ][.='0']"
+            ,meta+"/int[@name='after'  ][.='0']"
+            ,meta+"/int[@name='between'][.='6']"
+            );
+
+    assertQ(f + ": checking counts for upper & edge",
+            req( "q", "*:*"
+                ,"rows", "0"
+                ,"facet", "true"
+                ,"facet.range", f
+                ,"facet.range.start", "20"
+                ,"facet.range.end",   "50"
+                ,"facet.range.gap",   "10"
+                ,"facet.range.other", "all"
+                ,"facet.range.include", "upper"
+                ,"facet.range.include", "edge"
+                )
+            ,"*[count("+pre+"/int)=3]"
+            ,pre+"/int[@name='20.0'][.='4'  ]"
+            ,pre+"/int[@name='30.0'][.='1'  ]"
+            ,pre+"/int[@name='40.0'][.='0'  ]"
+            //
+            ,meta+"/int[@name='before' ][.='1']"
+            ,meta+"/int[@name='after'  ][.='0']"
+            ,meta+"/int[@name='between'][.='5']"
+            );
+
+    assertQ(f + ": checking counts for upper & outer",
+            req( "q", "*:*"
+                ,"rows", "0"
+                ,"facet", "true"
+                ,"facet.range", f
+                ,"facet.range.start", "10"
+                ,"facet.range.end",   "30"
+                ,"facet.range.gap",   "10"
+                ,"facet.range.other", "all"
+                ,"facet.range.include", "upper"
+                ,"facet.range.include", "outer"
+                )
+            ,"*[count("+pre+"/int)=2]"
+            ,pre+"/int[@name='10.0'][.='2'  ]"
+            ,pre+"/int[@name='20.0'][.='3'  ]"
+            //
+            ,meta+"/int[@name='before' ][.='0']"
+            ,meta+"/int[@name='after'  ][.='2']"
+            ,meta+"/int[@name='between'][.='5']"
+            );
+
+    assertQ(f + ": checking counts for lower & edge",
+            req( "q", "*:*"
+                ,"rows", "0"
+                ,"facet", "true"
+                ,"facet.range", f
+                ,"facet.range.start", "10"
+                ,"facet.range.end",   "30"
+                ,"facet.range.gap",   "10"
+                ,"facet.range.other", "all"
+                ,"facet.range.include", "lower"
+                ,"facet.range.include", "edge"
+                )
+            ,"*[count("+pre+"/int)=2]"
+            ,pre+"/int[@name='10.0'][.='1'  ]"
+            ,pre+"/int[@name='20.0'][.='4'  ]"
+            //
+            ,meta+"/int[@name='before' ][.='0']"
+            ,meta+"/int[@name='after'  ][.='1']"
+            ,meta+"/int[@name='between'][.='5']"
+            );
+
+    assertQ(f + ": checking counts for lower & outer",
+            req( "q", "*:*"
+                ,"rows", "0"
+                ,"facet", "true"
+                ,"facet.range", f
+                ,"facet.range.start", "20"
+                ,"facet.range.end",   "40"
+                ,"facet.range.gap",   "10"
+                ,"facet.range.other", "all"
+                ,"facet.range.include", "lower"
+                ,"facet.range.include", "outer"
+                )
+            ,"*[count("+pre+"/int)=2]"
+            ,pre+"/int[@name='20.0'][.='3'  ]"
+            ,pre+"/int[@name='30.0'][.='2'  ]"
+            //
+            ,meta+"/int[@name='before' ][.='2']"
+            ,meta+"/int[@name='after'  ][.='0']"
+            ,meta+"/int[@name='between'][.='5']"
+            );
+
+    assertQ(f + ": checking counts for lower & edge & outer",
+            req( "q", "*:*"
+                ,"rows", "0"
+                ,"facet", "true"
+                ,"facet.range", f
+                ,"facet.range.start", "20"
+                ,"facet.range.end",   "35.3"
+                ,"facet.range.gap",   "10"
+                ,"facet.range.other", "all"
+                ,"facet.range.hardend", "true"
+                ,"facet.range.include", "lower"
+                ,"facet.range.include", "edge"
+                ,"facet.range.include", "outer"
+                )
+            ,"*[count("+pre+"/int)=2]"
+            ,pre+"/int[@name='20.0'][.='3'  ]"
+            ,pre+"/int[@name='30.0'][.='2'  ]"
+            //
+            ,meta+"/int[@name='before' ][.='2']"
+            ,meta+"/int[@name='after'  ][.='1']"
+            ,meta+"/int[@name='between'][.='5']"
+            );
+
+    assertQ(f + ": checking counts for include all",
+            req( "q", "*:*"
+                ,"rows", "0"
+                ,"facet", "true"
+                ,"facet.range", f
+                ,"facet.range.start", "20"
+                ,"facet.range.end",   "35.3"
+                ,"facet.range.gap",   "10"
+                ,"facet.range.other", "all"
+                ,"facet.range.hardend", "true"
+                ,"facet.range.include", "all"
+                )
+            ,"*[count("+pre+"/int)=2]"
+            ,pre+"/int[@name='20.0'][.='4'  ]"
+            ,pre+"/int[@name='30.0'][.='2'  ]"
+            //
+            ,meta+"/int[@name='before' ][.='2']"
+            ,meta+"/int[@name='after'  ][.='1']"
+            ,meta+"/int[@name='between'][.='5']"
+            );
+  }
+
+  @Test
+  public void testNumericRangeFacetsTrieInt() {
+    helpTestWholeNumberRangeFacets("id");
+  }
+  @Test
+  public void testNumericRangeFacetsTrieLong() {
+    helpTestWholeNumberRangeFacets("range_facet_l");
+  }
+  @Test
+  public void testNumericRangeFacetsSortableInt() {
+    helpTestWholeNumberRangeFacets("range_facet_si");
+  }
+  @Test
+  public void testNumericRangeFacetsSortableLong() {
+    helpTestWholeNumberRangeFacets("range_facet_sl");
+  }
+
+  private void helpTestWholeNumberRangeFacets(final String fieldName) {
+
+    // the float test covers a lot of the weird edge cases
+    // here we just need some basic sanity checking of the parsing
+
+    final String f = fieldName;
+    final String pre = "//lst[@name='facet_ranges']/lst[@name='"+f+"']/lst[@name='counts']";
+    final String meta = pre + "/../";
+
+    assertQ(f+": checking counts for lower",
+            req( "q", "id:[30 TO 60]"
+                ,"rows", "0"
+                ,"facet", "true"
+                ,"facet.range", f
+                ,"facet.range.start", "35"
+                ,"facet.range.end",   "50"
+                ,"facet.range.gap",   "5"
+                ,"facet.range.other", "all"
+                ,"facet.range.include", "lower"
+                )
+            ,"*[count("+pre+"/int)=3]"
+            ,pre+"/int[@name='35'][.='0'  ]"
+            ,pre+"/int[@name='40'][.='3'  ]"
+            ,pre+"/int[@name='45'][.='3'  ]"
+            //
+            ,meta+"/int[@name='before' ][.='0']"
+            ,meta+"/int[@name='after'  ][.='0']"
+            ,meta+"/int[@name='between'][.='6']"
+            );
+
+    assertQ(f + ":checking counts for upper",
+            req( "q", "id:[30 TO 60]"
+                ,"rows", "0"
+                ,"facet", "true"
+                ,"facet.range", f
+                ,"facet.range.start", "35"
+                ,"facet.range.end",   "50"
+                ,"facet.range.gap",   "5"
+                ,"facet.range.other", "all"
+                ,"facet.range.include", "upper"
+                )
+            ,"*[count("+pre+"/int)=3]"
+            ,pre+"/int[@name='35'][.='0'  ]"
+            ,pre+"/int[@name='40'][.='4'  ]"
+            ,pre+"/int[@name='45'][.='2'  ]"
+            //
+            ,meta+"/int[@name='before' ][.='0']"
+            ,meta+"/int[@name='after'  ][.='0']"
+            ,meta+"/int[@name='between'][.='6']"
+            );
+    
   }
 
   static void indexFacetSingleValued() {

Modified: lucene/dev/trunk/solr/src/test/test-files/solr/conf/schema.xml
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/src/test/test-files/solr/conf/schema.xml?rev=980555&r1=980554&r2=980555&view=diff
==============================================================================
--- lucene/dev/trunk/solr/src/test/test-files/solr/conf/schema.xml (original)
+++ lucene/dev/trunk/solr/src/test/test-files/solr/conf/schema.xml Thu Jul 29 20:05:41 2010
@@ -564,7 +564,18 @@
    <copyField source="subject" dest="text"/>
  
    <copyField source="*_t" dest="text"/>
+
+   <copyField source="id"            dest="range_facet_si"/>
+   <copyField source="id"            dest="range_facet_l"/>
+   <copyField source="id"            dest="range_facet_sl"/>
+   <copyField source="range_facet_f" dest="range_facet_sf"/>
+   <copyField source="range_facet_f" dest="range_facet_d"/>
+   <copyField source="range_facet_f" dest="range_facet_sd"/>
+
+   <copyField source="bday" dest="bday_pdt"/>
+   <copyField source="a_tdt" dest="a_pdt"/>
    
+
    <!-- dynamic destination -->
    <copyField source="*_dynamic" dest="dynamic_*"/>