You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by er...@apache.org on 2014/07/24 00:02:17 UTC

svn commit: r1612958 - in /lucene/dev/branches/branch_4x: ./ solr/ solr/core/ solr/core/src/java/org/apache/solr/handler/component/ solr/core/src/java/org/apache/solr/request/ solr/core/src/test-files/solr/collection1/conf/ solr/core/src/test/org/apach...

Author: erick
Date: Wed Jul 23 22:02:16 2014
New Revision: 1612958

URL: http://svn.apache.org/r1612958
Log:
SOLR-6216: Better faceting for multiple intervals on DV fields. Thanks Tomas

Added:
    lucene/dev/branches/branch_4x/solr/core/src/java/org/apache/solr/request/IntervalFacets.java
      - copied unchanged from r1612889, lucene/dev/trunk/solr/core/src/java/org/apache/solr/request/IntervalFacets.java
    lucene/dev/branches/branch_4x/solr/core/src/test-files/solr/collection1/conf/schema-distrib-interval-faceting.xml
      - copied unchanged from r1612889, lucene/dev/trunk/solr/core/src/test-files/solr/collection1/conf/schema-distrib-interval-faceting.xml
    lucene/dev/branches/branch_4x/solr/core/src/test/org/apache/solr/DistributedIntervalFacetingTest.java
      - copied, changed from r1612889, lucene/dev/trunk/solr/core/src/test/org/apache/solr/DistributedIntervalFacetingTest.java
    lucene/dev/branches/branch_4x/solr/core/src/test/org/apache/solr/request/TestIntervalFaceting.java
      - copied, changed from r1612889, lucene/dev/trunk/solr/core/src/test/org/apache/solr/request/TestIntervalFaceting.java
Modified:
    lucene/dev/branches/branch_4x/   (props changed)
    lucene/dev/branches/branch_4x/solr/   (props changed)
    lucene/dev/branches/branch_4x/solr/CHANGES.txt   (contents, props changed)
    lucene/dev/branches/branch_4x/solr/core/   (props changed)
    lucene/dev/branches/branch_4x/solr/core/src/java/org/apache/solr/handler/component/FacetComponent.java
    lucene/dev/branches/branch_4x/solr/core/src/java/org/apache/solr/request/SimpleFacets.java
    lucene/dev/branches/branch_4x/solr/core/src/test-files/solr/collection1/conf/schema-docValuesFaceting.xml
    lucene/dev/branches/branch_4x/solr/core/src/test/org/apache/solr/TestGroupingSearch.java
    lucene/dev/branches/branch_4x/solr/solrj/   (props changed)
    lucene/dev/branches/branch_4x/solr/solrj/src/java/org/apache/solr/common/params/FacetParams.java
    lucene/dev/branches/branch_4x/solr/test-framework/   (props changed)
    lucene/dev/branches/branch_4x/solr/test-framework/src/java/org/apache/solr/SolrTestCaseJ4.java

Modified: lucene/dev/branches/branch_4x/solr/CHANGES.txt
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_4x/solr/CHANGES.txt?rev=1612958&r1=1612957&r2=1612958&view=diff
==============================================================================
--- lucene/dev/branches/branch_4x/solr/CHANGES.txt (original)
+++ lucene/dev/branches/branch_4x/solr/CHANGES.txt Wed Jul 23 22:02:16 2014
@@ -88,6 +88,9 @@ New Features
 
 * SOLR-6263: Add DIH handler name to variable resolver as ${dih.handlerName}. (ehatcher)
 
+* SOLR-6216: Better faceting for multiple intervals on DV fields (Tomas Fernandez-Lobbe
+  via Erick Erickson)
+
 
 Bug Fixes
 ----------------------

Modified: lucene/dev/branches/branch_4x/solr/core/src/java/org/apache/solr/handler/component/FacetComponent.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_4x/solr/core/src/java/org/apache/solr/handler/component/FacetComponent.java?rev=1612958&r1=1612957&r2=1612958&view=diff
==============================================================================
--- lucene/dev/branches/branch_4x/solr/core/src/java/org/apache/solr/handler/component/FacetComponent.java (original)
+++ lucene/dev/branches/branch_4x/solr/core/src/java/org/apache/solr/handler/component/FacetComponent.java Wed Jul 23 22:02:16 2014
@@ -23,6 +23,7 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Comparator;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
@@ -326,92 +327,16 @@ public class FacetComponent extends Sear
       }
 
       // Distributed facet_dates
