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

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

Modified: lucene/dev/branches/branch_5x/solr/core/src/test/org/apache/solr/handler/component/DistributedFacetPivotSmallTest.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_5x/solr/core/src/test/org/apache/solr/handler/component/DistributedFacetPivotSmallTest.java?rev=1689839&r1=1689838&r2=1689839&view=diff
==============================================================================
--- lucene/dev/branches/branch_5x/solr/core/src/test/org/apache/solr/handler/component/DistributedFacetPivotSmallTest.java (original)
+++ lucene/dev/branches/branch_5x/solr/core/src/test/org/apache/solr/handler/component/DistributedFacetPivotSmallTest.java Wed Jul  8 11:05:27 2015
@@ -21,17 +21,19 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.Date;
+import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Map;
 
+import junit.framework.AssertionFailedError;
 import org.apache.solr.BaseDistributedSearchTestCase;
 import org.apache.solr.client.solrj.response.FieldStatsInfo;
 import org.apache.solr.client.solrj.response.PivotField;
 import org.apache.solr.client.solrj.response.QueryResponse;
+import org.apache.solr.client.solrj.response.RangeFacet;
 import org.apache.solr.common.params.FacetParams;
-import org.apache.solr.common.params.SolrParams;
 import org.apache.solr.common.params.ModifiableSolrParams;
