You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by ho...@apache.org on 2014/11/05 00:09:42 UTC

svn commit: r1636772 [2/2] - in /lucene/dev/trunk/solr: ./ core/src/java/org/apache/solr/handler/component/ core/src/java/org/apache/solr/util/ core/src/test/org/apache/solr/cloud/ core/src/test/org/apache/solr/handler/component/ solrj/src/java/org/apa...

Added: lucene/dev/trunk/solr/core/src/test/org/apache/solr/handler/component/DistributedFacetPivotWhiteBoxTest.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/test/org/apache/solr/handler/component/DistributedFacetPivotWhiteBoxTest.java?rev=1636772&view=auto
==============================================================================
--- lucene/dev/trunk/solr/core/src/test/org/apache/solr/handler/component/DistributedFacetPivotWhiteBoxTest.java (added)
+++ lucene/dev/trunk/solr/core/src/test/org/apache/solr/handler/component/DistributedFacetPivotWhiteBoxTest.java Tue Nov  4 23:09:41 2014
@@ -0,0 +1,138 @@
+package org.apache.solr.handler.component;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import org.apache.solr.BaseDistributedSearchTestCase;
+import org.apache.solr.client.solrj.response.PivotField;
+import org.apache.solr.client.solrj.response.QueryResponse;
+import org.apache.solr.common.params.ModifiableSolrParams;
+import org.apache.solr.common.params.SolrParams;
+
+import java.util.List;
+
+public class DistributedFacetPivotWhiteBoxTest extends BaseDistributedSearchTestCase {
+
+  public DistributedFacetPivotWhiteBoxTest() {
+    this.fixShardCount = true;
+    this.shardCount = 4;
+  }
+
+  @Override
+  public void doTest() throws Exception {
+
+    del("*:*");
+
+    // NOTE: we use the literal (4 character) string "null" as a company name
+    // to help ensure there isn't any bugs where the literal string is treated as if it 
+    // were a true NULL value.
+    index(id, 19, "place_t", "cardiff dublin", "company_t", "microsoft polecat", "price_ti", "15");
+    index(id, 20, "place_t", "dublin", "company_t", "polecat microsoft null", "price_ti", "19",
+        // this is the only doc to have solo_* fields, therefore only 1 shard has them
+        // TODO: add enum field - blocked by SOLR-6682
+        "solo_i", 42, "solo_s", "lonely", "solo_dt", "1976-03-06T01:23:45Z");
+    index(id, 21, "place_t", "krakow london la dublin", "company_t",
+        "microsoft fujitsu null polecat", "price_ti", "29");
+    index(id, 22, "place_t", "krakow london cardiff", "company_t",
+        "polecat null bbc", "price_ti", "39");
+    index(id, 23, "place_t", "krakow london", "company_t", "", "price_ti", "29");
+    index(id, 24, "place_t", "krakow la", "company_t", "");
+    index(id, 25, "company_t", "microsoft polecat null fujitsu null bbc", "price_ti", "59");
+    index(id, 26, "place_t", "krakow", "company_t", "null");
+    index(id, 27, "place_t", "krakow cardiff dublin london la",
+        "company_t", "null microsoft polecat bbc fujitsu");
+    index(id, 28, "place_t", "krakow cork", "company_t", "fujitsu rte");
+    commit();
+
+    handle.clear();
+    handle.put("QTime", SKIPVAL);
+    handle.put("timestamp", SKIPVAL);
+    handle.put("maxScore", SKIPVAL);
+
+    doShardTestTopStats();
+    doTestRefinementRequest();
+  }
+
+  /** 
+   * recreates the initial request to a shard in a distributed query
+   * confirming that both top level stats, and per-pivot stats are returned.
+   */
+  private void doShardTestTopStats() throws Exception {
+
+    SolrParams params = params("facet", "true", 
+                               "q", "*:*", 
+                               // "wt", "javabin", 
+                               "facet.pivot", "{!stats=s1}place_t,company_t", 
+                               // "version", "2", 
+                               "start", "0", "rows", "0", 
+                               "fsv", "true", 
+                               "fl", "id,score",
+                               "stats", "true", 
+                               "stats.field", "{!key=avg_price tag=s1}price_ti",
+                               "f.place_t.facet.limit", "160", 
+                               "f.place_t.facet.pivot.mincount", "0", 
+                               "f.company_t.facet.limit", "160", 
+                               "f.company_t.facet.pivot.mincount", "0", 
+                               "isShard", "true", "distrib", "false");
+    QueryResponse rsp = queryServer(new ModifiableSolrParams(params));
+
+    assertNotNull("initial shard request should include non-null top level stats", 
+                  rsp.getFieldStatsInfo());
+    assertFalse("initial shard request should include top level stats", 
+                rsp.getFieldStatsInfo().isEmpty());
+
+    List<PivotField> placePivots = rsp.getFacetPivot().get("place_t,company_t");
+    for (PivotField pivotField : placePivots) {
+      assertFalse("pivot stats should not be empty in initial request",
+                  pivotField.getFieldStatsInfo().isEmpty());
+    }
+  }
+
+  /** 
+   * recreates a pivot refinement request to a shard in a distributed query
+   * confirming that the per-pivot stats are returned, but not the top level stats
+   * because they shouldn't be overcounted.
+   */
+  private void doTestRefinementRequest() throws Exception {
+    SolrParams params = params("facet.missing", "true",
+                               "facet", "true", 
+                               "facet.limit", "4", 
+                               "distrib", "false", 
+                               // "wt", "javabin",
+                               // "version", "2", 
+                               "rows", "0", 
+                               "facet.sort", "index",
+                               "fpt0", "~krakow",
+                               "facet.pivot.mincount", "-1", 
+                               "isShard", "true", 
+                               "facet.pivot", "{!fpt=0 stats=st1}place_t,company_t",
+                               "stats", "false", 
+                               "stats.field", "{!key=sk1 tag=st1,st2}price_ti");
+    QueryResponse rsp = clients.get(0).query(new ModifiableSolrParams(params));
+
+    assertNull("pivot refine request should *NOT* include top level stats", 
+               rsp.getFieldStatsInfo());
+
+    List<PivotField> placePivots = rsp.getFacetPivot().get("place_t,company_t");
+
+    assertEquals("asked to refine exactly one place",
+                 1, placePivots.size());
+    assertFalse("pivot stats should not be empty in refinement request",
+                placePivots.get(0).getFieldStatsInfo().isEmpty());
+
+  }
+}