-      //
-      // The implementation below uses the first encountered shard's 
-      // facet_dates as the basis for subsequent shards' data to be merged.
-      // (the "NOW" param should ensure consistency)
-      @SuppressWarnings("unchecked")
-      SimpleOrderedMap<SimpleOrderedMap<Object>> facet_dates = 
-        (SimpleOrderedMap<SimpleOrderedMap<Object>>) 
-        facet_counts.get("facet_dates");
-      
-      if (facet_dates != null) {
+      doDistribDates(fi, facet_counts);
 
-        // go through each facet_date
-        for (Map.Entry<String,SimpleOrderedMap<Object>> entry : facet_dates) {
-          final String field = entry.getKey();
-          if (fi.dateFacets.get(field) == null) { 
-            // first time we've seen this field, no merging
-            fi.dateFacets.add(field, entry.getValue());
-
-          } else { 
-            // not the first time, merge current field
-
-            SimpleOrderedMap<Object> shardFieldValues 
-              = entry.getValue();
-            SimpleOrderedMap<Object> existFieldValues 
-              = fi.dateFacets.get(field);
-
-            for (Map.Entry<String,Object> existPair : existFieldValues) {
-              final String key = existPair.getKey();
-              if (key.equals("gap") || 
-                  key.equals("end") || 
-                  key.equals("start")) {
-                // we can skip these, must all be the same across shards
-                continue; 
-              }
-              // can be null if inconsistencies in shards responses
-              Integer newValue = (Integer) shardFieldValues.get(key);
-              if  (null != newValue) {
-                Integer oldValue = ((Integer) existPair.getValue());
-                existPair.setValue(oldValue + newValue);
-              }
-            }
-          }
-        }
-      }
 
       // Distributed facet_ranges
-      //
-      // The implementation below uses the first encountered shard's 
-      // facet_ranges as the basis for subsequent shards' data to be merged.
-      @SuppressWarnings("unchecked")
-      SimpleOrderedMap<SimpleOrderedMap<Object>> facet_ranges = 
-        (SimpleOrderedMap<SimpleOrderedMap<Object>>) 
-        facet_counts.get("facet_ranges");
-      
-      if (facet_ranges != null) {
+      doDistribRanges(fi, facet_counts);
+
+
+      // Distributed facet_intervals
+      doDistribIntervals(fi, facet_counts);
 
-        // go through each facet_range
-        for (Map.Entry<String,SimpleOrderedMap<Object>> entry : facet_ranges) {
-          final String field = entry.getKey();
-          if (fi.rangeFacets.get(field) == null) { 
-            // first time we've seen this field, no merging
-            fi.rangeFacets.add(field, entry.getValue());
-
-          } else { 
-            // not the first time, merge current field counts
-
-            @SuppressWarnings("unchecked")
-            NamedList<Integer> shardFieldValues 
-              = (NamedList<Integer>) entry.getValue().get("counts");
-
-            @SuppressWarnings("unchecked")
-            NamedList<Integer> existFieldValues 
-              = (NamedList<Integer>) fi.rangeFacets.get(field).get("counts");
-
-            for (Map.Entry<String,Integer> existPair : existFieldValues) {
-              final String key = existPair.getKey();
-              // can be null if inconsistencies in shards responses
-              Integer newValue = shardFieldValues.get(key);
-              if  (null != newValue) {
-                Integer oldValue = existPair.getValue();
-                existPair.setValue(oldValue + newValue);
-              }
-            }
-          }
-        }
-      }
     }
 
     //
@@ -480,6 +405,145 @@ public class FacetComponent extends Sear
     }
   }
 