-
-import junit.framework.AssertionFailedError;
+import org.apache.solr.common.params.SolrParams;
 import org.junit.Test;
 
 public class DistributedFacetPivotSmallTest extends BaseDistributedSearchTestCase {
@@ -66,7 +68,7 @@ public class DistributedFacetPivotSmallT
     handle.clear();
     handle.put("QTime", SKIPVAL);
     handle.put("timestamp", SKIPVAL);
-    handle.put("maxScore", SKIPVAL);    
+    handle.put("maxScore", SKIPVAL);
     
     
     final ModifiableSolrParams params = new ModifiableSolrParams();
@@ -338,6 +340,16 @@ public class DistributedFacetPivotSmallT
     doTestDeepPivotStats(true); // just the mean price stat
 
     doTestPivotStatsFromOneShard();
+
+    testFacetPivotRange();
+
+    testFacetPivotQuery();
+
+    testNegativeFacetQuery();
+
+    testNegativeRangeQuery();
+
+    testPivotFacetRangeAndQuery();
   }
 
   /**
@@ -439,6 +451,986 @@ public class DistributedFacetPivotSmallT
     }
   }
 
+  private void testFacetPivotRange() throws Exception {
+    final ModifiableSolrParams params = new ModifiableSolrParams();
+    setDistributedParams(params);
+    params.add("q", "*:*");
+    params.add("facet", "true");
+    params.add("facet.pivot", "{!range=s1}place_t,company_t");
+    params.add("facet.range", "{!tag=s1 key=price}price_ti");
+    params.add("facet.range.start", "0");
+    params.add("facet.range.end", "100");
+    params.add("facet.range.gap", "20");
+
+    QueryResponse rsp = queryServer(params);
+
+    List<PivotField> expectedPlacePivots = new UnorderedEqualityArrayList<PivotField>();
+    List<PivotField> expectedCardiffPivots = new UnorderedEqualityArrayList<PivotField>();
+    expectedCardiffPivots.add(new ComparablePivotField("company_t",
+        "microsoft", 2, null, null, createExpectedRange("price", 0, 100,
+        20, 1, 0, 0, 0, 0)));
+    expectedCardiffPivots.add(new ComparablePivotField("company_t", "null", 2,
+        null, null, createExpectedRange("price", 0, 100, 20, 0, 1, 0, 0,
+        0)));
+    expectedCardiffPivots.add(new ComparablePivotField("company_t", "bbc", 2,
+        null, null, createExpectedRange("price", 0, 100, 20, 0, 1, 0, 0,
+        0)));
+    expectedCardiffPivots.add(new ComparablePivotField("company_t", "polecat",
+        3, null, null, createExpectedRange("price", 0, 100, 20, 1, 1, 0,
+        0, 0)));
+    expectedCardiffPivots.add(new ComparablePivotField("company_t", "fujitsu",
+        1, null, null, createExpectedRange("price", 0, 100, 20, 0, 0, 0,
+        0, 0)));
+    List<PivotField> expectedDublinPivots = new UnorderedEqualityArrayList<PivotField>();
+    expectedDublinPivots.add(new ComparablePivotField("company_t", "polecat",
+        4, null, null, createExpectedRange("price", 0, 100, 20, 2, 1, 0,
+        0, 0)));
+    expectedDublinPivots.add(new ComparablePivotField("company_t", "microsoft",
+        4, null, null, createExpectedRange("price", 0, 100, 20, 2, 1, 0,
+        0, 0)));
+    expectedDublinPivots.add(new ComparablePivotField("company_t", "null", 3,
+        null, null, createExpectedRange("price", 0, 100, 20, 1, 1, 0, 0,
+        0)));
+    expectedDublinPivots.add(new ComparablePivotField("company_t", "fujitsu",
+        2, null, null, createExpectedRange("price", 0, 100, 20, 0, 1, 0,
+        0, 0)));
+    expectedDublinPivots.add(new ComparablePivotField("company_t", "bbc", 1,
+        null, null, createExpectedRange("price", 0, 100, 20, 0, 0, 0, 0,
+        0)));
+    List<PivotField> expectedLondonPivots = new UnorderedEqualityArrayList<PivotField>();
+    expectedLondonPivots.add(new ComparablePivotField("company_t", "polecat",
+        3, null, null, createExpectedRange("price", 0, 100, 20, 0, 2, 0,
+        0, 0)));
+    expectedLondonPivots.add(new ComparablePivotField("company_t", "microsoft",
+        2, null, null, createExpectedRange("price", 0, 100, 20, 0, 1, 0,
+        0, 0)));
+    expectedLondonPivots.add(new ComparablePivotField("company_t", "fujitsu",
+        2, null, null, createExpectedRange("price", 0, 100, 20, 0, 1, 0,
+        0, 0)));
+    expectedLondonPivots.add(new ComparablePivotField("company_t", "null", 3,
+        null, null, createExpectedRange("price", 0, 100, 20, 0, 2, 0, 0,
+        0)));
+    expectedLondonPivots.add(new ComparablePivotField("company_t", "bbc", 2,
+        null, null, createExpectedRange("price", 0, 100, 20, 0, 1, 0, 0,
+        0)));
+    List<PivotField> expectedLAPivots = new UnorderedEqualityArrayList<PivotField>();
+    expectedLAPivots.add(new ComparablePivotField("company_t", "microsoft", 2,
+        null, null, createExpectedRange("price", 0, 100, 20, 0, 1, 0, 0,
+        0)));
+    expectedLAPivots.add(new ComparablePivotField("company_t", "fujitsu", 2,
+        null, null, createExpectedRange("price", 0, 100, 20, 0, 1, 0, 0,
+        0)));
+    expectedLAPivots.add(new ComparablePivotField("company_t", "null", 2, null,
+        null, createExpectedRange("price", 0, 100, 20, 0, 1, 0, 0, 0)));
+    expectedLAPivots.add(new ComparablePivotField("company_t", "bbc", 1, null,
+        null, createExpectedRange("price", 0, 100, 20, 0, 0, 0, 0, 0)));
+    expectedLAPivots.add(new ComparablePivotField("company_t", "polecat", 2,
+        null, null, createExpectedRange("price", 0, 100, 20, 0, 1, 0, 0,
+        0)));
+    List<PivotField> expectedKrakowPivots = new UnorderedEqualityArrayList<PivotField>();
+    expectedKrakowPivots.add(new ComparablePivotField("company_t", "polecat",
+        2, null, null, createExpectedRange("price", 0, 100, 20, 0, 1, 0,
+        0, 0)));
+    expectedKrakowPivots.add(new ComparablePivotField("company_t", "bbc", 2,
+        null, null, createExpectedRange("price", 0, 100, 20, 0, 1, 0, 0,
+        0)));
+    expectedKrakowPivots.add(new ComparablePivotField("company_t", "null", 3,
+        null, null, createExpectedRange("price", 0, 100, 20, 0, 1, 0, 0,
+        0)));
+    expectedKrakowPivots.add(new ComparablePivotField("company_t", "fujitsu",
+        1, null, null, createExpectedRange("price", 0, 100, 20, 0, 0, 0,
+        0, 0)));
+    expectedKrakowPivots.add(new ComparablePivotField("company_t", "microsoft",
+        1, null, null, createExpectedRange("price", 0, 100, 20, 0, 0, 0,
+        0, 0)));
+    List<PivotField> expectedCorkPivots = new UnorderedEqualityArrayList<PivotField>();
+    expectedCorkPivots.add(new ComparablePivotField("company_t", "fujitsu", 1,
+        null, null, createExpectedRange("price", 0, 100, 20, 0, 0, 0, 0,
+        0)));
+    expectedCorkPivots.add(new ComparablePivotField("company_t", "rte", 1,
+        null, null, createExpectedRange("price", 0, 100, 20, 0, 0, 0, 0,
+        0)));
+
+    expectedPlacePivots.add(new ComparablePivotField("place_t", "dublin", 4,
+        expectedDublinPivots, null, createExpectedRange("price", 0, 100,
+        20, 2, 1, 0, 0, 0)));
+    expectedPlacePivots.add(new ComparablePivotField("place_t", "cardiff", 3,
+        expectedCardiffPivots, null, createExpectedRange("price", 0, 100,
+        20, 1, 1, 0, 0, 0)));
+    expectedPlacePivots.add(new ComparablePivotField("place_t", "london", 4,
+        expectedLondonPivots, null, createExpectedRange("price", 0, 100,
+        20, 0, 3, 0, 0, 0)));
+    expectedPlacePivots.add(new ComparablePivotField("place_t", "la", 3,
+        expectedLAPivots, null, createExpectedRange("price", 0, 100, 20,
+        0, 1, 0, 0, 0)));
+    expectedPlacePivots.add(new ComparablePivotField("place_t", "krakow", 3,
+        expectedKrakowPivots, null, createExpectedRange("price", 0, 100,
+        20, 0, 1, 0, 0, 0)));
+    expectedPlacePivots.add(new ComparablePivotField("place_t", "cork", 1,
+        expectedCorkPivots, null, createExpectedRange("price", 0, 100,
+        20, 0, 0, 0, 0, 0)));
+
+    List<PivotField> placePivots = rsp.getFacetPivot().get("place_t,company_t");
+
+    // Useful to check for errors, orders lists and does toString() equality
+    // check
+    testOrderedPivotsStringEquality(expectedPlacePivots, placePivots);
+
+    assertEquals(expectedPlacePivots, placePivots);
+
+    // Test sorting by count
+
+    params.set(FacetParams.FACET_SORT, FacetParams.FACET_SORT_COUNT);
+
+    rsp = queryServer(params);
+
+    placePivots = rsp.getFacetPivot().get("place_t,company_t");
+
+    testCountSorting(placePivots);
+
+    // Test limit
+
+    params.set(FacetParams.FACET_LIMIT, 2);
+
+    rsp = queryServer(params);
+
+    expectedPlacePivots = new UnorderedEqualityArrayList<PivotField>();
+    expectedDublinPivots = new UnorderedEqualityArrayList<PivotField>();
+    expectedDublinPivots.add(new ComparablePivotField("company_t", "polecat",
+        4, null, null, createExpectedRange("price", 0, 100, 20, 2, 1, 0,
+        0, 0)));
+    expectedDublinPivots.add(new ComparablePivotField("company_t", "microsoft",
+        4, null, null, createExpectedRange("price", 0, 100, 20, 2, 1, 0,
+        0, 0)));
+    expectedLondonPivots = new UnorderedEqualityArrayList<PivotField>();
+    expectedLondonPivots.add(new ComparablePivotField("company_t", "null", 3,
+        null, null, createExpectedRange("price", 0, 100, 20, 0, 2, 0, 0,
+        0)));
+    expectedLondonPivots.add(new ComparablePivotField("company_t", "polecat",
+        3, null, null, createExpectedRange("price", 0, 100, 20, 0, 2, 0,
+        0, 0)));
+    expectedPlacePivots.add(new ComparablePivotField("place_t", "dublin", 4,
+        expectedDublinPivots, null, createExpectedRange("price", 0, 100,
+        20, 2, 1, 0, 0, 0)));
+    expectedPlacePivots.add(new ComparablePivotField("place_t", "london", 4,
+        expectedLondonPivots, null, createExpectedRange("price", 0, 100,
+        20, 0, 3, 0, 0, 0)));
+
+    placePivots = rsp.getFacetPivot().get("place_t,company_t");
+
+    assertEquals(expectedPlacePivots, placePivots);
+
+    // Test individual facet.limit values
+    params.remove(FacetParams.FACET_LIMIT);
+
+    params.set("f.place_t." + FacetParams.FACET_LIMIT, 1);
+    params.set("f.company_t." + FacetParams.FACET_LIMIT, 4);
+
+    rsp = queryServer(params);
+
+    expectedPlacePivots = new UnorderedEqualityArrayList<PivotField>();
+
+    expectedDublinPivots = new UnorderedEqualityArrayList<PivotField>();
+    expectedDublinPivots.add(new ComparablePivotField("company_t", "microsoft",
+        4, null, null, createExpectedRange("price", 0, 100, 20, 2, 1, 0,
+        0, 0)));
+    expectedDublinPivots.add(new ComparablePivotField("company_t", "polecat",
+        4, null, null, createExpectedRange("price", 0, 100, 20, 2, 1, 0,
+        0, 0)));
+    expectedDublinPivots.add(new ComparablePivotField("company_t", "null", 3,
+        null, null, createExpectedRange("price", 0, 100, 20, 1, 1, 0, 0,
+        0)));
+    expectedDublinPivots.add(new ComparablePivotField("company_t", "fujitsu",
+        2, null, null, createExpectedRange("price", 0, 100, 20, 0, 1, 0,
+        0, 0)));
+
+    expectedLondonPivots = new UnorderedEqualityArrayList<PivotField>();
+    expectedLondonPivots.add(new ComparablePivotField("company_t", "null", 3,
+        null, null, createExpectedRange("price", 0, 100, 20, 0, 2, 0, 0,
+        0)));
+    expectedLondonPivots.add(new ComparablePivotField("company_t", "polecat",
+        3, null, null, createExpectedRange("price", 0, 100, 20, 0, 2, 0,
+        0, 0)));
+    expectedLondonPivots.add(new ComparablePivotField("company_t", "bbc", 2,
+        null, null, createExpectedRange("price", 0, 100, 20, 0, 1, 0, 0,
+        0)));
+    expectedLondonPivots.add(new ComparablePivotField("company_t", "fujitsu",
+        2, null, null, createExpectedRange("price", 0, 100, 20, 0, 1, 0,
+        0, 0)));
+
+    expectedCardiffPivots = new UnorderedEqualityArrayList<PivotField>();
+    expectedCardiffPivots.add(new ComparablePivotField("company_t", "polecat",
+        3, null, null, createExpectedRange("price", 0, 100, 20, 1, 1, 0,
+        0, 0)));
+
+    expectedKrakowPivots = new UnorderedEqualityArrayList<PivotField>();
+    expectedKrakowPivots.add(new ComparablePivotField("company_t", "null", 3,
+        null, null, createExpectedRange("price", 0, 100, 20, 0, 1, 0, 0,
+        0)));
+
+    expectedLAPivots = new UnorderedEqualityArrayList<PivotField>();
+    expectedLAPivots.add(new ComparablePivotField("company_t", "fujitsu", 2,
+        null, null, createExpectedRange("price", 0, 100, 20, 0, 1, 0, 0,
+        0)));
+
+    expectedCorkPivots = new UnorderedEqualityArrayList<PivotField>();
+    expectedCorkPivots.add(new ComparablePivotField("company_t", "fujitsu", 1,
+        null, null, createExpectedRange("price", 0, 100, 20, 0, 0, 0, 0,
+        0)));
+
+    expectedPlacePivots.add(new ComparablePivotField("place_t", "dublin", 4,
+        expectedDublinPivots, null, createExpectedRange("price", 0, 100,
+        20, 2, 1, 0, 0, 0)));
+
+    placePivots = rsp.getFacetPivot().get("place_t,company_t");
+    assertEquals(expectedPlacePivots, placePivots);
+
+    params.remove("f.company_t." + FacetParams.FACET_LIMIT);
+    params.remove("f.place_t." + FacetParams.FACET_LIMIT);
+    params.set(FacetParams.FACET_LIMIT, 2);
+
+    // Test facet.missing=true with diff sorts
+
+    index("id", 777); // NOTE: id=25 has no place as well
+    commit();
+
+    SolrParams missingA = params("q", "*:*", "rows", "0", "facet", "true",
+        "facet.pivot", "place_t,company_t",
+        // default facet.sort
+        FacetParams.FACET_MISSING, "true");
+    SolrParams missingB = SolrParams.wrapDefaults(missingA,
+        params(FacetParams.FACET_LIMIT, "4", "facet.sort", "index"));
+    for (SolrParams p : new SolrParams[]{missingA, missingB}) {
+      // in either case, the last pivot option should be the same
+      rsp = query(p);
+      placePivots = rsp.getFacetPivot().get("place_t,company_t");
+      assertTrue("not enough values for pivot: " + p + " => " + placePivots,
+          1 < placePivots.size());
+      PivotField missing = placePivots.get(placePivots.size() - 1);
+      assertNull("not the missing place value: " + p, missing.getValue());
+      assertEquals("wrong missing place count: " + p, 2, missing.getCount());
+      assertTrue("not enough sub-pivots for missing place: " + p + " => "
+          + missing.getPivot(), 1 < missing.getPivot().size());
+      missing = missing.getPivot().get(missing.getPivot().size() - 1);
+      assertNull("not the missing company value: " + p, missing.getValue());
+      assertEquals("wrong missing company count: " + p, 1, missing.getCount());
+      assertNull("company shouldn't have sub-pivots: " + p, missing.getPivot());
+    }
+
+    // sort=index + mincount + limit
+    for (SolrParams variableParams : new SolrParams[]{
+        // we should get the same results regardless of overrequest
+        params("facet.overrequest.count", "0", "facet.overrequest.ratio", "0"),
+        params()}) {
+
+      SolrParams p = SolrParams.wrapDefaults(
+          params("q", "*:*", "rows", "0", "facet", "true", "facet.pivot",
+              "company_t", "facet.sort", "index", "facet.pivot.mincount", "4",
+              "facet.limit", "4"), variableParams);
+
+      try {
+        List<PivotField> pivots = query(p).getFacetPivot().get("company_t");
+        assertEquals(4, pivots.size());
+        assertEquals("fujitsu", pivots.get(0).getValue());
+        assertEquals(4, pivots.get(0).getCount());
+        assertEquals("microsoft", pivots.get(1).getValue());
+        assertEquals(5, pivots.get(1).getCount());
+        assertEquals("null", pivots.get(2).getValue());
+        assertEquals(6, pivots.get(2).getCount());
+        assertEquals("polecat", pivots.get(3).getValue());
+        assertEquals(6, pivots.get(3).getCount());
+
+      } catch (AssertionFailedError ae) {
+        throw new AssertionError(ae.getMessage() + " <== " + p.toString(), ae);
+      }
+    }
+
+    // sort=index + mincount + limit + offset
+    for (SolrParams variableParams : new SolrParams[]{
+        // we should get the same results regardless of overrequest
+        params("facet.overrequest.count", "0", "facet.overrequest.ratio", "0"),
+        params()}) {
+
+      SolrParams p = SolrParams.wrapDefaults(
+          params("q", "*:*", "rows", "0", "facet", "true", "facet.pivot",
+              "company_t", "facet.sort", "index", "facet.pivot.mincount", "4",
+              "facet.offset", "1", "facet.limit", "4"), variableParams);
+      try {
+        List<PivotField> pivots = query(p).getFacetPivot().get("company_t");
+        assertEquals(3, pivots.size()); // asked for 4, but not enough meet the
+        // mincount
+        assertEquals("microsoft", pivots.get(0).getValue());
+        assertEquals(5, pivots.get(0).getCount());
+        assertEquals("null", pivots.get(1).getValue());
+        assertEquals(6, pivots.get(1).getCount());
+        assertEquals("polecat", pivots.get(2).getValue());
+        assertEquals(6, pivots.get(2).getCount());
+
+      } catch (AssertionFailedError ae) {
+        throw new AssertionError(ae.getMessage() + " <== " + p.toString(), ae);
+      }
+
+    }
+
+    // sort=index + mincount + limit + offset (more permutations)
+    for (SolrParams variableParams : new SolrParams[]{
+        // all of these combinations should result in the same first value
+        params("facet.pivot.mincount", "4", "facet.offset", "2"),
+        params("facet.pivot.mincount", "5", "facet.offset", "1"),
+        params("facet.pivot.mincount", "6", "facet.offset", "0")}) {
+
+      SolrParams p = SolrParams.wrapDefaults(
+          params("q", "*:*", "rows", "0", "facet", "true", "facet.limit", "1",
+              "facet.sort", "index", "facet.overrequest.ratio", "0",
+              "facet.pivot", "company_t"), variableParams);
+
+      try {
+        List<PivotField> pivots = query(p).getFacetPivot().get("company_t");
+        assertEquals(1, pivots.size());
+        assertEquals(pivots.toString(), "null", pivots.get(0).getValue());
+        assertEquals(pivots.toString(), 6, pivots.get(0).getCount());
+
+      } catch (AssertionFailedError ae) {
+        throw new AssertionError(ae.getMessage() + " <== " + p.toString(), ae);
+      }
+    }
+  }
+
+  private void testFacetPivotQuery() throws Exception {
+    final ModifiableSolrParams params = new ModifiableSolrParams();
+    setDistributedParams(params);
+    params.add("q", "*:*");
+    params.add("facet", "true");
+    params.add("facet.pivot", "{!query=s1}place_t,company_t");
+    params.add("facet.query", "{!tag=s1 key=highPrice}price_ti:[25 TO 100]");
+
+    QueryResponse rsp = queryServer(params);
+
+    List<PivotField> expectedPlacePivots = new UnorderedEqualityArrayList<PivotField>();
+    List<PivotField> expectedCardiffPivots = new UnorderedEqualityArrayList<PivotField>();
+    expectedCardiffPivots.add(new ComparablePivotField("company_t",
+        "microsoft", 2, null, createExpectedQCount(
+        new String[]{"highPrice"}, new int[]{0}), null));
+    expectedCardiffPivots.add(new ComparablePivotField("company_t", "null", 2,
+        null, createExpectedQCount(new String[]{"highPrice"},
+        new int[]{1}), null));
+    expectedCardiffPivots.add(new ComparablePivotField("company_t", "bbc", 2,
+        null, createExpectedQCount(new String[]{"highPrice"},
+        new int[]{1}), null));
+    expectedCardiffPivots.add(new ComparablePivotField("company_t", "polecat",
+        3, null, createExpectedQCount(new String[]{"highPrice"},
+        new int[]{1}), null));
+    expectedCardiffPivots.add(new ComparablePivotField("company_t", "fujitsu",
+        1, null, createExpectedQCount(new String[]{"highPrice"},
+        new int[]{0}), null));
+    List<PivotField> expectedDublinPivots = new UnorderedEqualityArrayList<PivotField>();
+    expectedDublinPivots.add(new ComparablePivotField("company_t", "polecat",
+        4, null, createExpectedQCount(new String[]{"highPrice"},
+        new int[]{1}), null));
+    expectedDublinPivots.add(new ComparablePivotField("company_t", "microsoft",
+        4, null, createExpectedQCount(new String[]{"highPrice"},
+        new int[]{1}), null));
+    expectedDublinPivots.add(new ComparablePivotField("company_t", "null", 3,
+        null, createExpectedQCount(new String[]{"highPrice"},
+        new int[]{1}), null));
+    expectedDublinPivots.add(new ComparablePivotField("company_t", "fujitsu",
+        2, null, createExpectedQCount(new String[]{"highPrice"},
+        new int[]{1}), null));
+    expectedDublinPivots.add(new ComparablePivotField("company_t", "bbc", 1,
+        null, createExpectedQCount(new String[]{"highPrice"},
+        new int[]{0}), null));
+    List<PivotField> expectedLondonPivots = new UnorderedEqualityArrayList<PivotField>();
+    expectedLondonPivots.add(new ComparablePivotField("company_t", "polecat",
+        3, null, createExpectedQCount(new String[]{"highPrice"},
+        new int[]{2}), null));
+    expectedLondonPivots.add(new ComparablePivotField("company_t", "microsoft",
+        2, null, createExpectedQCount(new String[]{"highPrice"},
+        new int[]{1}), null));
+    expectedLondonPivots.add(new ComparablePivotField("company_t", "fujitsu",
+        2, null, createExpectedQCount(new String[]{"highPrice"},
+        new int[]{1}), null));
+    expectedLondonPivots.add(new ComparablePivotField("company_t", "null", 3,
+        null, createExpectedQCount(new String[]{"highPrice"},
+        new int[]{2}), null));
+    expectedLondonPivots.add(new ComparablePivotField("company_t", "bbc", 2,
+        null, createExpectedQCount(new String[]{"highPrice"},
+        new int[]{1}), null));
+    List<PivotField> expectedLAPivots = new UnorderedEqualityArrayList<PivotField>();
+    expectedLAPivots.add(new ComparablePivotField("company_t", "microsoft", 2,
+        null, createExpectedQCount(new String[]{"highPrice"},
+        new int[]{1}), null));
+    expectedLAPivots.add(new ComparablePivotField("company_t", "fujitsu", 2,
+        null, createExpectedQCount(new String[]{"highPrice"},
+        new int[]{1}), null));
+    expectedLAPivots.add(new ComparablePivotField("company_t", "null", 2, null,
+        createExpectedQCount(new String[]{"highPrice"}, new int[]{1}),
+        null));
+    expectedLAPivots.add(new ComparablePivotField("company_t", "bbc", 1, null,
+        createExpectedQCount(new String[]{"highPrice"}, new int[]{0}),
+        null));
+    expectedLAPivots.add(new ComparablePivotField("company_t", "polecat", 2,
+        null, createExpectedQCount(new String[]{"highPrice"},
+        new int[]{1}), null));
+    List<PivotField> expectedKrakowPivots = new UnorderedEqualityArrayList<PivotField>();
+    expectedKrakowPivots.add(new ComparablePivotField("company_t", "polecat",
+        2, null, createExpectedQCount(new String[]{"highPrice"},
+        new int[]{1}), null));
+    expectedKrakowPivots.add(new ComparablePivotField("company_t", "bbc", 2,
+        null, createExpectedQCount(new String[]{"highPrice"},
+        new int[]{1}), null));
+    expectedKrakowPivots.add(new ComparablePivotField("company_t", "null", 3,
+        null, createExpectedQCount(new String[]{"highPrice"},
+        new int[]{1}), null));
+    expectedKrakowPivots.add(new ComparablePivotField("company_t", "fujitsu",
+        1, null, createExpectedQCount(new String[]{"highPrice"},
+        new int[]{0}), null));
+    expectedKrakowPivots.add(new ComparablePivotField("company_t", "microsoft",
+        1, null, createExpectedQCount(new String[]{"highPrice"},
+        new int[]{0}), null));
+    List<PivotField> expectedCorkPivots = new UnorderedEqualityArrayList<PivotField>();
+    expectedCorkPivots.add(new ComparablePivotField("company_t", "fujitsu", 1,
+        null, createExpectedQCount(new String[]{"highPrice"},
+        new int[]{0}), null));
+    expectedCorkPivots.add(new ComparablePivotField("company_t", "rte", 1,
+        null, createExpectedQCount(new String[]{"highPrice"},
+        new int[]{0}), null));
+
+    expectedPlacePivots.add(new ComparablePivotField("place_t", "dublin", 4,
+        expectedDublinPivots, createExpectedQCount(
+        new String[]{"highPrice"}, new int[]{1}), null));
+    expectedPlacePivots.add(new ComparablePivotField("place_t", "cardiff", 3,
+        expectedCardiffPivots, createExpectedQCount(
+        new String[]{"highPrice"}, new int[]{1}), null));
+    expectedPlacePivots.add(new ComparablePivotField("place_t", "london", 4,
+        expectedLondonPivots, createExpectedQCount(
+        new String[]{"highPrice"}, new int[]{3}), null));
+    expectedPlacePivots.add(new ComparablePivotField("place_t", "la", 3,
+        expectedLAPivots, createExpectedQCount(
+        new String[]{"highPrice"}, new int[]{1}), null));
+    expectedPlacePivots.add(new ComparablePivotField("place_t", "krakow", 3,
+        expectedKrakowPivots, createExpectedQCount(
+        new String[]{"highPrice"}, new int[]{1}), null));
+    expectedPlacePivots.add(new ComparablePivotField("place_t", "cork", 1,
+        expectedCorkPivots, createExpectedQCount(
+        new String[]{"highPrice"}, new int[]{0}), null));
+
+    List<PivotField> placePivots = rsp.getFacetPivot().get("place_t,company_t");
+
+    // Useful to check for errors, orders lists and does toString() equality
+    // check
+    testOrderedPivotsStringEquality(expectedPlacePivots, placePivots);
+
+    assertEquals(expectedPlacePivots, placePivots);
+
+    // Add second query for low price
+    params.add("facet.query", "{!tag=s1 key=lowPrice}price_ti:[0 TO 20]");
+    expectedPlacePivots = new UnorderedEqualityArrayList<PivotField>();
+    expectedCardiffPivots = new UnorderedEqualityArrayList<PivotField>();
+    expectedCardiffPivots.add(new ComparablePivotField("company_t",
+        "microsoft", 2, null, createExpectedQCount(new String[]{
+        "highPrice", "lowPrice"}, new int[]{0, 1}), null));
+    expectedCardiffPivots.add(new ComparablePivotField("company_t", "null", 2,
+        null, createExpectedQCount(
+        new String[]{"highPrice", "lowPrice"}, new int[]{1, 0}), null));
+    expectedCardiffPivots.add(new ComparablePivotField("company_t", "bbc", 2,
+        null, createExpectedQCount(
+        new String[]{"highPrice", "lowPrice"}, new int[]{1, 0}), null));
+    expectedCardiffPivots.add(new ComparablePivotField("company_t", "polecat",
+        3, null, createExpectedQCount(new String[]{"highPrice",
+        "lowPrice"}, new int[]{1, 1}), null));
+    expectedCardiffPivots.add(new ComparablePivotField("company_t", "fujitsu",
+        1, null, createExpectedQCount(new String[]{"highPrice",
+        "lowPrice"}, new int[]{0, 0}), null));
+    expectedDublinPivots = new UnorderedEqualityArrayList<PivotField>();
+    expectedDublinPivots.add(new ComparablePivotField("company_t", "polecat",
+        4, null, createExpectedQCount(new String[]{"highPrice",
+        "lowPrice"}, new int[]{1, 2}), null));
+    expectedDublinPivots.add(new ComparablePivotField("company_t", "microsoft",
+        4, null, createExpectedQCount(new String[]{"highPrice",
+        "lowPrice"}, new int[]{1, 2}), null));
+    expectedDublinPivots.add(new ComparablePivotField("company_t", "null", 3,
+        null, createExpectedQCount(
+        new String[]{"highPrice", "lowPrice"}, new int[]{1, 1}), null));
+    expectedDublinPivots.add(new ComparablePivotField("company_t", "fujitsu",
+        2, null, createExpectedQCount(new String[]{"highPrice",
+        "lowPrice"}, new int[]{1, 0}), null));
+    expectedDublinPivots.add(new ComparablePivotField("company_t", "bbc", 1,
+        null, createExpectedQCount(
+        new String[]{"highPrice", "lowPrice"}, new int[]{0, 0}), null));
+    expectedLondonPivots = new UnorderedEqualityArrayList<PivotField>();
+    expectedLondonPivots.add(new ComparablePivotField("company_t", "polecat",
+        3, null, createExpectedQCount(new String[]{"highPrice",
+        "lowPrice"}, new int[]{2, 0}), null));
+    expectedLondonPivots.add(new ComparablePivotField("company_t", "microsoft",
+        2, null, createExpectedQCount(new String[]{"highPrice",
+        "lowPrice"}, new int[]{1, 0}), null));
+    expectedLondonPivots.add(new ComparablePivotField("company_t", "fujitsu",
+        2, null, createExpectedQCount(new String[]{"highPrice",
+        "lowPrice"}, new int[]{1, 0}), null));
+    expectedLondonPivots.add(new ComparablePivotField("company_t", "null", 3,
+        null, createExpectedQCount(
+        new String[]{"highPrice", "lowPrice"}, new int[]{2, 0}), null));
+    expectedLondonPivots.add(new ComparablePivotField("company_t", "bbc", 2,
+        null, createExpectedQCount(
+        new String[]{"highPrice", "lowPrice"}, new int[]{1, 0}), null));
+    expectedLAPivots = new UnorderedEqualityArrayList<PivotField>();
+    expectedLAPivots.add(new ComparablePivotField("company_t", "microsoft", 2,
+        null, createExpectedQCount(
+        new String[]{"highPrice", "lowPrice"}, new int[]{1, 0}), null));
+    expectedLAPivots.add(new ComparablePivotField("company_t", "fujitsu", 2,
+        null, createExpectedQCount(
+        new String[]{"highPrice", "lowPrice"}, new int[]{1, 0}), null));
+    expectedLAPivots.add(new ComparablePivotField("company_t", "null", 2, null,
+        createExpectedQCount(new String[]{"highPrice", "lowPrice"},
+            new int[]{1, 0}), null));
+    expectedLAPivots.add(new ComparablePivotField("company_t", "bbc", 1, null,
+        createExpectedQCount(new String[]{"highPrice", "lowPrice"},
+            new int[]{0, 0}), null));
+    expectedLAPivots.add(new ComparablePivotField("company_t", "polecat", 2,
+        null, createExpectedQCount(
+        new String[]{"highPrice", "lowPrice"}, new int[]{1, 0}), null));
+    expectedKrakowPivots = new UnorderedEqualityArrayList<PivotField>();
+    expectedKrakowPivots.add(new ComparablePivotField("company_t", "polecat",
+        2, null, createExpectedQCount(new String[]{"highPrice",
+        "lowPrice"}, new int[]{1, 0}), null));
+    expectedKrakowPivots.add(new ComparablePivotField("company_t", "bbc", 2,
+        null, createExpectedQCount(
+        new String[]{"highPrice", "lowPrice"}, new int[]{1, 0}), null));
+    expectedKrakowPivots.add(new ComparablePivotField("company_t", "null", 3,
+        null, createExpectedQCount(
+        new String[]{"highPrice", "lowPrice"}, new int[]{1, 0}), null));
+    expectedKrakowPivots.add(new ComparablePivotField("company_t", "fujitsu",
+        1, null, createExpectedQCount(new String[]{"highPrice",
+        "lowPrice"}, new int[]{0, 0}), null));
+    expectedKrakowPivots.add(new ComparablePivotField("company_t", "microsoft",
+        1, null, createExpectedQCount(new String[]{"highPrice",
+        "lowPrice"}, new int[]{0, 0}), null));
+    expectedCorkPivots = new UnorderedEqualityArrayList<PivotField>();
+    expectedCorkPivots.add(new ComparablePivotField("company_t", "fujitsu", 1,
+        null, createExpectedQCount(
+        new String[]{"highPrice", "lowPrice"}, new int[]{0, 0}), null));
+    expectedCorkPivots.add(new ComparablePivotField("company_t", "rte", 1,
+        null, createExpectedQCount(
+        new String[]{"highPrice", "lowPrice"}, new int[]{0, 0}), null));
+
+    expectedPlacePivots.add(new ComparablePivotField("place_t", "dublin", 4,
+        expectedDublinPivots, createExpectedQCount(new String[]{
+        "highPrice", "lowPrice"}, new int[]{1, 2}), null));
+    expectedPlacePivots.add(new ComparablePivotField("place_t", "cardiff", 3,
+        expectedCardiffPivots, createExpectedQCount(new String[]{
+        "highPrice", "lowPrice"}, new int[]{1, 1}), null));
+    expectedPlacePivots.add(new ComparablePivotField("place_t", "london", 4,
+        expectedLondonPivots, createExpectedQCount(new String[]{
+        "highPrice", "lowPrice"}, new int[]{3, 0}), null));
+    expectedPlacePivots.add(new ComparablePivotField("place_t", "la", 3,
+        expectedLAPivots, createExpectedQCount(new String[]{"highPrice",
+        "lowPrice"}, new int[]{1, 0}), null));
+    expectedPlacePivots.add(new ComparablePivotField("place_t", "krakow", 3,
+        expectedKrakowPivots, createExpectedQCount(new String[]{
+        "highPrice", "lowPrice"}, new int[]{1, 0}), null));
+    expectedPlacePivots.add(new ComparablePivotField("place_t", "cork", 1,
+        expectedCorkPivots, createExpectedQCount(new String[]{
+        "highPrice", "lowPrice"}, new int[]{0, 0}), null));
+
+    rsp = queryServer(params);
+
+    placePivots = rsp.getFacetPivot().get("place_t,company_t");
+
+    // Useful to check for errors, orders lists and does toString() equality
+    // check
+    testOrderedPivotsStringEquality(expectedPlacePivots, placePivots);
+    assertEquals(expectedPlacePivots, placePivots);
+
+    // Test sorting by count
+
+    params.set(FacetParams.FACET_SORT, FacetParams.FACET_SORT_COUNT);
+
+    rsp = queryServer(params);
+
+    placePivots = rsp.getFacetPivot().get("place_t,company_t");
+
+    testCountSorting(placePivots);
+
+    // Test limit
+
+    params.set(FacetParams.FACET_LIMIT, 2);
+
+    rsp = queryServer(params);
+
+    expectedPlacePivots = new UnorderedEqualityArrayList<PivotField>();
+    expectedDublinPivots = new UnorderedEqualityArrayList<PivotField>();
+    expectedDublinPivots.add(new ComparablePivotField("company_t", "polecat",
+        4, null, createExpectedQCount(new String[]{"highPrice",
+        "lowPrice"}, new int[]{1, 2}), null));
+    expectedDublinPivots.add(new ComparablePivotField("company_t", "microsoft",
+        4, null, createExpectedQCount(new String[]{"highPrice",
+        "lowPrice"}, new int[]{1, 2}), null));
+    expectedLondonPivots = new UnorderedEqualityArrayList<PivotField>();
+    expectedLondonPivots.add(new ComparablePivotField("company_t", "null", 3,
+        null, createExpectedQCount(
+        new String[]{"highPrice", "lowPrice"}, new int[]{2, 0}), null));
+    expectedLondonPivots.add(new ComparablePivotField("company_t", "polecat",
+        3, null, createExpectedQCount(new String[]{"highPrice",
+        "lowPrice"}, new int[]{2, 0}), null));
+    expectedPlacePivots.add(new ComparablePivotField("place_t", "dublin", 4,
+        expectedDublinPivots, createExpectedQCount(new String[]{
+        "highPrice", "lowPrice"}, new int[]{1, 2}), null));
+    expectedPlacePivots.add(new ComparablePivotField("place_t", "london", 4,
+        expectedLondonPivots, createExpectedQCount(new String[]{
+        "highPrice", "lowPrice"}, new int[]{3, 0}), null));
+
+    placePivots = rsp.getFacetPivot().get("place_t,company_t");
+
+    assertEquals(expectedPlacePivots, placePivots);
+
+    // Test individual facet.limit values
+    params.remove(FacetParams.FACET_LIMIT);
+
+    params.set("f.place_t." + FacetParams.FACET_LIMIT, 1);
+    params.set("f.company_t." + FacetParams.FACET_LIMIT, 4);
+
+    rsp = queryServer(params);
+
+    expectedPlacePivots = new UnorderedEqualityArrayList<PivotField>();
+
+    expectedDublinPivots = new UnorderedEqualityArrayList<PivotField>();
+    expectedDublinPivots.add(new ComparablePivotField("company_t", "microsoft",
+        4, null, createExpectedQCount(new String[]{"highPrice",
+        "lowPrice"}, new int[]{1, 2}), null));
+    expectedDublinPivots.add(new ComparablePivotField("company_t", "polecat",
+        4, null, createExpectedQCount(new String[]{"highPrice",
+        "lowPrice"}, new int[]{1, 2}), null));
+    expectedDublinPivots.add(new ComparablePivotField("company_t", "null", 3,
+        null, createExpectedQCount(
+        new String[]{"highPrice", "lowPrice"}, new int[]{1, 1}), null));
+    expectedDublinPivots.add(new ComparablePivotField("company_t", "fujitsu",
+        2, null, createExpectedQCount(new String[]{"highPrice",
+        "lowPrice"}, new int[]{1, 0}), null));
+
+    expectedLondonPivots = new UnorderedEqualityArrayList<PivotField>();
+    expectedLondonPivots.add(new ComparablePivotField("company_t", "null", 3,
+        null, createExpectedQCount(
+        new String[]{"highPrice", "lowPrice"}, new int[]{2, 0}), null));
+    expectedLondonPivots.add(new ComparablePivotField("company_t", "polecat",
+        3, null, createExpectedQCount(new String[]{"highPrice",
+        "lowPrice"}, new int[]{2, 0}), null));
+    expectedLondonPivots.add(new ComparablePivotField("company_t", "bbc", 2,
+        null, createExpectedQCount(
+        new String[]{"highPrice", "lowPrice"}, new int[]{1, 0}), null));
+    expectedLondonPivots.add(new ComparablePivotField("company_t", "fujitsu",
+        2, null, createExpectedQCount(new String[]{"highPrice",
+        "lowPrice"}, new int[]{1, 0}), null));
+
+    expectedCardiffPivots = new UnorderedEqualityArrayList<PivotField>();
+    expectedCardiffPivots.add(new ComparablePivotField("company_t", "polecat",
+        3, null, createExpectedQCount(new String[]{"highPrice",
+        "lowPrice"}, new int[]{1, 1}), null));
+
+    expectedKrakowPivots = new UnorderedEqualityArrayList<PivotField>();
+    expectedKrakowPivots.add(new ComparablePivotField("company_t", "null", 3,
+        null, createExpectedQCount(
+        new String[]{"highPrice", "lowPrice"}, new int[]{1, 0}), null));
+
+    expectedLAPivots = new UnorderedEqualityArrayList<PivotField>();
+    expectedLAPivots.add(new ComparablePivotField("company_t", "fujitsu", 2,
+        null, createExpectedQCount(
+        new String[]{"highPrice", "lowPrice"}, new int[]{1, 0}), null));
+
+    expectedCorkPivots = new UnorderedEqualityArrayList<PivotField>();
+    expectedCorkPivots.add(new ComparablePivotField("company_t", "fujitsu", 1,
+        null, createExpectedQCount(
+        new String[]{"highPrice", "lowPrice"}, new int[]{0, 0}), null));
+
+    expectedPlacePivots.add(new ComparablePivotField("place_t", "dublin", 4,
+        expectedDublinPivots, createExpectedQCount(new String[]{
+        "highPrice", "lowPrice"}, new int[]{1, 2}), null));
+
+    placePivots = rsp.getFacetPivot().get("place_t,company_t");
+    assertEquals(expectedPlacePivots, placePivots);
+
+    params.remove("f.company_t." + FacetParams.FACET_LIMIT);
+    params.remove("f.place_t." + FacetParams.FACET_LIMIT);
+    params.set(FacetParams.FACET_LIMIT, 2);
+
+    // Test facet.missing=true with diff sorts
+
+    index("id", 777); // NOTE: id=25 has no place as well
+    commit();
+
+    SolrParams missingA = params("q", "*:*", "rows", "0", "facet", "true",
+        "facet.pivot", "place_t,company_t",
+        // default facet.sort
+        FacetParams.FACET_MISSING, "true");
+    SolrParams missingB = SolrParams.wrapDefaults(missingA,
+        params(FacetParams.FACET_LIMIT, "4", "facet.sort", "index"));
+    for (SolrParams p : new SolrParams[]{missingA, missingB}) {
+      // in either case, the last pivot option should be the same
+      rsp = query(p);
+      placePivots = rsp.getFacetPivot().get("place_t,company_t");
+      assertTrue("not enough values for pivot: " + p + " => " + placePivots,
+          1 < placePivots.size());
+      PivotField missing = placePivots.get(placePivots.size() - 1);
+      assertNull("not the missing place value: " + p, missing.getValue());
+      assertEquals("wrong missing place count: " + p, 2, missing.getCount());
+      assertTrue("not enough sub-pivots for missing place: " + p + " => "
+          + missing.getPivot(), 1 < missing.getPivot().size());
+      missing = missing.getPivot().get(missing.getPivot().size() - 1);
+      assertNull("not the missing company value: " + p, missing.getValue());
+      assertEquals("wrong missing company count: " + p, 1, missing.getCount());
+      assertNull("company shouldn't have sub-pivots: " + p, missing.getPivot());
+    }
+
+    // sort=index + mincount + limit
+    for (SolrParams variableParams : new SolrParams[]{
+        // we should get the same results regardless of overrequest
+        params("facet.overrequest.count", "0", "facet.overrequest.ratio", "0"),
+        params()}) {
+
+      SolrParams p = SolrParams.wrapDefaults(
+          params("q", "*:*", "rows", "0", "facet", "true", "facet.pivot",
+              "company_t", "facet.sort", "index", "facet.pivot.mincount", "4",
+              "facet.limit", "4"), variableParams);
+
+      try {
+        List<PivotField> pivots = query(p).getFacetPivot().get("company_t");
+        assertEquals(4, pivots.size());
+        assertEquals("fujitsu", pivots.get(0).getValue());
+        assertEquals(4, pivots.get(0).getCount());
+        assertEquals("microsoft", pivots.get(1).getValue());
+        assertEquals(5, pivots.get(1).getCount());
+        assertEquals("null", pivots.get(2).getValue());
+        assertEquals(6, pivots.get(2).getCount());
+        assertEquals("polecat", pivots.get(3).getValue());
+        assertEquals(6, pivots.get(3).getCount());
+
+      } catch (AssertionFailedError ae) {
+        throw new AssertionError(ae.getMessage() + " <== " + p.toString(), ae);
+      }
+    }
+
+    // sort=index + mincount + limit + offset
+    for (SolrParams variableParams : new SolrParams[]{
+        // we should get the same results regardless of overrequest
+        params("facet.overrequest.count", "0", "facet.overrequest.ratio", "0"),
+        params()}) {
+
+      SolrParams p = SolrParams.wrapDefaults(
+          params("q", "*:*", "rows", "0", "facet", "true", "facet.pivot",
+              "company_t", "facet.sort", "index", "facet.pivot.mincount", "4",
+              "facet.offset", "1", "facet.limit", "4"), variableParams);
+      try {
+        List<PivotField> pivots = query(p).getFacetPivot().get("company_t");
+        assertEquals(3, pivots.size()); // asked for 4, but not enough meet the
+        // mincount
+        assertEquals("microsoft", pivots.get(0).getValue());
+        assertEquals(5, pivots.get(0).getCount());
+        assertEquals("null", pivots.get(1).getValue());
+        assertEquals(6, pivots.get(1).getCount());
+        assertEquals("polecat", pivots.get(2).getValue());
+        assertEquals(6, pivots.get(2).getCount());
+
+      } catch (AssertionFailedError ae) {
+        throw new AssertionError(ae.getMessage() + " <== " + p.toString(), ae);
+      }
+
+    }
+
+    // sort=index + mincount + limit + offset (more permutations)
+    for (SolrParams variableParams : new SolrParams[]{
+        // all of these combinations should result in the same first value
+        params("facet.pivot.mincount", "4", "facet.offset", "2"),
+        params("facet.pivot.mincount", "5", "facet.offset", "1"),
+        params("facet.pivot.mincount", "6", "facet.offset", "0")}) {
+
+      SolrParams p = SolrParams.wrapDefaults(
+          params("q", "*:*", "rows", "0", "facet", "true", "facet.limit", "1",
+              "facet.sort", "index", "facet.overrequest.ratio", "0",
+              "facet.pivot", "company_t"), variableParams);
+
+      try {
+        List<PivotField> pivots = query(p).getFacetPivot().get("company_t");
+        assertEquals(1, pivots.size());
+        assertEquals(pivots.toString(), "null", pivots.get(0).getValue());
+        assertEquals(pivots.toString(), 6, pivots.get(0).getCount());
+
+      } catch (AssertionFailedError ae) {
+        throw new AssertionError(ae.getMessage() + " <== " + p.toString(), ae);
+      }
+    }
+  }
+
+  private void testPivotFacetRangeAndQuery() throws Exception {
+    SolrParams params = params("q", "*:*",
+        "rows", "0",
+        "facet", "true",
+        "stats", "true",
+        "facet.pivot", "{!range=s1 query=s2 stats=s3}place_t,company_t",
+        "facet.range", "{!tag=s1 key=price}price_ti",
+        "facet.query", "{!tag=s2 key=highPrice}price_ti:[25 TO 100]",
+        "facet.query", "{!tag=s2 key=lowPrice}price_ti:[0 TO 20]",
+        "stats.field", ("{!tag=s3 key=avg_price}price_ti"),
+        "facet.range.start", "0",
+        "facet.range.end", "100",
+        "facet.range.gap", "20",
+        FacetParams.FACET_SORT, FacetParams.FACET_SORT_COUNT,
+        FacetParams.FACET_LIMIT, "2");
+
+    UnorderedEqualityArrayList<PivotField> expectedPlacePivots = new UnorderedEqualityArrayList<>();
+    UnorderedEqualityArrayList<PivotField> expectedDublinPivots = new UnorderedEqualityArrayList<>();
+    expectedDublinPivots.add(new ComparablePivotField("company_t", "polecat",
+        4, null, createExpectedQCount(new String[]{"highPrice",
+        "lowPrice"}, new int[]{1, 2}), createExpectedRange("price", 0, 100, 20, 2, 1, 0,
+        0, 0)));
+    expectedDublinPivots.add(new ComparablePivotField("company_t", "microsoft",
+        4, null, createExpectedQCount(new String[]{"highPrice",
+        "lowPrice"}, new int[]{1, 2}), createExpectedRange("price", 0, 100, 20, 2, 1, 0,
+        0, 0)));
+    UnorderedEqualityArrayList<PivotField> expectedLondonPivots = new UnorderedEqualityArrayList<>();
+    expectedLondonPivots.add(new ComparablePivotField("company_t", "null", 3,
+        null, createExpectedQCount(
+        new String[]{"highPrice", "lowPrice"}, new int[]{2, 0}), createExpectedRange("price", 0, 100, 20, 0, 2, 0, 0,
+        0)));
+    expectedLondonPivots.add(new ComparablePivotField("company_t", "polecat",
+        3, null, createExpectedQCount(new String[]{"highPrice",
+        "lowPrice"}, new int[]{2, 0}), createExpectedRange("price", 0, 100, 20, 0, 2, 0,
+        0, 0)));
+    expectedPlacePivots.add(new ComparablePivotField("place_t", "dublin", 4,
+        expectedDublinPivots, createExpectedQCount(new String[]{
+        "highPrice", "lowPrice"}, new int[]{1, 2}), createExpectedRange("price", 0, 100,
+        20, 2, 1, 0, 0, 0)));
+    expectedPlacePivots.add(new ComparablePivotField("place_t", "london", 4,
+        expectedLondonPivots, createExpectedQCount(new String[]{
+        "highPrice", "lowPrice"}, new int[]{3, 0}), createExpectedRange("price", 0, 100,
+        20, 0, 3, 0, 0, 0)));
+
+    QueryResponse rsp = query(params);
+    List<PivotField> placePivots = rsp.getFacetPivot().get("place_t,company_t");
+    assertEquals(expectedPlacePivots, placePivots);
+
+    PivotField dublinPivotField = placePivots.get(0);
+    assertEquals("dublin", dublinPivotField.getValue());
+    assertEquals(4, dublinPivotField.getCount());
+
+    PivotField microsoftPivotField = dublinPivotField.getPivot().get(0);
+    assertEquals("microsoft", microsoftPivotField.getValue());
+    assertEquals(4, microsoftPivotField.getCount());
+
+    FieldStatsInfo dublinMicrosoftStatsInfo = microsoftPivotField.getFieldStatsInfo().get("avg_price");
+    assertEquals(21.0, (double) dublinMicrosoftStatsInfo.getMean(), 0.1E-7);
+    assertEquals(15.0, dublinMicrosoftStatsInfo.getMin());
+    assertEquals(29.0, dublinMicrosoftStatsInfo.getMax());
+    assertEquals(3, (long) dublinMicrosoftStatsInfo.getCount());
+    assertEquals(1, (long) dublinMicrosoftStatsInfo.getMissing());
+    assertEquals(63.0, dublinMicrosoftStatsInfo.getSum());
+    assertEquals(1427.0, dublinMicrosoftStatsInfo.getSumOfSquares(), 0.1E-7);
+    assertEquals(7.211102550927978, dublinMicrosoftStatsInfo.getStddev(), 0.1E-7);
+  }
+
+  private void testNegativeFacetQuery() throws Exception {
+    // this should not hang facet.query under the pivot
+    SolrParams params = params("q", "*:*",
+        "rows", "0",
+        "stats", "true",
+        "facet.query", "{!tag=ttt}price_ti:[25 TO 100]",
+        "facet", "true",
+        "facet.pivot", "{!query=t}place_t,company_t");
+    QueryResponse rsp = query(params);
+
+    assertNullFacetTypeInsidePivot(FacetParams.FACET_QUERY, rsp.getFacetPivot().get("place_t,company_t"));
+
+    params = params("q", "*:*",
+        "rows", "0",
+        "stats", "true",
+        "facet", "true",
+        "facet.pivot", "{!query=t}place_t,company_t");
+    rsp = query(params);
+    assertNullFacetTypeInsidePivot(FacetParams.FACET_QUERY, rsp.getFacetPivot().get("place_t,company_t"));
+
+    params = params("q", "*:*",
+        "rows", "0",
+        "facet.query", "{!tag=t}price_ti:[25 TO 100]",
+        "hang", "", // empty
+        "facet", "true",
+        "facet.pivot", "{!query=$hang}place_t,company_t");
+    rsp = query(params);
+    assertNullFacetTypeInsidePivot(FacetParams.FACET_QUERY, rsp.getFacetPivot().get("place_t,company_t"));
+
+    params = params("q", "*:*",
+        "rows", "0",
+        "facet.query", "{!tag=t}price_ti:[25 TO 100]",
+        "hang", "price_ti:[0 TO 20]", // with a query
+        "facet", "true",
+        "facet.pivot", "{!query=$hang}place_t,company_t");
+    rsp = query(params);
+    // we aren't going to start calculating facet query unless the query is specified with a 'facet.query' param
+    // hence hanging an arbitrary query shouldn't work
+    assertNullFacetTypeInsidePivot(FacetParams.FACET_QUERY, rsp.getFacetPivot().get("place_t,company_t"));
+  }
+
+  private void testNegativeRangeQuery() throws Exception {
+    SolrParams params = params("q", "*:*",
+        "rows", "0",
+        "stats", "true",
+        "facet.range", "{!tag=s1 key=price}price_ti",
+        "facet", "true",
+        "facet.pivot", "{!range=s}place_t,company_t",
+        "facet.range.start", "0",
+        "facet.range.end", "100",
+        "facet.range.gap", "20");
+    QueryResponse rsp = query(params);
+    assertNullFacetTypeInsidePivot(FacetParams.FACET_RANGE, rsp.getFacetPivot().get("place_t,company_t"));
+
+    params = params("q", "*:*",
+        "rows", "0",
+        "stats", "true",
+        "facet.range", "{!tag=s1 key=price}price_ti",
+        "facet", "true",
+        "hang", "", // empty!
+        "facet.pivot", "{!range=$hang}place_t,company_t",
+        "facet.range.start", "0",
+        "facet.range.end", "100",
+        "facet.range.gap", "20");
+    rsp = query(params);
+    assertNullFacetTypeInsidePivot(FacetParams.FACET_RANGE, rsp.getFacetPivot().get("place_t,company_t"));
+
+    params = params("q", "*:*",
+        "rows", "0",
+        "stats", "true",
+        "facet.range", "{!tag=s1 key=price}price_ti",
+        "facet", "true",
+        "hang", "price_ti",
+        "facet.pivot", "{!range=$hang}place_t,company_t",
+        "facet.range.start", "0",
+        "facet.range.end", "100",
+        "facet.range.gap", "20");
+    rsp = query(params);
+    assertNullFacetTypeInsidePivot(FacetParams.FACET_RANGE, rsp.getFacetPivot().get("place_t,company_t"));
+  }
+
+  private Map<String, Integer> createExpectedQCount(String[] keys, int[] counts) {
+    Map<String, Integer> expectedQCounts = new LinkedHashMap<>();
+    for (int idx = 0; idx < keys.length; idx++) {
+      expectedQCounts.put(keys[idx], counts[idx]);
+    }
+    return expectedQCounts;
+  }
+
+  private void assertNullFacetTypeInsidePivot(String facetType, List<PivotField> pivots) {
+    for (PivotField pivot : pivots) {
+      if (facetType == FacetParams.FACET_QUERY) {
+        assertNull("pivot=" + pivot + " facetType=" + facetType
+            + " should've been null. Found: " + pivot.getFacetQuery(), pivot.getFacetQuery());
+      } else if (facetType == FacetParams.FACET_RANGE) {
+        assertNull("pivot=" + pivot + " facetType=" + facetType
+            + " should've been null. Found: " + pivot.getFacetRanges(), pivot.getFacetRanges());
+      }
+
+      if (pivot.getPivot() != null) {
+        assertNullFacetTypeInsidePivot(facetType, pivot.getPivot());
+      }
+    }
+  }
+
   // Useful to check for errors, orders lists and does toString() equality check
   private void testOrderedPivotsStringEquality(
       List<PivotField> expectedPlacePivots, List<PivotField> placePivots) {
@@ -526,11 +1518,31 @@ public class DistributedFacetPivotSmallT
     assertEquals(msg + " stats max", val, stats.getMax());
   }
 
+  private List<RangeFacet> createExpectedRange(String key, int start, int end,
+                                               int gap, int... values) {
+    List<RangeFacet> expectedRanges = new ArrayList<>();
+    RangeFacet expectedPrices = new RangeFacet.Numeric(key, start, end, gap, null, null, null);
+    expectedRanges.add(expectedPrices);
+    int idx = 0;
+    for (int range = start; range < end; range += gap) {
+      expectedPrices.addCount(String.valueOf(range), values[idx]);
+      if (idx < values.length) {
+        idx++;
+      }
+    }
+    return expectedRanges;
+  }
+
   public static class ComparablePivotField extends PivotField {
-    
 
-    public ComparablePivotField(String f, Object v, int count, List<PivotField> pivot) {
-      super(f,v,count,pivot, null);
+    public ComparablePivotField(String f, Object v, int count,
+                                List<PivotField> pivot,  Map<String,Integer> queryCounts, List<RangeFacet> ranges) {
+      super(f, v, count, pivot, null, queryCounts, ranges);
+    }
+
+    public ComparablePivotField(String f, Object v, int count,
+                                List<PivotField> pivot) {
+      super(f, v, count, pivot, null, null, null);
     }
 
     @Override
@@ -549,6 +1561,44 @@ public class DistributedFacetPivotSmallT
       if (getValue() == null) {
         if (other.getValue() != null) return false;
       } else if (!getValue().equals(other.getValue())) return false;
+      if (getFacetRanges() == null) {
+        if (other.getFacetRanges() != null) return false;
+      } else {
+        if (getFacetRanges().size() != other.getFacetRanges().size()) return false;
+        for (RangeFacet entry : getFacetRanges()) {
+          boolean found = false;
+          for (RangeFacet otherRange : other.getFacetRanges()) {
+            if (otherRange.getName().equals(entry.getName())) {
+              found = true;
+
+              if (!entry.getGap().equals(otherRange.getGap()))  return false;
+              if (!entry.getStart().equals(otherRange.getStart()))  return false;
+              if (!entry.getEnd().equals(otherRange.getEnd()))  return false;
+
+              List<RangeFacet.Count> myCounts = entry.getCounts();
+              List<RangeFacet.Count> otherRangeCounts = otherRange.getCounts();
+              if ( (myCounts == null && otherRangeCounts != null)
+                  || (myCounts != null && otherRangeCounts == null)
+                  || (myCounts.size() != otherRangeCounts.size()))  return false;
+
+              for (int i=0; i<myCounts.size(); i++) {
+                if (!myCounts.get(i).getValue().equals(otherRangeCounts.get(i).getValue())) return false;
+                if (myCounts.get(i).getCount() != otherRangeCounts.get(i).getCount())  return false;
+              }
+            }
+          }
+          if (!found) return false;
+        }
+      }
+      if (getFacetQuery() == null) {
+        if (other.getFacetQuery() != null) return false;
+      } else {
+        if (getFacetQuery().size() != other.getFacetQuery().size()) return false;
+        for (Map.Entry<String,Integer> entry : getFacetQuery().entrySet()) {
+          Integer otherQCount = other.getFacetQuery().get(entry.getKey());
+          if (otherQCount == null || otherQCount != entry.getValue()) return false;
+        }
+      }
       return true;
     }
   }
@@ -564,7 +1614,7 @@ public class DistributedFacetPivotSmallT
           equal = true;
           for (Object objectInOtherList : otherList) {
             if (!contains(objectInOtherList)) {
-              equal = false;
+              return false;
             }
           }
         }
@@ -582,7 +1632,7 @@ public class DistributedFacetPivotSmallT
     }
   }
   
-  public class PivotFieldComparator implements Comparator<PivotField> {
+  public static class PivotFieldComparator implements Comparator<PivotField> {
     
     @Override
     public int compare(PivotField o1, PivotField o2) {
@@ -591,6 +1641,33 @@ public class DistributedFacetPivotSmallT
       if (compare == 0) {
         compare = ((String) o2.getValue()).compareTo((String) o1.getValue());
       }
+      if (compare == 0) {
+        for (Map.Entry<String,Integer> entry : o1.getFacetQuery().entrySet()) {
+          compare = entry.getValue().compareTo(
+              o2.getFacetQuery().get(entry.getKey()));
+          if (compare != 0) {
+            break;
+          }
+        }
+        if (compare == 0) {
+          compare = Integer.valueOf(o1.getFacetQuery().size()).compareTo(
+              o2.getFacetQuery().size());
+        }
+      }
+      if (compare == 0) {
+        for (RangeFacet entry : o1.getFacetRanges()) {
+          boolean found = false;
+          for (RangeFacet otherRangeFacet : o2.getFacetRanges()) {
+            if (otherRangeFacet.getName().equals(entry.getName()))  {
+              found = true;
+            }
+          }
+          if (!found) {
+            compare = 1;
+            break;
+          }
+        }
+      }
       return compare;
     }
     

Modified: lucene/dev/branches/branch_5x/solr/solrj/src/java/org/apache/solr/client/solrj/response/PivotField.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_5x/solr/solrj/src/java/org/apache/solr/client/solrj/response/PivotField.java?rev=1689839&r1=1689838&r2=1689839&view=diff
==============================================================================
--- lucene/dev/branches/branch_5x/solr/solrj/src/java/org/apache/solr/client/solrj/response/PivotField.java (original)
+++ lucene/dev/branches/branch_5x/solr/solrj/src/java/org/apache/solr/client/solrj/response/PivotField.java Wed Jul  8 11:05:27 2015
@@ -22,6 +22,8 @@ import java.io.Serializable;
 import java.util.List;
 import java.util.Map;
 
+import org.apache.solr.common.util.NamedList;
+
 public class PivotField implements Serializable
 {
   final String  _field;
@@ -29,22 +31,26 @@ public class PivotField implements Seria
   final int     _count;
   final List<PivotField> _pivot;
   final Map<String,FieldStatsInfo> _statsInfo;
+  final Map<String,Integer> _querycounts;
+  final List<RangeFacet> _ranges;
 
   /**
-   * @deprecated Use {@link #PivotField(String,Object,int,List,Map)} with a null <code>statsInfo</code>
+   * @deprecated Use {@link #PivotField(String,Object,int,List,Map,Map,List)} with null <code>statsInfo</code>, queryCounts and ranges
    */
   @Deprecated
   public PivotField( String f, Object v, int count, List<PivotField> pivot) {
-    this(f, v, count, pivot, null);
+    this(f, v, count, pivot, null, null, null);
   }
 