Added: lucene/dev/trunk/solr/core/src/test/org/apache/solr/handler/component/FacetPivotSmallTest.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/test/org/apache/solr/handler/component/FacetPivotSmallTest.java?rev=1636772&view=auto
==============================================================================
--- lucene/dev/trunk/solr/core/src/test/org/apache/solr/handler/component/FacetPivotSmallTest.java (added)
+++ lucene/dev/trunk/solr/core/src/test/org/apache/solr/handler/component/FacetPivotSmallTest.java Tue Nov  4 23:09:41 2014
@@ -0,0 +1,504 @@
+package org.apache.solr.handler.component;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.common.params.FacetParams;
+import org.apache.solr.common.params.ModifiableSolrParams;
+import org.apache.solr.common.params.SolrParams;
+import org.apache.solr.request.SolrQueryRequest;
+import org.junit.BeforeClass;
+
+/**
+ * Single node testing of pivot facets
+ */
+public class FacetPivotSmallTest extends SolrTestCaseJ4 {
+
+  @BeforeClass
+  public static void beforeClass() throws Exception {
+    initCore("solrconfig.xml", "schema11.xml");
+  }
+
+  @Override
+  public void setUp() throws Exception {
+    super.setUp();
+    clearIndex();
+    assertU(commit());
+    lrf = h.getRequestFactory("standard", 0, 20);
+  }
+
+  /**
+   * we don't support comma's in the "stats" local param ... yet: SOLR-6663  
+   */
+  public void testStatsTagHasComma() throws Exception {
+
+    if (random().nextBoolean()) {
+      // behavior should be same either way
+      index();
+    }
+
+    assertQEx("Can't use multiple tags in stats local param until SOLR-6663 is decided",
+              req("q","*:*", "facet", "true",
+                  "stats", "true",
+                  "stats.field", "{!tag=foo}price_ti",
+                  "stats.field", "{!tag=bar}id",
+                  "facet.pivot", "{!stats=foo,bar}place_t,company_t"),
+              400);
+  }
+
+  /**
+   * if bogus stats are requested, the pivots should still work
+   */
+  public void testBogusStatsTag() throws Exception {
+    index();
+
+    assertQ(req("q","*:*", "facet", "true",
+                "facet.pivot", "{!stats=bogus}place_t,company_t")
+            // check we still get pivots...
+            , "//arr[@name='place_t,company_t']/lst[str[@name='value'][.='dublin']]"
+            // .. but sanity check we don't have any stats
+            , "count(//arr[@name='place_t,company_t']/lst[str[@name='value'][.='dublin']]/lst[@name='stats'])=0");
+  }
+
+  public void testPivotFacetUnsorted() throws Exception {
+    index();
+
+    final ModifiableSolrParams params = new ModifiableSolrParams();
+    params.add("q", "*:*");
+    params.add("facet", "true");
+    params.add("facet.pivot", "place_t,company_t");
+
+    SolrQueryRequest req = req(params);
+    final String facetPivotPrefix = "//lst[@name='facet_counts']/lst[@name='facet_pivot']/arr[@name='place_t,company_t']/lst";
+    assertQ(req, facetPivotPrefix + "/str[@name='field'][.='place_t']",
+        // dublin
+        facetPivotPrefix + "[str[@name='value'][.='dublin']]/arr[@name='pivot']/lst[1]/str[@name='value'][.='microsoft']",
+        facetPivotPrefix + "[str[@name='value'][.='dublin']]/arr[@name='pivot']/lst[1]/int[@name='count'][.=4]",
+        facetPivotPrefix + "[str[@name='value'][.='dublin']]/arr[@name='pivot']/lst[2]/str[@name='value'][.='polecat']",
+        facetPivotPrefix + "[str[@name='value'][.='dublin']]/arr[@name='pivot']/lst[2]/int[@name='count'][.=4]",
+        facetPivotPrefix + "[str[@name='value'][.='dublin']]/arr[@name='pivot']/lst[3]/str[@name='value'][.='null']",
+        facetPivotPrefix + "[str[@name='value'][.='dublin']]/arr[@name='pivot']/lst[3]/int[@name='count'][.=3]",
+        facetPivotPrefix + "[str[@name='value'][.='dublin']]/arr[@name='pivot']/lst[4]/str[@name='value'][.='fujitsu']",
+        facetPivotPrefix + "[str[@name='value'][.='dublin']]/arr[@name='pivot']/lst[4]/int[@name='count'][.=2]",
+        facetPivotPrefix + "[str[@name='value'][.='dublin']]/arr[@name='pivot']/lst[5]/str[@name='value'][.='bbc']",
+        facetPivotPrefix + "[str[@name='value'][.='dublin']]/arr[@name='pivot']/lst[5]/int[@name='count'][.=1]",
+        // london
+        facetPivotPrefix + "[str[@name='value'][.='london']]/arr[@name='pivot']/lst[1]/str[@name='value'][.='null']",
+        facetPivotPrefix + "[str[@name='value'][.='london']]/arr[@name='pivot']/lst[1]/int[@name='count'][.=3]",
+        facetPivotPrefix + "[str[@name='value'][.='london']]/arr[@name='pivot']/lst[2]/str[@name='value'][.='polecat']",
+        facetPivotPrefix + "[str[@name='value'][.='london']]/arr[@name='pivot']/lst[2]/int[@name='count'][.=3]",
+        facetPivotPrefix + "[str[@name='value'][.='london']]/arr[@name='pivot']/lst[3]/str[@name='value'][.='bbc']",
+        facetPivotPrefix + "[str[@name='value'][.='london']]/arr[@name='pivot']/lst[3]/int[@name='count'][.=2]",
+        facetPivotPrefix + "[str[@name='value'][.='london']]/arr[@name='pivot']/lst[4]/str[@name='value'][.='fujitsu']",
+        facetPivotPrefix + "[str[@name='value'][.='london']]/arr[@name='pivot']/lst[4]/int[@name='count'][.=2]",
+        facetPivotPrefix + "[str[@name='value'][.='london']]/arr[@name='pivot']/lst[5]/str[@name='value'][.='microsoft']",
+        facetPivotPrefix + "[str[@name='value'][.='london']]/arr[@name='pivot']/lst[5]/int[@name='count'][.=2]",
+        // cardiff
+        facetPivotPrefix + "[str[@name='value'][.='cardiff']]/arr[@name='pivot']/lst[1]/str[@name='value'][.='polecat']",
+        facetPivotPrefix + "[str[@name='value'][.='cardiff']]/arr[@name='pivot']/lst[1]/int[@name='count'][.=3]",
+        facetPivotPrefix + "[str[@name='value'][.='cardiff']]/arr[@name='pivot']/lst[2]/str[@name='value'][.='bbc']",
+        facetPivotPrefix + "[str[@name='value'][.='cardiff']]/arr[@name='pivot']/lst[2]/int[@name='count'][.=2]",
+        facetPivotPrefix + "[str[@name='value'][.='cardiff']]/arr[@name='pivot']/lst[3]/str[@name='value'][.='microsoft']",
+        facetPivotPrefix + "[str[@name='value'][.='cardiff']]/arr[@name='pivot']/lst[3]/int[@name='count'][.=2]",
+        facetPivotPrefix + "[str[@name='value'][.='cardiff']]/arr[@name='pivot']/lst[4]/str[@name='value'][.='null']",
+        facetPivotPrefix + "[str[@name='value'][.='cardiff']]/arr[@name='pivot']/lst[4]/int[@name='count'][.=2]",
+        facetPivotPrefix + "[str[@name='value'][.='cardiff']]/arr[@name='pivot']/lst[5]/str[@name='value'][.='fujitsu']",
+        facetPivotPrefix + "[str[@name='value'][.='cardiff']]/arr[@name='pivot']/lst[5]/int[@name='count'][.=1]",
+        // krakow
+        facetPivotPrefix + "[str[@name='value'][.='krakow']]/arr[@name='pivot']/lst[1]/str[@name='value'][.='null']",
+        facetPivotPrefix + "[str[@name='value'][.='krakow']]/arr[@name='pivot']/lst[1]/int[@name='count'][.=3]",
+        facetPivotPrefix + "[str[@name='value'][.='krakow']]/arr[@name='pivot']/lst[2]/str[@name='value'][.='bbc']",
+        facetPivotPrefix + "[str[@name='value'][.='krakow']]/arr[@name='pivot']/lst[2]/int[@name='count'][.=2]",
+        facetPivotPrefix + "[str[@name='value'][.='krakow']]/arr[@name='pivot']/lst[3]/str[@name='value'][.='polecat']",
+        facetPivotPrefix + "[str[@name='value'][.='krakow']]/arr[@name='pivot']/lst[3]/int[@name='count'][.=2]",
+        facetPivotPrefix + "[str[@name='value'][.='krakow']]/arr[@name='pivot']/lst[4]/str[@name='value'][.='fujitsu']",
+        facetPivotPrefix + "[str[@name='value'][.='krakow']]/arr[@name='pivot']/lst[4]/int[@name='count'][.=1]",
+        facetPivotPrefix + "[str[@name='value'][.='krakow']]/arr[@name='pivot']/lst[5]/str[@name='value'][.='microsoft']",
+        facetPivotPrefix + "[str[@name='value'][.='krakow']]/arr[@name='pivot']/lst[5]/int[@name='count'][.=1]",
+
+        // la
+        facetPivotPrefix + "[str[@name='value'][.='la']]/arr[@name='pivot']/lst[1]/str[@name='value'][.='fujitsu']",
+        facetPivotPrefix + "[str[@name='value'][.='la']]/arr[@name='pivot']/lst[1]/int[@name='count'][.=2]",
+        facetPivotPrefix + "[str[@name='value'][.='la']]/arr[@name='pivot']/lst[2]/str[@name='value'][.='microsoft']",
+        facetPivotPrefix + "[str[@name='value'][.='la']]/arr[@name='pivot']/lst[2]/int[@name='count'][.=2]",
+        facetPivotPrefix + "[str[@name='value'][.='la']]/arr[@name='pivot']/lst[3]/str[@name='value'][.='null']",
+        facetPivotPrefix + "[str[@name='value'][.='la']]/arr[@name='pivot']/lst[3]/int[@name='count'][.=2]",
+        facetPivotPrefix + "[str[@name='value'][.='la']]/arr[@name='pivot']/lst[4]/str[@name='value'][.='polecat']",
+        facetPivotPrefix + "[str[@name='value'][.='la']]/arr[@name='pivot']/lst[4]/int[@name='count'][.=2]",
+        facetPivotPrefix + "[str[@name='value'][.='la']]/arr[@name='pivot']/lst[5]/str[@name='value'][.='bbc']",
+        facetPivotPrefix + "[str[@name='value'][.='la']]/arr[@name='pivot']/lst[5]/int[@name='count'][.=1]",
+        // cork
+        facetPivotPrefix + "[str[@name='value'][.='cork']]/arr[@name='pivot']/lst[1]/str[@name='value'][.='fujitsu']",
+        facetPivotPrefix + "[str[@name='value'][.='cork']]/arr[@name='pivot']/lst[1]/int[@name='count'][.=1]",
+        facetPivotPrefix + "[str[@name='value'][.='cork']]/arr[@name='pivot']/lst[2]/str[@name='value'][.='rte']",
+        facetPivotPrefix + "[str[@name='value'][.='cork']]/arr[@name='pivot']/lst[2]/int[@name='count'][.=1]"
+    );
+  }
+
+  public void testPivotFacetStatsUnsortedTagged() throws Exception {
+    index();
+
+    final ModifiableSolrParams params = new ModifiableSolrParams();
+    params.add("q", "*:*");
+    params.add("facet", "true");
+    params.add("facet.pivot", "{!stats=s1}place_t,company_t");
+    params.add("stats", "true");
+    params.add("stats.field", "{!key=avg_price tag=s1 mean=true}price_ti");
+
+    SolrQueryRequest req = req(params);
+    final String statsPrefix = "//lst[@name='facet_counts']/lst[@name='facet_pivot']/arr[@name='place_t,company_t']/lst";
+    String dublinMicrosoftStats = statsPrefix + "[str[@name='value'][.='dublin']]/arr[@name='pivot']/lst[str[@name='value'][.='microsoft']]/lst[@name='stats']/lst[@name='stats_fields']/lst[@name='avg_price']";
+    String cardiffPolecatStats = statsPrefix + "[str[@name='value'][.='cardiff']]/arr[@name='pivot']/lst[str[@name='value'][.='polecat']]/lst[@name='stats']/lst[@name='stats_fields']/lst[@name='avg_price']";
+    String krakowFujitsuStats = statsPrefix + "[str[@name='value'][.='krakow']]/arr[@name='pivot']/lst[str[@name='value'][.='fujitsu']]/lst[@name='stats']/lst[@name='stats_fields']/lst[@name='avg_price']";
+    assertQ(req,
+        dublinMicrosoftStats + "/double[@name='min'][.=15.0]",
+        dublinMicrosoftStats + "/double[@name='max'][.=29.0]",
+        dublinMicrosoftStats + "/long[@name='count'][.=3]",
+        dublinMicrosoftStats + "/long[@name='missing'][.=1]",
+        dublinMicrosoftStats + "/double[@name='sum'][.=63.0]",
+        dublinMicrosoftStats + "/double[@name='sumOfSquares'][.=1427.0]",
+        dublinMicrosoftStats + "/double[@name='mean'][.=21.0]",
+        dublinMicrosoftStats + "/double[@name='stddev'][.=7.211102550927978]",
+
+        cardiffPolecatStats + "/double[@name='min'][.=15.0]",
+        cardiffPolecatStats + "/double[@name='max'][.=39.0]",
+        cardiffPolecatStats + "/long[@name='count'][.=2]",
+        cardiffPolecatStats + "/long[@name='missing'][.=1]",
+        cardiffPolecatStats + "/double[@name='sum'][.=54.0]",
+        cardiffPolecatStats + "/double[@name='sumOfSquares'][.=1746.0]",
+        cardiffPolecatStats + "/double[@name='mean'][.=27.0]",
+        cardiffPolecatStats + "/double[@name='stddev'][.=16.97056274847714]",
+
+        krakowFujitsuStats + "/null[@name='min']",
+        krakowFujitsuStats + "/null[@name='max']",
+        krakowFujitsuStats + "/long[@name='count'][.=0]",
+        krakowFujitsuStats + "/long[@name='missing'][.=1]",
+        krakowFujitsuStats + "/double[@name='sum'][.=0.0]",
+        krakowFujitsuStats + "/double[@name='sumOfSquares'][.=0.0]",
+        krakowFujitsuStats + "/double[@name='mean'][.='NaN']",
+        krakowFujitsuStats + "/double[@name='stddev'][.=0.0]"
+    );
+  }
+
+
+  public void testPivotFacetSortedCount() throws Exception {
+    index();
+
+    final ModifiableSolrParams params = new ModifiableSolrParams();
+    params.add("q", "*:*");
+    params.add("facet", "true");
+    params.add("facet.pivot", "place_t,company_t");
+
+    // Test sorting by count
+    //TODO clarify why facet count active by default
+    // The default is count if facet.limit is greater than 0, index otherwise, but facet.limit was not defined
+    params.set(FacetParams.FACET_SORT, FacetParams.FACET_SORT_COUNT);
+    final String facetPivotPrefix = "//lst[@name='facet_counts']/lst[@name='facet_pivot']/arr[@name='place_t,company_t']/lst";
+    SolrQueryRequest req = req(params);
+    assertQ(req, facetPivotPrefix + "/str[@name='field'][.='place_t']",
+        // dublin
+        facetPivotPrefix + "[str[@name='value'][.='dublin']]/arr[@name='pivot']/lst[1]/str[@name='value'][.='microsoft']",
+        facetPivotPrefix + "[str[@name='value'][.='dublin']]/arr[@name='pivot']/lst[1]/int[@name='count'][.=4]",
+        facetPivotPrefix + "[str[@name='value'][.='dublin']]/arr[@name='pivot']/lst[2]/str[@name='value'][.='polecat']",
+        facetPivotPrefix + "[str[@name='value'][.='dublin']]/arr[@name='pivot']/lst[2]/int[@name='count'][.=4]",
+        facetPivotPrefix + "[str[@name='value'][.='dublin']]/arr[@name='pivot']/lst[3]/str[@name='value'][.='null']",
+        facetPivotPrefix + "[str[@name='value'][.='dublin']]/arr[@name='pivot']/lst[3]/int[@name='count'][.=3]",
+        facetPivotPrefix + "[str[@name='value'][.='dublin']]/arr[@name='pivot']/lst[4]/str[@name='value'][.='fujitsu']",
+        facetPivotPrefix + "[str[@name='value'][.='dublin']]/arr[@name='pivot']/lst[4]/int[@name='count'][.=2]",
+        facetPivotPrefix + "[str[@name='value'][.='dublin']]/arr[@name='pivot']/lst[5]/str[@name='value'][.='bbc']",
+        facetPivotPrefix + "[str[@name='value'][.='dublin']]/arr[@name='pivot']/lst[5]/int[@name='count'][.=1]",
+        // london
+        facetPivotPrefix + "[str[@name='value'][.='london']]/arr[@name='pivot']/lst[1]/str[@name='value'][.='null']",
+        facetPivotPrefix + "[str[@name='value'][.='london']]/arr[@name='pivot']/lst[1]/int[@name='count'][.=3]",
+        facetPivotPrefix + "[str[@name='value'][.='london']]/arr[@name='pivot']/lst[2]/str[@name='value'][.='polecat']",
+        facetPivotPrefix + "[str[@name='value'][.='london']]/arr[@name='pivot']/lst[2]/int[@name='count'][.=3]",
+        facetPivotPrefix + "[str[@name='value'][.='london']]/arr[@name='pivot']/lst[3]/str[@name='value'][.='bbc']",
+        facetPivotPrefix + "[str[@name='value'][.='london']]/arr[@name='pivot']/lst[3]/int[@name='count'][.=2]",
+        facetPivotPrefix + "[str[@name='value'][.='london']]/arr[@name='pivot']/lst[4]/str[@name='value'][.='fujitsu']",
+        facetPivotPrefix + "[str[@name='value'][.='london']]/arr[@name='pivot']/lst[4]/int[@name='count'][.=2]",
+        facetPivotPrefix + "[str[@name='value'][.='london']]/arr[@name='pivot']/lst[5]/str[@name='value'][.='microsoft']",
+        facetPivotPrefix + "[str[@name='value'][.='london']]/arr[@name='pivot']/lst[5]/int[@name='count'][.=2]",
+        // cardiff
+        facetPivotPrefix + "[str[@name='value'][.='cardiff']]/arr[@name='pivot']/lst[1]/str[@name='value'][.='polecat']",
+        facetPivotPrefix + "[str[@name='value'][.='cardiff']]/arr[@name='pivot']/lst[1]/int[@name='count'][.=3]",
+        facetPivotPrefix + "[str[@name='value'][.='cardiff']]/arr[@name='pivot']/lst[2]/str[@name='value'][.='bbc']",
+        facetPivotPrefix + "[str[@name='value'][.='cardiff']]/arr[@name='pivot']/lst[2]/int[@name='count'][.=2]",
+        facetPivotPrefix + "[str[@name='value'][.='cardiff']]/arr[@name='pivot']/lst[3]/str[@name='value'][.='microsoft']",
+        facetPivotPrefix + "[str[@name='value'][.='cardiff']]/arr[@name='pivot']/lst[3]/int[@name='count'][.=2]",
+        facetPivotPrefix + "[str[@name='value'][.='cardiff']]/arr[@name='pivot']/lst[4]/str[@name='value'][.='null']",
+        facetPivotPrefix + "[str[@name='value'][.='cardiff']]/arr[@name='pivot']/lst[4]/int[@name='count'][.=2]",
+        facetPivotPrefix + "[str[@name='value'][.='cardiff']]/arr[@name='pivot']/lst[5]/str[@name='value'][.='fujitsu']",
+        facetPivotPrefix + "[str[@name='value'][.='cardiff']]/arr[@name='pivot']/lst[5]/int[@name='count'][.=1]",
+        // krakow
+        facetPivotPrefix + "[str[@name='value'][.='krakow']]/arr[@name='pivot']/lst[1]/str[@name='value'][.='null']",
+        facetPivotPrefix + "[str[@name='value'][.='krakow']]/arr[@name='pivot']/lst[1]/int[@name='count'][.=3]",
+        facetPivotPrefix + "[str[@name='value'][.='krakow']]/arr[@name='pivot']/lst[2]/str[@name='value'][.='bbc']",
+        facetPivotPrefix + "[str[@name='value'][.='krakow']]/arr[@name='pivot']/lst[2]/int[@name='count'][.=2]",
+        facetPivotPrefix + "[str[@name='value'][.='krakow']]/arr[@name='pivot']/lst[3]/str[@name='value'][.='polecat']",
+        facetPivotPrefix + "[str[@name='value'][.='krakow']]/arr[@name='pivot']/lst[3]/int[@name='count'][.=2]",
+        facetPivotPrefix + "[str[@name='value'][.='krakow']]/arr[@name='pivot']/lst[4]/str[@name='value'][.='fujitsu']",
+        facetPivotPrefix + "[str[@name='value'][.='krakow']]/arr[@name='pivot']/lst[4]/int[@name='count'][.=1]",
+        facetPivotPrefix + "[str[@name='value'][.='krakow']]/arr[@name='pivot']/lst[5]/str[@name='value'][.='microsoft']",
+        facetPivotPrefix + "[str[@name='value'][.='krakow']]/arr[@name='pivot']/lst[5]/int[@name='count'][.=1]",
+
+        // la
+        facetPivotPrefix + "[str[@name='value'][.='la']]/arr[@name='pivot']/lst[1]/str[@name='value'][.='fujitsu']",
+        facetPivotPrefix + "[str[@name='value'][.='la']]/arr[@name='pivot']/lst[1]/int[@name='count'][.=2]",
+        facetPivotPrefix + "[str[@name='value'][.='la']]/arr[@name='pivot']/lst[2]/str[@name='value'][.='microsoft']",
+        facetPivotPrefix + "[str[@name='value'][.='la']]/arr[@name='pivot']/lst[2]/int[@name='count'][.=2]",
+        facetPivotPrefix + "[str[@name='value'][.='la']]/arr[@name='pivot']/lst[3]/str[@name='value'][.='null']",
+        facetPivotPrefix + "[str[@name='value'][.='la']]/arr[@name='pivot']/lst[3]/int[@name='count'][.=2]",
+        facetPivotPrefix + "[str[@name='value'][.='la']]/arr[@name='pivot']/lst[4]/str[@name='value'][.='polecat']",
+        facetPivotPrefix + "[str[@name='value'][.='la']]/arr[@name='pivot']/lst[4]/int[@name='count'][.=2]",
+        facetPivotPrefix + "[str[@name='value'][.='la']]/arr[@name='pivot']/lst[5]/str[@name='value'][.='bbc']",
+        facetPivotPrefix + "[str[@name='value'][.='la']]/arr[@name='pivot']/lst[5]/int[@name='count'][.=1]",
+        // cork
+        facetPivotPrefix + "[str[@name='value'][.='cork']]/arr[@name='pivot']/lst[1]/str[@name='value'][.='fujitsu']",
+        facetPivotPrefix + "[str[@name='value'][.='cork']]/arr[@name='pivot']/lst[1]/int[@name='count'][.=1]",
+        facetPivotPrefix + "[str[@name='value'][.='cork']]/arr[@name='pivot']/lst[2]/str[@name='value'][.='rte']",
+        facetPivotPrefix + "[str[@name='value'][.='cork']]/arr[@name='pivot']/lst[2]/int[@name='count'][.=1]"
+    );
+
+
+  }
+
+
+  public void testPivotFacetLimit() throws Exception {
+    index();
+
+    final ModifiableSolrParams params = new ModifiableSolrParams();
+    params.add("q", "*:*");
+    params.add("facet", "true");
+    params.add("facet.pivot", "place_t,company_t");
+
+    params.set(FacetParams.FACET_SORT, FacetParams.FACET_SORT_COUNT);
+    params.set(FacetParams.FACET_LIMIT, 2);
+
+    final String facetPivotPrefix = "//lst[@name='facet_counts']/lst[@name='facet_pivot']/arr[@name='place_t,company_t']/lst";
+    SolrQueryRequest req = req(params);
+    assertQ(req, facetPivotPrefix + "/str[@name='field'][.='place_t']",
+        // dublin
+        facetPivotPrefix + "[str[@name='value'][.='dublin']]/arr[@name='pivot']/lst[1]/str[@name='value'][.='microsoft']",
+        facetPivotPrefix + "[str[@name='value'][.='dublin']]/arr[@name='pivot']/lst[1]/int[@name='count'][.=4]",
+        facetPivotPrefix + "[str[@name='value'][.='dublin']]/arr[@name='pivot']/lst[2]/str[@name='value'][.='polecat']",
+        facetPivotPrefix + "[str[@name='value'][.='dublin']]/arr[@name='pivot']/lst[2]/int[@name='count'][.=4]",
+        // london
+        facetPivotPrefix + "[str[@name='value'][.='london']]/arr[@name='pivot']/lst[1]/str[@name='value'][.='null']",
+        facetPivotPrefix + "[str[@name='value'][.='london']]/arr[@name='pivot']/lst[1]/int[@name='count'][.=3]",
+        facetPivotPrefix + "[str[@name='value'][.='london']]/arr[@name='pivot']/lst[2]/str[@name='value'][.='polecat']",
+        facetPivotPrefix + "[str[@name='value'][.='london']]/arr[@name='pivot']/lst[2]/int[@name='count'][.=3]"
+    );
+  }
+
+  public void testPivotIndividualFacetLimit() throws Exception {
+    index();
+
+    final ModifiableSolrParams params = new ModifiableSolrParams();
+    params.add("q", "*:*");
+    params.add("facet", "true");
+    params.add("facet.pivot", "place_t,company_t");
+
+    params.set(FacetParams.FACET_SORT, FacetParams.FACET_SORT_COUNT);
+    params.set("f.place_t." + FacetParams.FACET_LIMIT, 1);
+    params.set("f.company_t." + FacetParams.FACET_LIMIT, 4);
+
+    final String facetPivotPrefix = "//lst[@name='facet_counts']/lst[@name='facet_pivot']/arr[@name='place_t,company_t']/lst";
+    SolrQueryRequest req = req(params);
+    assertQ(req, facetPivotPrefix + "/str[@name='field'][.='place_t']",
+        // dublin
+        facetPivotPrefix + "[str[@name='value'][.='dublin']]/arr[@name='pivot']/lst[1]/str[@name='value'][.='microsoft']",
+        facetPivotPrefix + "[str[@name='value'][.='dublin']]/arr[@name='pivot']/lst[1]/int[@name='count'][.=4]",
+        facetPivotPrefix + "[str[@name='value'][.='dublin']]/arr[@name='pivot']/lst[2]/str[@name='value'][.='polecat']",
+        facetPivotPrefix + "[str[@name='value'][.='dublin']]/arr[@name='pivot']/lst[2]/int[@name='count'][.=4]",
+        facetPivotPrefix + "[str[@name='value'][.='dublin']]/arr[@name='pivot']/lst[3]/str[@name='value'][.='null']",
+        facetPivotPrefix + "[str[@name='value'][.='dublin']]/arr[@name='pivot']/lst[3]/int[@name='count'][.=3]",
+        facetPivotPrefix + "[str[@name='value'][.='dublin']]/arr[@name='pivot']/lst[4]/str[@name='value'][.='fujitsu']",
+        facetPivotPrefix + "[str[@name='value'][.='dublin']]/arr[@name='pivot']/lst[4]/int[@name='count'][.=2]"
+    );
+  }
+
+  public void testPivotFacetMissing() throws Exception {
+    // Test facet.missing=true with diff sorts
+    index();
+    indexMissing();
+
+    SolrParams missingA = params("q", "*:*",
+        "rows", "0",
+        "facet", "true",
+        "facet.pivot", "place_t,company_t",
+        // default facet.sort
+        FacetParams.FACET_MISSING, "true");
+
+    final String facetPivotPrefix = "//lst[@name='facet_counts']/lst[@name='facet_pivot']/arr[@name='place_t,company_t']/lst";
+    SolrQueryRequest req = req(missingA);
+    assertQ(req, facetPivotPrefix + "/arr[@name='pivot'][count(.) > 0]",   // not enough values for pivot
+        facetPivotPrefix + "[7]/null[@name='value'][.='']",   // not the missing place value
+        facetPivotPrefix + "[7]/int[@name='count'][.=2]",       // wrong missing place count
+        facetPivotPrefix + "[7]/arr[@name='pivot'][count(.) > 0]", // not enough sub-pivots for missing place
+        facetPivotPrefix + "[7]/arr[@name='pivot']/lst[6]/null[@name='value'][.='']", // not the missing company value
+        facetPivotPrefix + "[7]/arr[@name='pivot']/lst[6]/int[@name='count'][.=1]", // wrong missing company count
+        facetPivotPrefix + "[7]/arr[@name='pivot']/lst[6][not(arr[@name='pivot'])]" // company shouldn't have sub-pivots
+    );
+
+    SolrParams missingB = SolrParams.wrapDefaults(missingA,
+        params(FacetParams.FACET_LIMIT, "4",
+            "facet.sort", "index"));
+
+
+    req = req(missingB);
+    assertQ(req, facetPivotPrefix + "/arr[@name='pivot'][count(.) > 0]",   // not enough values for pivot
+        facetPivotPrefix + "[5]/null[@name='value'][.='']",   // not the missing place value
+        facetPivotPrefix + "[5]/int[@name='count'][.=2]",       // wrong missing place count
+        facetPivotPrefix + "[5]/arr[@name='pivot'][count(.) > 0]", // not enough sub-pivots for missing place
+        facetPivotPrefix + "[5]/arr[@name='pivot']/lst[5]/null[@name='value'][.='']", // not the missing company value
+        facetPivotPrefix + "[5]/arr[@name='pivot']/lst[5]/int[@name='count'][.=1]", // wrong missing company count
+        facetPivotPrefix + "[5]/arr[@name='pivot']/lst[5][not(arr[@name='pivot'])]" // company shouldn't have sub-pivots
+    );
+  }
+
+  public void testPivotFacetIndexSortMincountAndLimit() throws Exception {
+    // sort=index + mincount + limit
+    index();
+    indexMissing();
+
+    for (SolrParams variableParams : new SolrParams[]{
+        // we should get the same results regardless of overrequest
+        params(),
+        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);
+      final String facetPivotPrefix = "//lst[@name='facet_counts']/lst[@name='facet_pivot']/arr[@name='company_t']";
+      SolrQueryRequest req = req(p);
+      assertQ(req, facetPivotPrefix + "[count(./lst) = 4]",   // not enough values for pivot
+          facetPivotPrefix + "/lst[1]/str[@name='value'][.='fujitsu']",
+          facetPivotPrefix + "/lst[1]/int[@name='count'][.=4]",
+          facetPivotPrefix + "/lst[2]/str[@name='value'][.='microsoft']",
+          facetPivotPrefix + "/lst[2]/int[@name='count'][.=5]",
+          facetPivotPrefix + "/lst[3]/str[@name='value'][.='null']",
+          facetPivotPrefix + "/lst[3]/int[@name='count'][.=6]",
+          facetPivotPrefix + "/lst[4]/str[@name='value'][.='polecat']",
+          facetPivotPrefix + "/lst[4]/int[@name='count'][.=6]"
+      );
+    }
+  }
+
+  public void testPivotFacetIndexSortMincountLimitAndOffset() throws Exception {
+    // sort=index + mincount + limit + offset
+    index();
+    indexMissing();
+
+    for (SolrParams variableParams : new SolrParams[]{
+        // we should get the same results regardless of overrequest
+        params(),
+        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);
+      final String facetPivotPrefix = "//lst[@name='facet_counts']/lst[@name='facet_pivot']/arr[@name='company_t']";
+      SolrQueryRequest req = req(p);
+      assertQ(req, facetPivotPrefix + "[count(./lst) = 3]", // asked for 4, but not enough meet the mincount
+          facetPivotPrefix + "/lst[1]/str[@name='value'][.='microsoft']",
+          facetPivotPrefix + "/lst[1]/int[@name='count'][.=5]",
+          facetPivotPrefix + "/lst[2]/str[@name='value'][.='null']",
+          facetPivotPrefix + "/lst[2]/int[@name='count'][.=6]",
+          facetPivotPrefix + "/lst[3]/str[@name='value'][.='polecat']",
+          facetPivotPrefix + "/lst[3]/int[@name='count'][.=6]"
+      );
+    }
+  }
+
+
+  public void testPivotFacetIndexSortMincountLimitAndOffsetPermutations() throws Exception {
+    // sort=index + mincount + limit + offset (more permutations)
+    index();
+    indexMissing();
+
+    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);
+      final String facetPivotPrefix = "//lst[@name='facet_counts']/lst[@name='facet_pivot']/arr[@name='company_t']";
+      SolrQueryRequest req = req(p);
+      assertQ(req, facetPivotPrefix + "[count(./lst) = 1]", // asked for 4, but not enough meet the mincount
+          facetPivotPrefix + "/lst[1]/str[@name='value'][.='null']",
+          facetPivotPrefix + "/lst[1]/int[@name='count'][.=6]"
+      );
+    }
+  }
+
+  private void indexMissing() {
+    String[] missingDoc = {"id", "777"};
+    assertU(adoc(missingDoc));
+    assertU(commit());
+  }
+
+  private void index() {
+    // NOTE: we use the literal (4 character) string "null" as a company name
+    // to help ensure there isn't any bugs where the literal string is treated as if it
+    // were a true NULL value.
+    String[] doc = {"id", "19", "place_t", "cardiff dublin", "company_t", "microsoft polecat", "price_ti", "15"};
+    assertU(adoc(doc));
+    String[] doc1 = {"id", "20", "place_t", "dublin", "company_t", "polecat microsoft null", "price_ti", "19"};
+    assertU(adoc(doc1));
+    String[] doc2 = {"id", "21", "place_t", "london la dublin", "company_t",
+        "microsoft fujitsu null polecat", "price_ti", "29"};
+    assertU(adoc(doc2));
+    String[] doc3 = {"id", "22", "place_t", "krakow london cardiff", "company_t",
+        "polecat null bbc", "price_ti", "39"};
+    assertU(adoc(doc3));
+    String[] doc4 = {"id", "23", "place_t", "london", "company_t", "", "price_ti", "29"};
+    assertU(adoc(doc4));
+    String[] doc5 = {"id", "24", "place_t", "la", "company_t", ""};
+    assertU(adoc(doc5));
+    String[] doc6 = {"id", "25", "company_t", "microsoft polecat null fujitsu null bbc", "price_ti", "59"};
+    assertU(adoc(doc6));
+    String[] doc7 = {"id", "26", "place_t", "krakow", "company_t", "null"};
+    assertU(adoc(doc7));
+    String[] doc8 = {"id", "27", "place_t", "krakow cardiff dublin london la", "company_t",
+        "null microsoft polecat bbc fujitsu"};
+    assertU(adoc(doc8));
+    String[] doc9 = {"id", "28", "place_t", "cork", "company_t",
+        "fujitsu rte"};
+    assertU(adoc(doc9));
+    assertU(commit());
+  }
+}