+  //
+  // The implementation below uses the first encountered shard's
+  // facet_intervals as the basis for subsequent shards' data to be merged.
+  private void doDistribIntervals(FacetInfo fi, NamedList facet_counts) {
+    @SuppressWarnings("unchecked")
+    SimpleOrderedMap<SimpleOrderedMap<Integer>> facet_intervals =
+        (SimpleOrderedMap<SimpleOrderedMap<Integer>>)
+            facet_counts.get("facet_intervals");
+
+    if (facet_intervals != null) {
+
+      for (Map.Entry<String, SimpleOrderedMap<Integer>> entry : facet_intervals) {
+        final String field = entry.getKey();
+        SimpleOrderedMap<Integer> existingCounts = fi.intervalFacets.get(field);
+        if (existingCounts == null) {
+          // first time we've seen this field, no merging
+          fi.intervalFacets.add(field, entry.getValue());
+
+        } else {
+          // not the first time, merge current field counts
+          Iterator<Map.Entry<String, Integer>> newItr = entry.getValue().iterator();
+          Iterator<Map.Entry<String, Integer>> exItr = existingCounts.iterator();
+
+          // all intervals should be returned by each shard, even if they have zero count,
+          // and in the same order
+          while (exItr.hasNext()) {
+            Map.Entry<String, Integer> exItem = exItr.next();
+            if (!newItr.hasNext()) {
+              throw new SolrException(ErrorCode.SERVER_ERROR,
+                  "Interval facet shard response missing key: " + exItem.getKey());
+            }
+            Map.Entry<String, Integer> newItem = newItr.next();
+            if (!newItem.getKey().equals(exItem.getKey())) {
+              throw new SolrException(ErrorCode.SERVER_ERROR,
+                  "Interval facet shard response has extra key: " + newItem.getKey());
+            }
+            exItem.setValue(exItem.getValue() + newItem.getValue());
+          }
+          if (newItr.hasNext()) {
+            throw new SolrException(ErrorCode.SERVER_ERROR,
+                "Interval facet shard response has at least one extra key: "
+                + newItr.next().getKey());
+          }
+        }
+      }
+    }
+  }
+
+  //
+  // The implementation below uses the first encountered shard's
+  // facet_ranges as the basis for subsequent shards' data to be merged.
+
+  private void doDistribRanges(FacetInfo fi, NamedList facet_counts) {
+    @SuppressWarnings("unchecked")
+    SimpleOrderedMap<SimpleOrderedMap<Object>> facet_ranges =
+      (SimpleOrderedMap<SimpleOrderedMap<Object>>)
+      facet_counts.get("facet_ranges");
+
+    if (facet_ranges != null) {
+
+      // go through each facet_range
+      for (Map.Entry<String,SimpleOrderedMap<Object>> entry : facet_ranges) {
+        final String field = entry.getKey();
+        if (fi.rangeFacets.get(field) == null) {
+          // first time we've seen this field, no merging
+          fi.rangeFacets.add(field, entry.getValue());
+
+        } else {
+          // not the first time, merge current field counts
+
+          @SuppressWarnings("unchecked")
+          NamedList<Integer> shardFieldValues
+            = (NamedList<Integer>) entry.getValue().get("counts");
+
+          @SuppressWarnings("unchecked")
+          NamedList<Integer> existFieldValues
+            = (NamedList<Integer>) fi.rangeFacets.get(field).get("counts");
+
+          for (Map.Entry<String,Integer> existPair : existFieldValues) {
+            final String key = existPair.getKey();
+            // can be null if inconsistencies in shards responses
+            Integer newValue = shardFieldValues.get(key);
+            if  (null != newValue) {
+              Integer oldValue = existPair.getValue();
+              existPair.setValue(oldValue + newValue);
+            }
+          }
+        }
+      }
+    }
+  }
+
+  //
+  // The implementation below uses the first encountered shard's
+  // facet_dates as the basis for subsequent shards' data to be merged.
+  // (the "NOW" param should ensure consistency)
+  private void doDistribDates(FacetInfo fi, NamedList facet_counts) {
+    @SuppressWarnings("unchecked")
+    SimpleOrderedMap<SimpleOrderedMap<Object>> facet_dates =
+      (SimpleOrderedMap<SimpleOrderedMap<Object>>)
+      facet_counts.get("facet_dates");
+
+    if (facet_dates != null) {
+
+      // go through each facet_date
+      for (Map.Entry<String,SimpleOrderedMap<Object>> entry : facet_dates) {
+        final String field = entry.getKey();
+        if (fi.dateFacets.get(field) == null) {
+          // first time we've seen this field, no merging
+          fi.dateFacets.add(field, entry.getValue());
+
+        } else {
+          // not the first time, merge current field
+
+          SimpleOrderedMap<Object> shardFieldValues
+            = entry.getValue();
+          SimpleOrderedMap<Object> existFieldValues
+            = fi.dateFacets.get(field);
+
+          for (Map.Entry<String,Object> existPair : existFieldValues) {
+            final String key = existPair.getKey();
+            if (key.equals("gap") ||
+                key.equals("end") ||
+                key.equals("start")) {
+              // we can skip these, must all be the same across shards
+              continue;
+            }
+            // can be null if inconsistencies in shards responses
+            Integer newValue = (Integer) shardFieldValues.get(key);
+            if  (null != newValue) {
+              Integer oldValue = ((Integer) existPair.getValue());
+              existPair.setValue(oldValue + newValue);
+            }
+          }
+        }
+      }
+    }
+  }
+
 
   private void refineFacets(ResponseBuilder rb, ShardRequest sreq) {
     FacetInfo fi = rb._facetInfo;
@@ -589,6 +653,7 @@ public class FacetComponent extends Sear
 
     facet_counts.add("facet_dates", fi.dateFacets);
     facet_counts.add("facet_ranges", fi.rangeFacets);
+    facet_counts.add("facet_intervals", fi.intervalFacets);
 
     rb.rsp.add("facet_counts", facet_counts);
 
@@ -637,6 +702,8 @@ public class FacetComponent extends Sear
       = new SimpleOrderedMap<>();
     public SimpleOrderedMap<SimpleOrderedMap<Object>> rangeFacets
       = new SimpleOrderedMap<>();
+    public SimpleOrderedMap<SimpleOrderedMap<Integer>> intervalFacets
+      = new SimpleOrderedMap<>();
 
     void parse(SolrParams params, ResponseBuilder rb) {
       queryFacets = new LinkedHashMap<>();

Modified: lucene/dev/branches/branch_4x/solr/core/src/java/org/apache/solr/request/SimpleFacets.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_4x/solr/core/src/java/org/apache/solr/request/SimpleFacets.java?rev=1612958&r1=1612957&r2=1612958&view=diff
==============================================================================
--- lucene/dev/branches/branch_4x/solr/core/src/java/org/apache/solr/request/SimpleFacets.java (original)
+++ lucene/dev/branches/branch_4x/solr/core/src/java/org/apache/solr/request/SimpleFacets.java Wed Jul 23 22:02:16 2014
@@ -72,6 +72,7 @@ import org.apache.solr.common.util.Named
 import org.apache.solr.common.util.SimpleOrderedMap;
 import org.apache.solr.common.util.StrUtils;
 import org.apache.solr.handler.component.ResponseBuilder;
+import org.apache.solr.request.IntervalFacets.FacetInterval;
 import org.apache.solr.schema.BoolField;
 import org.apache.solr.schema.DateField;
 import org.apache.solr.schema.FieldType;
@@ -245,6 +246,7 @@ public class SimpleFacets {
    * @see #getFacetFieldCounts
    * @see #getFacetDateCounts
    * @see #getFacetRangeCounts
+   * @see #getFacetIntervalCounts
    * @see FacetParams#FACET
    * @return a NamedList of Facet Count info or null
    */
@@ -260,6 +262,7 @@ public class SimpleFacets {
       facetResponse.add("facet_fields", getFacetFieldCounts());
       facetResponse.add("facet_dates", getFacetDateCounts());
       facetResponse.add("facet_ranges", getFacetRangeCounts());
+      facetResponse.add("facet_intervals", getFacetIntervalCounts());
 
     } catch (IOException e) {
       throw new SolrException(ErrorCode.SERVER_ERROR, e);
@@ -1557,6 +1560,41 @@ public class SimpleFacets {
       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
+   * is deterministic and equals in all shards of a distributed request, otherwise the collation of results
+   * will fail. 
+   * 
+   */
+  public NamedList<Object> getFacetIntervalCounts() throws IOException, SyntaxError {
+    NamedList<Object> res = new SimpleOrderedMap<Object>();
+    String[] fields = params.getParams(FacetParams.FACET_INTERVAL);
+    if (fields == null || fields.length == 0) return res;
+
+    for (String field : fields) {
+      parseParams(FacetParams.FACET_INTERVAL, field);
+      String[] intervalStrs = required.getFieldParams(field, FacetParams.FACET_INTERVAL_SET);
+      SchemaField schemaField = searcher.getCore().getLatestSchema().getField(field);
+      if (!schemaField.hasDocValues()) {
+        throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Interval Faceting only on fields with doc values");
+      }
+      if (params.getBool(GroupParams.GROUP_FACET, false)) {
+        throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Interval Faceting can't be used with " + GroupParams.GROUP_FACET);
+      }
+      
+      SimpleOrderedMap<Integer> fieldResults = new SimpleOrderedMap<Integer>();
+      res.add(field, fieldResults);
+      IntervalFacets intervalFacets = new IntervalFacets(schemaField, searcher, docs, intervalStrs);
+      for (FacetInterval interval : intervalFacets) {
+        fieldResults.add(interval.getKey(), interval.getCount());
+      }
+    }
+
+    return res;
+  }
+
 }
 

Modified: lucene/dev/branches/branch_4x/solr/core/src/test-files/solr/collection1/conf/schema-docValuesFaceting.xml
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_4x/solr/core/src/test-files/solr/collection1/conf/schema-docValuesFaceting.xml?rev=1612958&r1=1612957&r2=1612958&view=diff
==============================================================================
--- lucene/dev/branches/branch_4x/solr/core/src/test-files/solr/collection1/conf/schema-docValuesFaceting.xml (original)
+++ lucene/dev/branches/branch_4x/solr/core/src/test-files/solr/collection1/conf/schema-docValuesFaceting.xml Wed Jul 23 22:02:16 2014
@@ -20,6 +20,9 @@
   <types>
     <fieldType name="int" class="solr.TrieIntField" precisionStep="0" omitNorms="true" positionIncrementGap="0"/>
     <fieldType name="float" class="solr.TrieFloatField" precisionStep="0" omitNorms="true" positionIncrementGap="0"/>
+    <fieldType name="long" class="solr.TrieLongField" precisionStep="0" omitNorms="true" positionIncrementGap="0"/>
+    <fieldType name="double" class="solr.TrieDoubleField" precisionStep="0" omitNorms="true" positionIncrementGap="0"/>
+    <fieldType name="date" class="solr.TrieDateField" precisionStep="0" omitNorms="true" positionIncrementGap="0"/>
     <fieldtype name="string" class="solr.StrField" sortMissingLast="true"/>
   </types>
 
@@ -35,7 +38,17 @@
     <dynamicField name="*_ss"    type="string" indexed="true"  stored="false" docValues="false" multiValued="true"/>
     <dynamicField name="*_ss_dv" type="string" indexed="false" stored="false" docValues="true"  multiValued="true"/>
     <dynamicField name="*_f"     type="float"  indexed="true"  stored="false" docValues="false"/>
-    <dynamicField name="*_f_dv"  type="float"  indexed="false" stored="false" docValues="true"/>
+    <dynamicField name="*_f_dv"  type="float"  indexed="true"  stored="false" docValues="true"/>
+    <dynamicField name="*_fs_dv" type="float"  indexed="true"  stored="false" docValues="true"  multiValued="true"/>
+    <dynamicField name="*_l"     type="long"   indexed="true"  stored="false" docValues="false"/>
+    <dynamicField name="*_l_dv"  type="long"   indexed="true"  stored="false" docValues="true"/>
+    <dynamicField name="*_ls_dv" type="long"   indexed="true"  stored="false" docValues="true"  multiValued="true"/>
+    <dynamicField name="*_d"     type="double" indexed="true"  stored="false" docValues="false"/>
+    <dynamicField name="*_d_dv"  type="double" indexed="true"  stored="false" docValues="true"/>
+    <dynamicField name="*_ds_dv" type="double" indexed="true"  stored="false" docValues="true"  multiValued="true"/>
+    <dynamicField name="*_dt"    type="date"   indexed="true"  stored="false" docValues="false"/>
+    <dynamicField name="*_dt_dv" type="date"   indexed="true"  stored="false" docValues="true"/>
+    <dynamicField name="*_dts_dv" type="date"  indexed="true"  stored="false" docValues="true"  multiValued="true"/>
   </fields>
 
   <defaultSearchField>id</defaultSearchField>

Copied: lucene/dev/branches/branch_4x/solr/core/src/test/org/apache/solr/DistributedIntervalFacetingTest.java (from r1612889, lucene/dev/trunk/solr/core/src/test/org/apache/solr/DistributedIntervalFacetingTest.java)
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_4x/solr/core/src/test/org/apache/solr/DistributedIntervalFacetingTest.java?p2=lucene/dev/branches/branch_4x/solr/core/src/test/org/apache/solr/DistributedIntervalFacetingTest.java&p1=lucene/dev/trunk/solr/core/src/test/org/apache/solr/DistributedIntervalFacetingTest.java&r1=1612889&r2=1612958&rev=1612958&view=diff
==============================================================================
--- lucene/dev/trunk/solr/core/src/test/org/apache/solr/DistributedIntervalFacetingTest.java (original)
+++ lucene/dev/branches/branch_4x/solr/core/src/test/org/apache/solr/DistributedIntervalFacetingTest.java Wed Jul 23 22:02:16 2014
@@ -25,7 +25,7 @@ import org.junit.BeforeClass;
  * limitations under the License.
  */
 @Slow
-@LuceneTestCase.SuppressCodecs({"Lucene40", "Lucene41", "Lucene42", "Lucene43"})
+@LuceneTestCase.SuppressCodecs({"Lucene3x", "Lucene40", "Lucene41", "Lucene42", "Lucene43"})
 public class DistributedIntervalFacetingTest extends
     BaseDistributedSearchTestCase {
 

Modified: lucene/dev/branches/branch_4x/solr/core/src/test/org/apache/solr/TestGroupingSearch.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_4x/solr/core/src/test/org/apache/solr/TestGroupingSearch.java?rev=1612958&r1=1612957&r2=1612958&view=diff
==============================================================================
--- lucene/dev/branches/branch_4x/solr/core/src/test/org/apache/solr/TestGroupingSearch.java (original)
+++ lucene/dev/branches/branch_4x/solr/core/src/test/org/apache/solr/TestGroupingSearch.java Wed Jul 23 22:02:16 2014
@@ -318,7 +318,7 @@ public class TestGroupingSearch extends 
     assertJQ(
         req,
         "/grouped=={'value1_s1':{'matches':5,'groups':[{'groupValue':'1','doclist':{'numFound':3,'start':0,'docs':[{'id':'1'}]}}]}}",
-        "/facet_counts=={'facet_queries':{},'facet_fields':{'value3_s1':['a',3,'b',2]},'facet_dates':{},'facet_ranges':{}}"
+        "/facet_counts=={'facet_queries':{},'facet_fields':{'value3_s1':['a',3,'b',2]},'facet_dates':{},'facet_ranges':{},'facet_intervals':{}}"
     );
 
     // Facet counts based on groups
@@ -327,7 +327,7 @@ public class TestGroupingSearch extends 
     assertJQ(
         req,
         "/grouped=={'value1_s1':{'matches':5,'groups':[{'groupValue':'1','doclist':{'numFound':3,'start':0,'docs':[{'id':'1'}]}}]}}",
-        "/facet_counts=={'facet_queries':{},'facet_fields':{'value3_s1':['a',1,'b',1]},'facet_dates':{},'facet_ranges':{}}"
+        "/facet_counts=={'facet_queries':{},'facet_fields':{'value3_s1':['a',1,'b',1]},'facet_dates':{},'facet_ranges':{},'facet_intervals':{}}"
     );
 
     // Facet counts based on groups and with group.func. This should trigger FunctionAllGroupHeadsCollector
@@ -336,7 +336,7 @@ public class TestGroupingSearch extends 
     assertJQ(
         req,
         "/grouped=={'strdist(1,value1_s1,edit)':{'matches':5,'groups':[{'groupValue':1.0,'doclist':{'numFound':3,'start':0,'docs':[{'id':'1'}]}}]}}",
-        "/facet_counts=={'facet_queries':{},'facet_fields':{'value3_s1':['a',1,'b',1]},'facet_dates':{},'facet_ranges':{}}"
+        "/facet_counts=={'facet_queries':{},'facet_fields':{'value3_s1':['a',1,'b',1]},'facet_dates':{},'facet_ranges':{},'facet_intervals':{}}"
     );
 
     // Facet counts based on groups without sort on an int field.
@@ -345,7 +345,7 @@ public class TestGroupingSearch extends 
     assertJQ(
         req,
         "/grouped=={'value4_i':{'matches':5,'groups':[{'groupValue':1,'doclist':{'numFound':3,'start':0,'docs':[{'id':'1'}]}}]}}",
-        "/facet_counts=={'facet_queries':{},'facet_fields':{'value3_s1':['a',1,'b',1]},'facet_dates':{},'facet_ranges':{}}"
+        "/facet_counts=={'facet_queries':{},'facet_fields':{'value3_s1':['a',1,'b',1]},'facet_dates':{},'facet_ranges':{},'facet_intervals':{}}"
     );
 
     // Multi select facets AND group.truncate=true
@@ -354,7 +354,7 @@ public class TestGroupingSearch extends 
     assertJQ(
         req,
         "/grouped=={'value4_i':{'matches':2,'groups':[{'groupValue':2,'doclist':{'numFound':2,'start':0,'docs':[{'id':'3'}]}}]}}",
-        "/facet_counts=={'facet_queries':{},'facet_fields':{'value3_s1':['a',1,'b',1]},'facet_dates':{},'facet_ranges':{}}"
+        "/facet_counts=={'facet_queries':{},'facet_fields':{'value3_s1':['a',1,'b',1]},'facet_dates':{},'facet_ranges':{},'facet_intervals':{}}"
     );
 
     // Multi select facets AND group.truncate=false
@@ -363,7 +363,7 @@ public class TestGroupingSearch extends 
     assertJQ(
         req,
         "/grouped=={'value4_i':{'matches':2,'groups':[{'groupValue':2,'doclist':{'numFound':2,'start':0,'docs':[{'id':'3'}]}}]}}",
-        "/facet_counts=={'facet_queries':{},'facet_fields':{'value3_s1':['a',3,'b',2]},'facet_dates':{},'facet_ranges':{}}"
+        "/facet_counts=={'facet_queries':{},'facet_fields':{'value3_s1':['a',3,'b',2]},'facet_dates':{},'facet_ranges':{},'facet_intervals':{}}"
     );
 
     // Multi select facets AND group.truncate=true
@@ -372,7 +372,7 @@ public class TestGroupingSearch extends 
     assertJQ(
         req,
         "/grouped=={'sub(value4_i,1)':{'matches':2,'groups':[{'groupValue':1.0,'doclist':{'numFound':2,'start':0,'docs':[{'id':'3'}]}}]}}",
-        "/facet_counts=={'facet_queries':{},'facet_fields':{'value3_s1':['a',1,'b',1]},'facet_dates':{},'facet_ranges':{}}"
+        "/facet_counts=={'facet_queries':{},'facet_fields':{'value3_s1':['a',1,'b',1]},'facet_dates':{},'facet_ranges':{},'facet_intervals':{}}"
     );
   }
 
@@ -395,7 +395,7 @@ public class TestGroupingSearch extends 
     assertJQ(
         req,
         "/grouped=={'cat_sI':{'matches':2,'groups':[{'groupValue':'a','doclist':{'numFound':1,'start':0,'docs':[{'id':'5'}]}}]}}",
-        "/facet_counts=={'facet_queries':{'LW1':2,'LM1':2,'LM3':2},'facet_fields':{},'facet_dates':{},'facet_ranges':{}}"
+        "/facet_counts=={'facet_queries':{'LW1':2,'LM1':2,'LM3':2},'facet_fields':{},'facet_dates':{},'facet_ranges':{},'facet_intervals':{}}"
     );
   }
 

Copied: lucene/dev/branches/branch_4x/solr/core/src/test/org/apache/solr/request/TestIntervalFaceting.java (from r1612889, lucene/dev/trunk/solr/core/src/test/org/apache/solr/request/TestIntervalFaceting.java)
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_4x/solr/core/src/test/org/apache/solr/request/TestIntervalFaceting.java?p2=lucene/dev/branches/branch_4x/solr/core/src/test/org/apache/solr/request/TestIntervalFaceting.java&p1=lucene/dev/trunk/solr/core/src/test/org/apache/solr/request/TestIntervalFaceting.java&r1=1612889&r2=1612958&rev=1612958&view=diff
==============================================================================
--- lucene/dev/trunk/solr/core/src/test/org/apache/solr/request/TestIntervalFaceting.java (original)
+++ lucene/dev/branches/branch_4x/solr/core/src/test/org/apache/solr/request/TestIntervalFaceting.java Wed Jul 23 22:02:16 2014
@@ -36,7 +36,7 @@ import org.apache.solr.util.RefCounted;
 import org.junit.BeforeClass;
 import org.junit.Test;
 
-@LuceneTestCase.SuppressCodecs({"Lucene40", "Lucene41", "Lucene42", "Lucene43"})
+@LuceneTestCase.SuppressCodecs({"Lucene3x", "Lucene40", "Lucene41", "Lucene42", "Lucene43"})
 public class TestIntervalFaceting extends SolrTestCaseJ4 {
 
   @BeforeClass

Modified: lucene/dev/branches/branch_4x/solr/solrj/src/java/org/apache/solr/common/params/FacetParams.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_4x/solr/solrj/src/java/org/apache/solr/common/params/FacetParams.java?rev=1612958&r1=1612957&r2=1612958&view=diff
==============================================================================
--- lucene/dev/branches/branch_4x/solr/solrj/src/java/org/apache/solr/common/params/FacetParams.java (original)
+++ lucene/dev/branches/branch_4x/solr/solrj/src/java/org/apache/solr/common/params/FacetParams.java Wed Jul 23 22:02:16 2014
@@ -254,6 +254,15 @@ public interface FacetParams {
    */
   public static final String FACET_RANGE_INCLUDE = FACET_RANGE + ".include";
 
+  /**
+   * Any field whose values the user wants to enumerate as explicit intervals of terms.
+   */
+  public static final String FACET_INTERVAL = FACET + ".interval";
+
+  /**
+   * Set of terms for a single interval to facet on.
+   */
+  public static final String FACET_INTERVAL_SET = FACET_INTERVAL + ".set";
 
   /**
    * An enumeration of the legal values for {@link #FACET_RANGE_OTHER} and {@link #FACET_DATE_OTHER} ...

Modified: lucene/dev/branches/branch_4x/solr/test-framework/src/java/org/apache/solr/SolrTestCaseJ4.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_4x/solr/test-framework/src/java/org/apache/solr/SolrTestCaseJ4.java?rev=1612958&r1=1612957&r2=1612958&view=diff
==============================================================================
--- lucene/dev/branches/branch_4x/solr/test-framework/src/java/org/apache/solr/SolrTestCaseJ4.java (original)
+++ lucene/dev/branches/branch_4x/solr/test-framework/src/java/org/apache/solr/SolrTestCaseJ4.java Wed Jul 23 22:02:16 2014
@@ -839,6 +839,28 @@ public abstract class SolrTestCaseJ4 ext
       unIgnoreException(".");
     }
   }
+  /**
+   * Makes sure a query throws a SolrException with the listed response code and expected message
+   * @param failMessage The assert message to show when the query doesn't throw the expected exception
+   * @param exceptionMessage A substring of the message expected in the exception
+   * @param req Solr request
+   * @param code expected error code for the query
+   */
+  public static void assertQEx(String failMessage, String exceptionMessage, SolrQueryRequest req, SolrException.ErrorCode code ) {
+    try {
+      ignoreException(".");
+      h.query(req);
+      fail( failMessage );
+    } catch (SolrException e) {
+      assertEquals( code.code, e.code() );
+      assertTrue("Unexpected error message. Expecting \"" + exceptionMessage + 
+          "\" but got \"" + e.getMessage() + "\"", e.getMessage()!= null && e.getMessage().contains(exceptionMessage));
+    } catch (Exception e2) {
+      throw new RuntimeException("Exception during query", e2);
+    } finally {
+      unIgnoreException(".");
+    }
+  }
 
 
   /**