-  public PivotField( String f, Object v, int count, List<PivotField> pivot, Map<String,FieldStatsInfo> statsInfo)
+  public PivotField( String f, Object v, int count, List<PivotField> pivot, Map<String,FieldStatsInfo> statsInfo, Map<String,Integer> queryCounts, List<RangeFacet> ranges)
   {
     _field = f;
     _value = v;
     _count = count;
     _pivot = pivot;
     _statsInfo = statsInfo;
+    _querycounts= queryCounts;
+    _ranges= ranges;
   }
    
   public String getField() {
@@ -67,6 +73,14 @@ public class PivotField implements Seria
     return _statsInfo;
   }
 
+  public Map<String,Integer> getFacetQuery() {
+    return _querycounts;
+  }
+
+  public List<RangeFacet> getFacetRanges() {
+    return _ranges;
+  }
+
   @Override
   public String toString()
   {
@@ -88,6 +102,12 @@ public class PivotField implements Seria
       out.print("]");
     }
     out.println();
+    if(_querycounts != null) {
+      out.println(_querycounts.toString());
+    }
+    if(_ranges != null) {
+      out.println(_ranges.toString());
+    }
     if( _pivot != null ) {
       for( PivotField p : _pivot ) {
         p.write( out, indent+1 );

Modified: lucene/dev/branches/branch_5x/solr/solrj/src/java/org/apache/solr/client/solrj/response/QueryResponse.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_5x/solr/solrj/src/java/org/apache/solr/client/solrj/response/QueryResponse.java?rev=1689839&r1=1689838&r2=1689839&view=diff
==============================================================================
--- lucene/dev/branches/branch_5x/solr/solrj/src/java/org/apache/solr/client/solrj/response/QueryResponse.java (original)
+++ lucene/dev/branches/branch_5x/solr/solrj/src/java/org/apache/solr/client/solrj/response/QueryResponse.java Wed Jul  8 11:05:27 2015
@@ -348,41 +348,7 @@ public class QueryResponse extends SolrR
     //Parse range facets
     NamedList<NamedList<Object>> rf = (NamedList<NamedList<Object>>) info.get("facet_ranges");
     if (rf != null) {
-      _facetRanges = new ArrayList<>( rf.size() );
-      for (Map.Entry<String, NamedList<Object>> facet : rf) {
-        NamedList<Object> values = facet.getValue();
-        Object rawGap = values.get("gap");
-
-        RangeFacet rangeFacet;
-        if (rawGap instanceof Number) {
-          Number gap = (Number) rawGap;
-          Number start = (Number) values.get("start");
-          Number end = (Number) values.get("end");
-
-          Number before = (Number) values.get("before");
-          Number after = (Number) values.get("after");
-          Number between = (Number) values.get("between");
-
-          rangeFacet = new RangeFacet.Numeric(facet.getKey(), start, end, gap, before, after, between);
-        } else {
-          String gap = (String) rawGap;
-          Date start = (Date) values.get("start");
-          Date end = (Date) values.get("end");
-
-          Number before = (Number) values.get("before");
-          Number after = (Number) values.get("after");
-          Number between = (Number) values.get("between");
-
-          rangeFacet = new RangeFacet.Date(facet.getKey(), start, end, gap, before, after, between);
-        }
-
-        NamedList<Integer> counts = (NamedList<Integer>) values.get("counts");
-        for (Map.Entry<String, Integer> entry : counts)   {
-          rangeFacet.addCount(entry.getKey(), entry.getValue());
-        }
-
-        _facetRanges.add(rangeFacet);
-      }
+      _facetRanges = extractRangeFacets(rf);
     }
     
     //Parse pivot facets
@@ -408,7 +374,47 @@ public class QueryResponse extends SolrR
       }
     }
   }