Modified: lucene/dev/trunk/solr/solrj/src/java/org/apache/solr/client/solrj/SolrQuery.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/solrj/src/java/org/apache/solr/client/solrj/SolrQuery.java?rev=1636772&r1=1636771&r2=1636772&view=diff
==============================================================================
--- lucene/dev/trunk/solr/solrj/src/java/org/apache/solr/client/solrj/SolrQuery.java (original)
+++ lucene/dev/trunk/solr/solrj/src/java/org/apache/solr/client/solrj/SolrQuery.java Tue Nov  4 23:09:41 2014
@@ -806,6 +806,13 @@ public class SolrQuery extends Modifiabl
     this.add( StatsParams.STATS_FIELD, field );
   }
   
+
+  public void addGetFieldStatistics( String ... field )
+    {
+      this.set( StatsParams.STATS, true );
+      this.add( StatsParams.STATS_FIELD, field );
+    }
+  
   public void addStatsFieldFacets( String field, String ... facets )
   {
     if( field == null ) {

Modified: lucene/dev/trunk/solr/solrj/src/java/org/apache/solr/client/solrj/response/FieldStatsInfo.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/solrj/src/java/org/apache/solr/client/solrj/response/FieldStatsInfo.java?rev=1636772&r1=1636771&r2=1636772&view=diff
==============================================================================
--- lucene/dev/trunk/solr/solrj/src/java/org/apache/solr/client/solrj/response/FieldStatsInfo.java (original)
+++ lucene/dev/trunk/solr/solrj/src/java/org/apache/solr/client/solrj/response/FieldStatsInfo.java Tue Nov  4 23:09:41 2014
@@ -180,6 +180,10 @@ public class FieldStatsInfo implements S
     return stddev;
   }
 
+  public Double getSumOfSquares() {
+    return sumOfSquares;
+  }
+
   public Map<String, List<FieldStatsInfo>> getFacets() {
     return facets;
   }

Modified: lucene/dev/trunk/solr/solrj/src/java/org/apache/solr/client/solrj/response/PivotField.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/solrj/src/java/org/apache/solr/client/solrj/response/PivotField.java?rev=1636772&r1=1636771&r2=1636772&view=diff
==============================================================================
--- lucene/dev/trunk/solr/solrj/src/java/org/apache/solr/client/solrj/response/PivotField.java (original)
+++ lucene/dev/trunk/solr/solrj/src/java/org/apache/solr/client/solrj/response/PivotField.java Tue Nov  4 23:09:41 2014
@@ -20,6 +20,7 @@ package org.apache.solr.client.solrj.res
 import java.io.PrintStream;
 import java.io.Serializable;
 import java.util.List;
+import java.util.Map;
 
 public class PivotField implements Serializable
 {
@@ -27,13 +28,23 @@ public class PivotField implements Seria
   final Object  _value;
   final int     _count;
   final List<PivotField> _pivot;
-   
-  public PivotField( String f, Object v, int count, List<PivotField> pivot )
+  final Map<String,FieldStatsInfo> _statsInfo;
+
+  /**
+   * @deprecated Use {@link #PivotField(String,Object,int,List,Map)} with a null <code>statsInfo</code>
+   */
+  @Deprecated
+  public PivotField( String f, Object v, int count, List<PivotField> pivot) {
+    this(f, v, count, pivot, null);
+  }
+
+  public PivotField( String f, Object v, int count, List<PivotField> pivot, Map<String,FieldStatsInfo> statsInfo)
   {
     _field = f;
     _value = v;
     _count = count;
     _pivot = pivot;
+    _statsInfo = statsInfo;
   }
    
   public String getField() {
@@ -52,6 +63,10 @@ public class PivotField implements Seria
     return _pivot;
   }
    
+  public Map<String,FieldStatsInfo> getFieldStatsInfo() {
+    return _statsInfo;
+  }
+
   @Override
   public String toString()
   {
@@ -63,7 +78,16 @@ public class PivotField implements Seria
     for( int i=0; i<indent; i++ ) {
       out.print( "  " );
     }
-    out.println( _field + "=" + _value + " ("+_count+")" );
+    out.print( _field + "=" + _value + " ("+_count+")" );
+    if (null != _statsInfo) {
+      out.print( "->stats:[" ); 
+      for( FieldStatsInfo fieldStatsInfo : _statsInfo.values() ) {
+        out.print(fieldStatsInfo.toString());
+        out.print(",");
+      }
+      out.print("]");
+    }
+    out.println();
     if( _pivot != null ) {
       for( PivotField p : _pivot ) {
         p.write( out, indent+1 );

Modified: lucene/dev/trunk/solr/solrj/src/java/org/apache/solr/client/solrj/response/QueryResponse.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/solrj/src/java/org/apache/solr/client/solrj/response/QueryResponse.java?rev=1636772&r1=1636771&r2=1636772&view=diff
==============================================================================
--- lucene/dev/trunk/solr/solrj/src/java/org/apache/solr/client/solrj/response/QueryResponse.java (original)
+++ lucene/dev/trunk/solr/solrj/src/java/org/apache/solr/client/solrj/response/QueryResponse.java Tue Nov  4 23:09:41 2014
@@ -23,6 +23,7 @@ import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.TreeMap;
 
 import org.apache.solr.client.solrj.SolrServer;
 import org.apache.solr.client.solrj.beans.DocumentObjectBinder;
@@ -163,19 +164,25 @@ public class QueryResponse extends SolrR
   }
   
   private void extractStatsInfo(NamedList<Object> info) {
+    _fieldStatsInfo = extractFieldStatsInfo(info);
+  }
+
+  private Map<String, FieldStatsInfo> extractFieldStatsInfo(NamedList<Object> info) {
     if( info != null ) {
-      _fieldStatsInfo = new HashMap<>();
+       Map<String, FieldStatsInfo> fieldStatsInfoMap = new TreeMap<>();
       NamedList<NamedList<Object>> ff = (NamedList<NamedList<Object>>) info.get( "stats_fields" );
       if( ff != null ) {
         for( Map.Entry<String,NamedList<Object>> entry : ff ) {
           NamedList<Object> v = entry.getValue();
           if( v != null ) {
-            _fieldStatsInfo.put( entry.getKey(), 
+             fieldStatsInfoMap.put( entry.getKey(),
                 new FieldStatsInfo( v, entry.getKey() ) );
           }
         }
       }
+       return fieldStatsInfoMap;
     }
+    return null;
   }
 
   private void extractDebugInfo( NamedList<Object> debug )
@@ -396,14 +403,38 @@ public class QueryResponse extends SolrR
       Object v = nl.getVal( 1 );
       assert "count".equals(nl.getName(2));
       int cnt = ((Integer)nl.getVal( 2 )).intValue();
-      List<PivotField> p = null;
+
+      List<PivotField> subPivots = null;
+      Map<String,FieldStatsInfo> fieldStatsInfos = null;
+
       if (4 <= nl.size()) {
-        assert "pivot".equals(nl.getName(3));
-        Object subPiv = nl.getVal(3);
-        assert null != subPiv : "Server sent back 'null' for sub pivots?";
-        p = readPivots( (List<NamedList>) subPiv );
+        for(int index = 3; index < nl.size(); index++) {
+          final String key = nl.getName(index);
+          final Object val = nl.getVal(index);
+          switch (key) {
+
+          case "pivot": {
+            assert null != val : "Server sent back 'null' for sub pivots?";
+            assert val instanceof List : "Server sent non-List for sub pivots?";
+
+            subPivots = readPivots( (List<NamedList>) val );
+            break;
+          }
+          case "stats": {
+            assert null != val : "Server sent back 'null' for stats?";
+            assert val instanceof NamedList : "Server sent non-NamedList for stats?";
+
+            fieldStatsInfos = extractFieldStatsInfo((NamedList<Object>) val);
+            break;
+          }
+          default: 
+            throw new RuntimeException( "unknown key in pivot: "+ key+ " ["+val+"]");
+
+          }
+        }
       }
-      values.add( new PivotField( f, v, cnt, p ) );
+
+      values.add( new PivotField( f, v, cnt, subPivots, fieldStatsInfos ) );
     }
     return values;
   }

Modified: lucene/dev/trunk/solr/solrj/src/test/org/apache/solr/client/solrj/SolrExampleTests.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/solrj/src/test/org/apache/solr/client/solrj/SolrExampleTests.java?rev=1636772&r1=1636771&r2=1636772&view=diff
==============================================================================
--- lucene/dev/trunk/solr/solrj/src/test/org/apache/solr/client/solrj/SolrExampleTests.java (original)
+++ lucene/dev/trunk/solr/solrj/src/test/org/apache/solr/client/solrj/SolrExampleTests.java Tue Nov  4 23:09:41 2014
@@ -57,12 +57,9 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.HashMap;
-import java.util.Iterator;
-import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Random;
-import java.util.Set;
 
 /**
  * This should include tests against the example solr config
@@ -814,6 +811,197 @@ abstract public class SolrExampleTests e
     doPivotFacetTest(false);
   }
     
+  @Test
+  public void testPivotFacetsStats() throws Exception {
+    SolrServer server = getSolrServer();
+
+    // Empty the database...
+    server.deleteByQuery("*:*");// delete everything!
+    server.commit();
+    assertNumFound("*:*", 0); // make sure it got in
+
+    int id = 1;
+    ArrayList<SolrInputDocument> docs = new ArrayList<>();
+    docs.add(makeTestDoc("id", id++, "features", "aaa", "manu", "apple", "cat", "a", "inStock", true, "popularity", 12, "price", .017));
+    docs.add(makeTestDoc("id", id++, "features", "aaa", "manu", "lg", "cat", "a", "inStock", false, "popularity", 13, "price", 16.04));
+    docs.add(makeTestDoc("id", id++, "features", "aaa", "manu", "samsung", "cat", "a", "inStock", true, "popularity", 14, "price", 12.34));
+    docs.add(makeTestDoc("id", id++, "features", "aaa", "manu", "lg", "cat", "b", "inStock", false, "popularity", 24, "price", 51.39));
+    docs.add(makeTestDoc("id", id++, "features", "aaa", "manu", "nokia", "cat", "b", "inStock", true, "popularity", 28, "price", 131.39));
+    docs.add(makeTestDoc("id", id++, "features", "bbb", "manu", "ztc", "cat", "a", "inStock", false, "popularity", 32));
+    docs.add(makeTestDoc("id", id++, "features", "bbb", "manu", "htc", "cat", "a", "inStock", true, "popularity", 31, "price", 131.39));
+    docs.add(makeTestDoc("id", id++, "features", "bbb", "manu", "apple", "cat", "b", "inStock", false, "popularity", 36));
+    docs.add(makeTestDoc("id", id++, "features", "bbb", "manu", "lg", "cat", "b", "inStock", true, "popularity", 37, "price", 1.39));
+    docs.add(makeTestDoc("id", id++, "features", "bbb", "manu", "ztc", "cat", "b", "inStock", false, "popularity", 38, "price", 47.98));
+    docs.add(makeTestDoc("id", id++, "features", "bbb", "manu", "ztc", "cat", "b", "inStock", true, "popularity", -38));
+    docs.add(makeTestDoc("id", id++, "cat", "b")); // something not matching all fields
+    server.add(docs);
+    server.commit();
+
+    for (String pivot : new String[] { "{!key=pivot_key stats=s1}features,manu",
+                                       "{!key=pivot_key stats=s1}features,manu,cat",
+                                       "{!key=pivot_key stats=s1}features,manu,cat,inStock"
+      }) {
+
+      // for any of these pivot params, the assertions we check should be teh same
+      // (we stop asserting at the "manu" level)
+      
+      SolrQuery query = new SolrQuery("*:*");
+      query.addFacetPivotField(pivot);
+      query.setFacetLimit(1);
+      query.addGetFieldStatistics("{!key=foo_price tag=s1}price", "{!tag=s1}popularity");
+      query.setFacetMinCount(0);
+      query.setRows(0);
+
+      QueryResponse rsp = server.query(query);
+
+      // check top (ie: non-pivot) stats
+      Map<String, FieldStatsInfo> map = rsp.getFieldStatsInfo();
+      FieldStatsInfo intValueStatsInfo = map.get("popularity");
+      assertEquals(-38.0d, intValueStatsInfo.getMin());
+      assertEquals(38.0d, intValueStatsInfo.getMax());
+      assertEquals(11l, intValueStatsInfo.getCount().longValue());
+      assertEquals(1l, intValueStatsInfo.getMissing().longValue());
+      assertEquals(227.0d, intValueStatsInfo.getSum());
+      assertEquals(20.636363636363637d, intValueStatsInfo.getMean());
+      
+      FieldStatsInfo doubleValueStatsInfo = map.get("foo_price");
+      assertEquals(.017d, (double) doubleValueStatsInfo.getMin(), .01d);
+      assertEquals(131.39d, (double) doubleValueStatsInfo.getMax(), .01d);
+      assertEquals(8l, doubleValueStatsInfo.getCount().longValue());
+      assertEquals(4l, doubleValueStatsInfo.getMissing().longValue());
+      assertEquals(391.93d, (double) doubleValueStatsInfo.getSum(), .01d);
+      assertEquals(48.99d, (double) doubleValueStatsInfo.getMean(), .01d);
+
+      // now get deeper and look at the pivots...
+
+      NamedList<List<PivotField>> pivots = rsp.getFacetPivot();
+      assertTrue( ! pivots.get("pivot_key").isEmpty() );
+
+      List<PivotField> list = pivots.get("pivot_key");
+      PivotField featuresBBBPivot = list.get(0);
+      assertEquals("features", featuresBBBPivot.getField());
+      assertEquals("bbb", featuresBBBPivot.getValue());
+      assertNotNull(featuresBBBPivot.getFieldStatsInfo());
+      assertEquals(2, featuresBBBPivot.getFieldStatsInfo().size());
+      
+      FieldStatsInfo featuresBBBPivotStats1 = featuresBBBPivot.getFieldStatsInfo().get("foo_price");
+      assertEquals("foo_price", featuresBBBPivotStats1.getName());
+      assertEquals(131.39d, (double) featuresBBBPivotStats1.getMax(), .01d);
+      assertEquals(1.38d, (double) featuresBBBPivotStats1.getMin(), .01d);
+      assertEquals(180.75d, (double) featuresBBBPivotStats1.getSum(), .01d);
+      assertEquals(3, (long) featuresBBBPivotStats1.getCount());
+      assertEquals(3, (long) featuresBBBPivotStats1.getMissing());
+      assertEquals(60.25d, (double) featuresBBBPivotStats1.getMean(), .01d);
+      assertEquals(65.86d, featuresBBBPivotStats1.getStddev(), .01d);
+      assertEquals(19567.34d, featuresBBBPivotStats1.getSumOfSquares(), .01d);
+      
+      FieldStatsInfo featuresBBBPivotStats2 = featuresBBBPivot.getFieldStatsInfo().get("popularity");
+      assertEquals("popularity", featuresBBBPivotStats2.getName());
+      assertEquals(38.0d, (double) featuresBBBPivotStats2.getMax(), .01d);
+      assertEquals(-38.0d, (double) featuresBBBPivotStats2.getMin(), .01d);
+      assertEquals(136.0d, (double) featuresBBBPivotStats2.getSum(), .01d);
+      assertEquals(6, (long) featuresBBBPivotStats2.getCount());
+      assertEquals(0, (long) featuresBBBPivotStats2.getMissing());
+      assertEquals(22.66d, (double) featuresBBBPivotStats2.getMean(), .01d);
+      assertEquals(29.85d, featuresBBBPivotStats2.getStddev(), .01d);
+      assertEquals(7538.0d, featuresBBBPivotStats2.getSumOfSquares(), .01d);
+      
+      List<PivotField> nestedPivotList = featuresBBBPivot.getPivot();
+      PivotField featuresBBBPivotPivot = nestedPivotList.get(0);
+      assertEquals("manu", featuresBBBPivotPivot.getField());
+      assertEquals("ztc", featuresBBBPivotPivot.getValue());
+      assertNotNull(featuresBBBPivotPivot.getFieldStatsInfo());
+      assertEquals(2, featuresBBBPivotPivot.getFieldStatsInfo().size());
+      
+      FieldStatsInfo featuresBBBManuZtcPivotStats1 = featuresBBBPivotPivot.getFieldStatsInfo().get("foo_price");
+      assertEquals("foo_price", featuresBBBManuZtcPivotStats1.getName());
+      assertEquals(47.97d, (double) featuresBBBManuZtcPivotStats1.getMax(), .01d);
+      assertEquals(47.97d, (double) featuresBBBManuZtcPivotStats1.getMin(), .01d);
+      assertEquals(47.97d, (double) featuresBBBManuZtcPivotStats1.getSum(), .01d);
+      assertEquals(1, (long) featuresBBBManuZtcPivotStats1.getCount());
+      assertEquals(2, (long) featuresBBBManuZtcPivotStats1.getMissing());
+      assertEquals(47.97d, (double) featuresBBBManuZtcPivotStats1.getMean(), .01d);
+      assertEquals(0.0d, featuresBBBManuZtcPivotStats1.getStddev(), .01d);
+      assertEquals(2302.08d, featuresBBBManuZtcPivotStats1.getSumOfSquares(), .01d);
+      
+      
+      FieldStatsInfo featuresBBBManuZtcPivotStats2 = featuresBBBPivotPivot.getFieldStatsInfo().get("popularity");
+      assertEquals("popularity", featuresBBBManuZtcPivotStats2.getName());
+      assertEquals(38.0d, (double) featuresBBBManuZtcPivotStats2.getMax(), .01d);
+      assertEquals(-38.0d, (double) featuresBBBManuZtcPivotStats2.getMin(), .01d);
+      assertEquals(32.0, (double) featuresBBBManuZtcPivotStats2.getSum(), .01d);
+      assertEquals(3, (long) featuresBBBManuZtcPivotStats2.getCount());
+      assertEquals(0, (long) featuresBBBManuZtcPivotStats2.getMissing());
+      assertEquals(10.66d, (double) featuresBBBManuZtcPivotStats2.getMean(), .01d);
+      assertEquals(42.25d, featuresBBBManuZtcPivotStats2.getStddev(), .01d);
+      assertEquals(3912.0d, featuresBBBManuZtcPivotStats2.getSumOfSquares(), .01d);
+    }
+  }
+
+  @Test
+  public void testPivotFacetsStatsNotSupported() throws Exception {
+    SolrServer server = getSolrServer();
+
+    // Empty the database...
+    server.deleteByQuery("*:*");// delete everything!
+    server.commit();
+    assertNumFound("*:*", 0); // make sure it got in
+
+    // results of this test should be the same regardless of wether any docs in index
+    if (random().nextBoolean()) {
+      server.add(makeTestDoc("id", 1, "features", "aaa", "cat", "a", "inStock", true, "popularity", 12, "price", .017));
+      server.commit();
+    }
+
+    ignoreException("is not currently supported");
+
+    // boolean field
+    SolrQuery query = new SolrQuery("*:*");
+    query.addFacetPivotField("{!stats=s1}features,manu");
+    query.addGetFieldStatistics("{!key=inStock_val tag=s1}inStock");
+    try {
+      server.query(query);
+      fail("SolrException should be thrown on query");
+    } catch (SolrException e) {
+      assertEquals("Pivot facet on boolean is not currently supported, bad request returned", 400, e.code());
+      assertTrue(e.getMessage().contains("is not currently supported"));
+      assertTrue(e.getMessage().contains("boolean"));
+    }
+
+    // asking for multiple stat tags -- see SOLR-6663
+    query = new SolrQuery("*:*");
+    query.addFacetPivotField("{!stats=tag1,tag2}features,manu");
+    query.addGetFieldStatistics("{!tag=tag1}price", "{!tag=tag2}popularity");
+    query.setFacetMinCount(0);
+    query.setRows(0);
+    try {
+      server.query(query);
+      fail("SolrException should be thrown on query");
+    } catch (SolrException e) {
+      assertEquals(400, e.code());
+      assertTrue(e.getMessage().contains("stats"));
+      assertTrue(e.getMessage().contains("comma"));
+      assertTrue(e.getMessage().contains("tag"));
+    }
+
+    // text field
+    query = new SolrQuery("*:*");
+    query.addFacetPivotField("{!stats=s1}features,manu");
+    query.addGetFieldStatistics("{!tag=s1}features");
+    query.setFacetMinCount(0);
+    query.setRows(0);
+    try {
+      server.query(query);
+      fail("SolrException should be thrown on query");
+    } catch (SolrException e) {
+      assertEquals("Pivot facet on string is not currently supported, bad request returned", 400, e.code());
+      assertTrue(e.getMessage().contains("is not currently supported"));
+      assertTrue(e.getMessage().contains("text_general"));
+    }
+    
+
+  }
+
   public void testPivotFacetsMissing() throws Exception {
     doPivotFacetTest(true);
   }