-  
+
+  private List<RangeFacet> extractRangeFacets(NamedList<NamedList<Object>> rf) {
+    List<RangeFacet> facetRanges = new ArrayList<>( rf.size() );
+
+    for (Map.Entry<String, NamedList<Object>> facet : rf) {
+      NamedList<Object> values = facet.getValue();
+      Object rawGap = values.get("gap");
+
+      RangeFacet rangeFacet;
+      if (rawGap instanceof Number) {
+        Number gap = (Number) rawGap;
+        Number start = (Number) values.get("start");
+        Number end = (Number) values.get("end");
+
+        Number before = (Number) values.get("before");
+        Number after = (Number) values.get("after");
+        Number between = (Number) values.get("between");
+
+        rangeFacet = new RangeFacet.Numeric(facet.getKey(), start, end, gap, before, after, between);
+      } else {
+        String gap = (String) rawGap;
+        Date start = (Date) values.get("start");
+        Date end = (Date) values.get("end");
+
+        Number before = (Number) values.get("before");
+        Number after = (Number) values.get("after");
+        Number between = (Number) values.get("between");
+
+        rangeFacet = new RangeFacet.Date(facet.getKey(), start, end, gap, before, after, between);
+      }
+
+      NamedList<Integer> counts = (NamedList<Integer>) values.get("counts");
+      for (Map.Entry<String, Integer> entry : counts)   {
+        rangeFacet.addCount(entry.getKey(), entry.getValue());
+      }
+
+      facetRanges.add(rangeFacet);
+    }
+    return facetRanges;
+  }
+
   protected List<PivotField> readPivots( List<NamedList> list )
   {
     ArrayList<PivotField> values = new ArrayList<>( list.size() );
@@ -423,6 +429,8 @@ public class QueryResponse extends SolrR
 
       List<PivotField> subPivots = null;
       Map<String,FieldStatsInfo> fieldStatsInfos = null;
+      Map<String,Integer> queryCounts = null;
+      List<RangeFacet> ranges = null;
 
       if (4 <= nl.size()) {
         for(int index = 3; index < nl.size(); index++) {
@@ -444,6 +452,21 @@ public class QueryResponse extends SolrR
             fieldStatsInfos = extractFieldStatsInfo((NamedList<Object>) val);
             break;
           }
+          case "queries": {
+            // Parse the queries
+            queryCounts = new LinkedHashMap<>();
+            NamedList<Integer> fq = (NamedList<Integer>) val;
+            if (fq != null) {
+              for( Map.Entry<String, Integer> entry : fq ) {
+                queryCounts.put( entry.getKey(), entry.getValue() );
+              }
+            }
+            break;
+          }
+          case "ranges": {
+            ranges  = extractRangeFacets((NamedList<NamedList<Object>>) val);
+            break;
+          }
           default: 
             throw new RuntimeException( "unknown key in pivot: "+ key+ " ["+val+"]");
 
@@ -451,7 +474,7 @@ public class QueryResponse extends SolrR
         }
       }
 
-      values.add( new PivotField( f, v, cnt, subPivots, fieldStatsInfos ) );
+      values.add( new PivotField( f, v, cnt, subPivots, fieldStatsInfos, queryCounts, ranges ) );
     }
     return values;
   }

Modified: lucene/dev/branches/branch_5x/solr/solrj/src/test/org/apache/solr/client/solrj/SolrExampleTests.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_5x/solr/solrj/src/test/org/apache/solr/client/solrj/SolrExampleTests.java?rev=1689839&r1=1689838&r2=1689839&view=diff
==============================================================================
--- lucene/dev/branches/branch_5x/solr/solrj/src/test/org/apache/solr/client/solrj/SolrExampleTests.java (original)
+++ lucene/dev/branches/branch_5x/solr/solrj/src/test/org/apache/solr/client/solrj/SolrExampleTests.java Wed Jul  8 11:05:27 2015
@@ -42,7 +42,9 @@ import org.apache.solr.client.solrj.resp
 import org.apache.solr.client.solrj.response.LukeResponse;
 import org.apache.solr.client.solrj.response.PivotField;
 import org.apache.solr.client.solrj.response.QueryResponse;
+import org.apache.solr.client.solrj.response.RangeFacet;
 import org.apache.solr.client.solrj.response.UpdateResponse;
+import org.apache.solr.client.solrj.response.RangeFacet.Count;
 import org.apache.solr.common.SolrDocument;
 import org.apache.solr.common.SolrDocumentList;
 import org.apache.solr.common.SolrException;
@@ -65,6 +67,7 @@ import java.util.Arrays;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Random;
 
@@ -1094,6 +1097,218 @@ abstract public class SolrExampleTests e
 
   }
 
+  @Test
+  public void testPivotFacetsQueries() throws Exception {
+    SolrClient client = getSolrClient();
+
+    // Empty the database...
+    client.deleteByQuery("*:*");// delete everything!
+    client.commit();
+    assertNumFound("*:*", 0); // make sure it got in
+
+    int id = 1;
+    ArrayList<SolrInputDocument> docs = new ArrayList<>();
+    docs.add(makeTestDoc("id", id++, "features", "aaa", "cat", "a", "inStock", true, "popularity", 12, "price", .017));
+    docs.add(makeTestDoc("id", id++, "features", "aaa", "cat", "a", "inStock", false, "popularity", 13, "price", 16.04));
+    docs.add(makeTestDoc("id", id++, "features", "aaa", "cat", "a", "inStock", true, "popularity", 14, "price", 12.34));
+    docs.add(makeTestDoc("id", id++, "features", "aaa", "cat", "b", "inStock", false, "popularity", 24, "price", 51.39));
+    docs.add(makeTestDoc("id", id++, "features", "aaa", "cat", "b", "inStock", true, "popularity", 28, "price", 131.39));
+    docs.add(makeTestDoc("id", id++, "features", "bbb", "cat", "a", "inStock", false, "popularity", 32));
+    docs.add(makeTestDoc("id", id++, "features", "bbb", "cat", "a", "inStock", true, "popularity", 31, "price", 131.39));
+    docs.add(makeTestDoc("id", id++, "features", "bbb", "cat", "b", "inStock", false, "popularity", 36));
+    docs.add(makeTestDoc("id", id++, "features", "bbb", "cat", "b", "inStock", true, "popularity", 37, "price", 1.39));
+    docs.add(makeTestDoc("id", id++, "features", "bbb", "cat", "b", "inStock", false, "popularity", 38, "price", 47.98));
+    docs.add(makeTestDoc("id", id++, "features", "bbb", "cat", "b", "inStock", true, "popularity", -38));
+    docs.add(makeTestDoc("id", id++, "cat", "b")); // something not matching all fields
+    client.add(docs);
+    client.commit();
+
+    SolrQuery query = new SolrQuery("*:*");
+    query.addFacetPivotField("{!query=s1}features,manu");
+    query.addFacetQuery("{!key=highPrice tag=s1}price:[100 TO *]");
+    query.addFacetQuery("{!tag=s1 key=lowPrice}price:[0 TO 50]");
+    query.setFacetMinCount(0);
+    query.setRows(0);
+    QueryResponse rsp = client.query(query);
+
+    Map<String,Integer> map = rsp.getFacetQuery();
+    assertEquals(2, map.get("highPrice").intValue());
+    assertEquals(5, map.get("lowPrice").intValue());
+    
+    NamedList<List<PivotField>> pivots = rsp.getFacetPivot();
+    List<PivotField> pivotValues = pivots.get("features,manu");
+
+    PivotField featuresBBBPivot = pivotValues.get(0);
+    assertEquals("features", featuresBBBPivot.getField());
+    assertEquals("bbb", featuresBBBPivot.getValue());
+    assertNotNull(featuresBBBPivot.getFacetQuery());
+    assertEquals(2, featuresBBBPivot.getFacetQuery().size());
+    assertEquals(1, featuresBBBPivot.getFacetQuery().get("highPrice").intValue());
+    assertEquals(2, featuresBBBPivot.getFacetQuery().get("lowPrice").intValue());
+    
+    PivotField featuresAAAPivot = pivotValues.get(1);
+    assertEquals("features", featuresAAAPivot.getField());
+    assertEquals("aaa", featuresAAAPivot.getValue());
+    assertNotNull(featuresAAAPivot.getFacetQuery());
+    assertEquals(2, featuresAAAPivot.getFacetQuery().size());
+    assertEquals(1, featuresAAAPivot.getFacetQuery().get("highPrice").intValue());
+    assertEquals(3, featuresAAAPivot.getFacetQuery().get("lowPrice").intValue());
+  }
+
+  @Test
+  public void testPivotFacetsRanges() throws Exception {
+    SolrClient client = getSolrClient();
+
+    // Empty the database...
+    client.deleteByQuery("*:*");// delete everything!
+    client.commit();
+    assertNumFound("*:*", 0); // make sure it got in
+
+    int id = 1;
+    ArrayList<SolrInputDocument> docs = new ArrayList<>();
+    docs.add(makeTestDoc("id", id++, "features", "aaa", "cat", "a", "inStock", true, "popularity", 12, "price", .017));
+    docs.add(makeTestDoc("id", id++, "features", "aaa", "cat", "a", "inStock", false, "popularity", 13, "price", 16.04));
+    docs.add(makeTestDoc("id", id++, "features", "aaa", "cat", "a", "inStock", true, "popularity", 14, "price", 12.34));
+    docs.add(makeTestDoc("id", id++, "features", "aaa", "cat", "b", "inStock", false, "popularity", 24, "price", 51.39));
+    docs.add(makeTestDoc("id", id++, "features", "aaa", "cat", "b", "inStock", true, "popularity", 28, "price", 131.39));
+    docs.add(makeTestDoc("id", id++, "features", "bbb", "cat", "a", "inStock", false, "popularity", 32));
+    docs.add(makeTestDoc("id", id++, "features", "bbb", "cat", "a", "inStock", true, "popularity", 31, "price", 131.39));
+    docs.add(makeTestDoc("id", id++, "features", "bbb", "cat", "b", "inStock", false, "popularity", 36));
+    docs.add(makeTestDoc("id", id++, "features", "bbb", "cat", "b", "inStock", true, "popularity", 37, "price", 1.39));
+    docs.add(makeTestDoc("id", id++, "features", "bbb", "cat", "b", "inStock", false, "popularity", 38, "price", 47.98));
+    docs.add(makeTestDoc("id", id++, "features", "bbb", "cat", "b", "inStock", true, "popularity", -38));
+    docs.add(makeTestDoc("id", id++, "cat", "b")); // something not matching all fields
+    client.add(docs);
+    client.commit();
+
+    SolrQuery query = new SolrQuery("*:*");
+    query.addFacetPivotField("{!range=s1}features,manu");
+    query.add(FacetParams.FACET_RANGE, "{!key=price1 tag=s1}price");
+    query.add(String.format(Locale.ROOT, "f.%s.%s", "price", FacetParams.FACET_RANGE_START), "0");
+    query.add(String.format(Locale.ROOT, "f.%s.%s", "price", FacetParams.FACET_RANGE_END), "200");
+    query.add(String.format(Locale.ROOT, "f.%s.%s", "price", FacetParams.FACET_RANGE_GAP), "50");
+    query.set(FacetParams.FACET, true);
+    query.add(FacetParams.FACET_RANGE, "{!key=price2 tag=s1}price");
+    query.setFacetMinCount(0);
+    query.setRows(0);
+    QueryResponse rsp = client.query(query);
+
+    List<RangeFacet> list = rsp.getFacetRanges();
+    assertEquals(2, list.size());
+    @SuppressWarnings("unchecked")
+    RangeFacet<Float, Float> range1 = list.get(0);
+    assertEquals("price1", range1.getName());
+    assertEquals(0, range1.getStart().intValue());
+    assertEquals(200, range1.getEnd().intValue());
+    assertEquals(50, range1.getGap().intValue());
+    List<Count> counts1 = range1.getCounts();
+    assertEquals(4, counts1.size());
+    assertEquals(5, counts1.get(0).getCount());
+    assertEquals("0.0", counts1.get(0).getValue());
+    assertEquals(1, counts1.get(1).getCount());
+    assertEquals("50.0", counts1.get(1).getValue());
+    assertEquals(2, counts1.get(2).getCount());
+    assertEquals("100.0", counts1.get(2).getValue());
+    assertEquals(0, counts1.get(3).getCount());
+    assertEquals("150.0", counts1.get(3).getValue());
+    @SuppressWarnings("unchecked")
+    RangeFacet<Float, Float> range2 = list.get(1);
+    assertEquals("price2", range2.getName());
+    assertEquals(0, range2.getStart().intValue());
+    assertEquals(200, range2.getEnd().intValue());
+    assertEquals(50, range2.getGap().intValue());
+    List<Count> counts2 = range2.getCounts();
+    assertEquals(4, counts2.size());
+    assertEquals(5, counts2.get(0).getCount());
+    assertEquals("0.0", counts2.get(0).getValue());
+    assertEquals(1, counts2.get(1).getCount());
+    assertEquals("50.0", counts2.get(1).getValue());
+    assertEquals(2, counts2.get(2).getCount());
+    assertEquals("100.0", counts2.get(2).getValue());
+    assertEquals(0, counts2.get(3).getCount());
+    assertEquals("150.0", counts2.get(3).getValue());
+    
+    NamedList<List<PivotField>> pivots = rsp.getFacetPivot();
+    List<PivotField> pivotValues = pivots.get("features,manu");
+
+    PivotField featuresBBBPivot = pivotValues.get(0);
+    assertEquals("features", featuresBBBPivot.getField());
+    assertEquals("bbb", featuresBBBPivot.getValue());
+    List<RangeFacet> featuresBBBRanges = featuresBBBPivot.getFacetRanges();
+
+    for (RangeFacet range : featuresBBBRanges) {
+      if (range.getName().equals("price1")) {
+        assertNotNull(range);
+        assertEquals(0, ((Float)range.getStart()).intValue());
+        assertEquals(200, ((Float)range.getEnd()).intValue());
+        assertEquals(50, ((Float)range.getGap()).intValue());
+        List<Count> counts = range.getCounts();
+        assertEquals(4, counts.size());
+        for (Count count : counts) {
+          switch (count.getValue()) {
+            case "0.0": assertEquals(2, count.getCount()); break;
+            case "50.0": assertEquals(0, count.getCount()); break;
+            case "100.0": assertEquals(1, count.getCount()); break;
+            case "150.0": assertEquals(0, count.getCount()); break;
+          }
+        }
+      } else if (range.getName().equals("price2"))  {
+        assertNotNull(range);
+        assertEquals(0, ((Float) range.getStart()).intValue());
+        assertEquals(200, ((Float) range.getEnd()).intValue());
+        assertEquals(50, ((Float) range.getGap()).intValue());
+        List<Count> counts = range.getCounts();
+        assertEquals(4, counts.size());
+        for (Count count : counts) {
+          switch (count.getValue()) {
+            case "0.0": assertEquals(2, count.getCount()); break;
+            case "50.0": assertEquals(0, count.getCount()); break;
+            case "100.0": assertEquals(1, count.getCount()); break;
+            case "150.0": assertEquals(0, count.getCount()); break;
+          }
+        }
+      }
+    }
+
+    PivotField featuresAAAPivot = pivotValues.get(1);
+    assertEquals("features", featuresAAAPivot.getField());
+    assertEquals("aaa", featuresAAAPivot.getValue());
+    List<RangeFacet> facetRanges = featuresAAAPivot.getFacetRanges();
+    for (RangeFacet range : facetRanges) {
+      if (range.getName().equals("price1")) {
+        assertNotNull(range);
+        assertEquals(0, ((Float)range.getStart()).intValue());
+        assertEquals(200, ((Float)range.getEnd()).intValue());
+        assertEquals(50, ((Float)range.getGap()).intValue());
+        List<Count> counts = range.getCounts();
+        assertEquals(4, counts.size());
+        for (Count count : counts) {
+          switch (count.getValue()) {
+            case "0.0": assertEquals(3, count.getCount()); break;
+            case "50.0": assertEquals(1, count.getCount()); break;
+            case "100.0": assertEquals(1, count.getCount()); break;
+            case "150.0": assertEquals(0, count.getCount()); break;
+          }
+        }
+      } else if (range.getName().equals("price2"))  {
+        assertNotNull(range);
+        assertEquals(0, ((Float)range.getStart()).intValue());
+        assertEquals(200, ((Float)range.getEnd()).intValue());
+        assertEquals(50, ((Float)range.getGap()).intValue());
+        List<Count> counts = range.getCounts();
+        assertEquals(4, counts.size());
+        for (Count count : counts) {
+          switch (count.getValue()) {
+            case "0.0": assertEquals(3, count.getCount()); break;
+            case "50.0": assertEquals(1, count.getCount()); break;
+            case "100.0": assertEquals(1, count.getCount()); break;
+            case "150.0": assertEquals(0, count.getCount()); break;
+          }
+        }
+      }
+    }
+  }
+
   public void testPivotFacetsMissing() throws Exception {
     doPivotFacetTest(true);
   }
@@ -1870,4 +2085,4 @@ abstract public class SolrExampleTests e
     }
     return sdoc;
   }
-}
\ No newline at end of file
+}