You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by ge...@apache.org on 2018/11/14 20:28:19 UTC
[1/2] lucene-solr:branch_7x: SOLR-12965: Add facet support to
JsonQueryRequest
Repository: lucene-solr
Updated Branches:
refs/heads/branch_7x 6faddfe3b -> b502ba288
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/b502ba28/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/DirectJsonQueryRequestFacetingIntegrationTest.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/DirectJsonQueryRequestFacetingIntegrationTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/DirectJsonQueryRequestFacetingIntegrationTest.java
new file mode 100644
index 0000000..be4f0cc
--- /dev/null
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/DirectJsonQueryRequestFacetingIntegrationTest.java
@@ -0,0 +1,615 @@
+/*
+ * 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.
+ */
+
+package org.apache.solr.client.solrj.request.json;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.solr.client.solrj.request.AbstractUpdateRequest;
+import org.apache.solr.client.solrj.request.CollectionAdminRequest;
+import org.apache.solr.client.solrj.request.ContentStreamUpdateRequest;
+import org.apache.solr.client.solrj.response.QueryResponse;
+import org.apache.solr.client.solrj.response.UpdateResponse;
+import org.apache.solr.cloud.SolrCloudTestCase;
+import org.apache.solr.common.SolrDocumentList;
+import org.apache.solr.common.util.NamedList;
+import org.apache.solr.util.ExternalPaths;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class DirectJsonQueryRequestFacetingIntegrationTest extends SolrCloudTestCase {
+
+ private static final String COLLECTION_NAME = "techproducts";
+ private static final String CONFIG_NAME = "techproducts_config";
+ private static final int NUM_TECHPRODUCTS_DOCS = 32;
+ private static final int NUM_IN_STOCK = 17;
+ private static final int NUM_ELECTRONICS = 12;
+ private static final int NUM_CURRENCY = 4;
+ private static final int NUM_MEMORY = 3;
+ private static final int NUM_CORSAIR = 3;
+ private static final int NUM_BELKIN = 2;
+ private static final int NUM_CANON = 2;
+
+
+
+ @BeforeClass
+ public static void setupCluster() throws Exception {
+ configureCluster(1)
+ .addConfig(CONFIG_NAME, new File(ExternalPaths.TECHPRODUCTS_CONFIGSET).toPath())
+ .configure();
+
+ final List<String> solrUrls = new ArrayList<>();
+ solrUrls.add(cluster.getJettySolrRunner(0).getBaseUrl().toString());
+
+ CollectionAdminRequest.createCollection(COLLECTION_NAME, CONFIG_NAME, 1, 1).process(cluster.getSolrClient());
+
+ ContentStreamUpdateRequest up = new ContentStreamUpdateRequest("/update");
+ up.setParam("collection", COLLECTION_NAME);
+ up.addFile(getFile("solrj/techproducts.xml"), "application/xml");
+ up.setAction(AbstractUpdateRequest.ACTION.COMMIT, true, true);
+ UpdateResponse updateResponse = up.process(cluster.getSolrClient());
+ assertEquals(0, updateResponse.getStatus());
+ }
+ @Test
+ public void testSingleTermsFacet() throws Exception {
+ final String jsonBody = String.join("\n","{",
+ " 'query': '*:*',",
+ " 'facet': {",
+ " 'top_cats': {",
+ " 'type': 'terms',",
+ " 'field': 'cat',",
+ " 'limit': 3",
+ " }",
+ " }",
+ "}");
+ final DirectJsonQueryRequest request = new DirectJsonQueryRequest(jsonBody);
+ QueryResponse response = request.process(cluster.getSolrClient(), COLLECTION_NAME);
+
+ assertEquals(0, response.getStatus());
+ final SolrDocumentList returnedDocs = response.getResults();
+ assertEquals(NUM_TECHPRODUCTS_DOCS, returnedDocs.getNumFound());
+ assertEquals(10, returnedDocs.size());
+ final NamedList<Object> rawResponse = response.getResponse();
+ assertHasFacetWithBucketValues(rawResponse,"top_cats", new FacetBucket("electronics",NUM_ELECTRONICS),
+ new FacetBucket("currency", NUM_CURRENCY), new FacetBucket("memory", NUM_MEMORY));
+ }
+
+ @Test
+ public void testMultiTermsFacet() throws Exception {
+ final String jsonBody = String.join("\n","{",
+ " 'query': '*:*',",
+ " 'facet': {",
+ " 'top_cats': {",
+ " 'type': 'terms',",
+ " 'field': 'cat',",
+ " 'limit': 3",
+ " },",
+ " 'top_manufacturers': {",
+ " 'type': 'terms',",
+ " 'field': 'manu_id_s',",
+ " 'limit': 3",
+ " }",
+ " }",
+ "}");
+ final DirectJsonQueryRequest request = new DirectJsonQueryRequest(jsonBody);
+ QueryResponse response = request.process(cluster.getSolrClient(), COLLECTION_NAME);
+
+ assertEquals(0, response.getStatus());
+ final SolrDocumentList returnedDocs = response.getResults();
+ assertEquals(NUM_TECHPRODUCTS_DOCS, returnedDocs.getNumFound());
+ assertEquals(10, returnedDocs.size());
+ final NamedList<Object> rawResponse = response.getResponse();
+
+ assertHasFacetWithBucketValues(rawResponse,"top_cats", new FacetBucket("electronics",NUM_ELECTRONICS),
+ new FacetBucket("currency", NUM_CURRENCY), new FacetBucket("memory", NUM_MEMORY));
+ assertHasFacetWithBucketValues(rawResponse,"top_manufacturers", new FacetBucket("corsair",NUM_CORSAIR),
+ new FacetBucket("belkin", NUM_BELKIN), new FacetBucket("canon", NUM_CANON));
+ }
+
+ @Test
+ public void testSingleRangeFacet() throws Exception {
+ final String jsonBody = String.join("\n","{",
+ " 'query': '*:*',",
+ " 'facet': {",
+ " 'prices': {",
+ " 'type': 'range',",
+ " 'field': 'price',",
+ " 'start': 0,",
+ " 'end': 100,",
+ " 'gap': 20",
+ " }",
+ " }",
+ "}");
+ final DirectJsonQueryRequest request = new DirectJsonQueryRequest(jsonBody);
+ QueryResponse response = request.process(cluster.getSolrClient(), COLLECTION_NAME);
+
+ assertEquals(0, response.getStatus());
+ final SolrDocumentList returnedDocs = response.getResults();
+ assertEquals(NUM_TECHPRODUCTS_DOCS, returnedDocs.getNumFound());
+ assertEquals(10, returnedDocs.size());
+ final NamedList<Object> rawResponse = response.getResponse();
+ assertHasFacetWithBucketValues(rawResponse,"prices",
+ new FacetBucket(0.0f, 5),
+ new FacetBucket(20.0f, 0),
+ new FacetBucket(40.0f, 0),
+ new FacetBucket(60.0f, 1),
+ new FacetBucket(80.0f, 1));
+ }
+
+ @Test
+ public void testMultiRangeFacet() throws Exception {
+ final String jsonBody = String.join("\n","{",
+ " 'query': '*:*',",
+ " 'facet': {",
+ " 'prices': {",
+ " 'type': 'range',",
+ " 'field': 'price',",
+ " 'start': 0,",
+ " 'end': 100,",
+ " 'gap': 20",
+ " },",
+ " 'shipping_weights': {",
+ " 'type': 'range',",
+ " 'field': 'weight',",
+ " 'start': 0,",
+ " 'end': 200,",
+ " 'gap': 50",
+ " }",
+ " }",
+ "}");
+ final DirectJsonQueryRequest request = new DirectJsonQueryRequest(jsonBody);
+ QueryResponse response = request.process(cluster.getSolrClient(), COLLECTION_NAME);
+
+ assertEquals(0, response.getStatus());
+ final SolrDocumentList returnedDocs = response.getResults();
+ assertEquals(NUM_TECHPRODUCTS_DOCS, returnedDocs.getNumFound());
+ assertEquals(10, returnedDocs.size());
+ final NamedList<Object> rawResponse = response.getResponse();
+ assertHasFacetWithBucketValues(rawResponse,"prices",
+ new FacetBucket(0.0f, 5),
+ new FacetBucket(20.0f, 0),
+ new FacetBucket(40.0f, 0),
+ new FacetBucket(60.0f, 1),
+ new FacetBucket(80.0f, 1));
+ assertHasFacetWithBucketValues(rawResponse, "shipping_weights",
+ new FacetBucket(0.0f, 6),
+ new FacetBucket(50.0f, 0),
+ new FacetBucket(100.0f, 0),
+ new FacetBucket(150.0f,1));
+ }
+
+ @Test
+ public void testSingleStatFacet() throws Exception {
+ final String jsonBody = String.join("\n","{",
+ " 'query': '*:*',",
+ " 'facet': {",
+ " 'sum_price': 'sum(price)'",
+ " }",
+ "}");
+ final DirectJsonQueryRequest request = new DirectJsonQueryRequest(jsonBody);
+ QueryResponse response = request.process(cluster.getSolrClient(), COLLECTION_NAME);
+
+ assertEquals(0, response.getStatus());
+ final SolrDocumentList returnedDocs = response.getResults();
+ assertEquals(NUM_TECHPRODUCTS_DOCS, returnedDocs.getNumFound());
+ assertEquals(10, returnedDocs.size());
+ final NamedList<Object> rawResponse = response.getResponse();
+ assertHasStatFacetWithValue(rawResponse,"sum_price", 5251.270030975342);
+ }
+
+ @Test
+ public void testMultiStatFacet() throws Exception {
+ final String jsonBody = String.join("\n","{",
+ " 'query': '*:*',",
+ " 'facet': {",
+ " 'sum_price': 'sum(price)',",
+ " 'avg_price': 'avg(price)'",
+ " }",
+ "}");
+ final DirectJsonQueryRequest request = new DirectJsonQueryRequest(jsonBody);
+ QueryResponse response = request.process(cluster.getSolrClient(), COLLECTION_NAME);
+
+ assertEquals(0, response.getStatus());
+ final SolrDocumentList returnedDocs = response.getResults();
+ assertEquals(NUM_TECHPRODUCTS_DOCS, returnedDocs.getNumFound());
+ assertEquals(10, returnedDocs.size());
+ final NamedList<Object> rawResponse = response.getResponse();
+ assertHasStatFacetWithValue(rawResponse,"sum_price", 5251.270030975342);
+ assertHasStatFacetWithValue(rawResponse,"avg_price", 328.20437693595886);
+ }
+
+ @Test
+ public void testMultiFacetsMixedTypes() throws Exception {
+ final String jsonBody = String.join("\n","{",
+ " 'query': '*:*',",
+ " 'facet': {",
+ " 'avg_price': 'avg(price)',",
+ " 'top_cats': {",
+ " 'type': 'terms',",
+ " 'field': 'cat',",
+ " 'limit': 3",
+ " }",
+ " }",
+ "}");
+ final DirectJsonQueryRequest request = new DirectJsonQueryRequest(jsonBody);
+ QueryResponse response = request.process(cluster.getSolrClient(), COLLECTION_NAME);
+
+ assertEquals(0, response.getStatus());
+ final SolrDocumentList returnedDocs = response.getResults();
+ assertEquals(NUM_TECHPRODUCTS_DOCS, returnedDocs.getNumFound());
+ assertEquals(10, returnedDocs.size());
+ final NamedList<Object> rawResponse = response.getResponse();
+ assertHasStatFacetWithValue(rawResponse,"avg_price", 328.20437693595886);
+ assertHasFacetWithBucketValues(rawResponse,"top_cats", new FacetBucket("electronics",NUM_ELECTRONICS),
+ new FacetBucket("currency", NUM_CURRENCY), new FacetBucket("memory", NUM_MEMORY));
+ }
+
+ @Test
+ public void testNestedTermsFacet() throws Exception {
+ final String jsonBody = String.join("\n","{",
+ " 'query': '*:*',",
+ " 'facet': {",
+ " 'top_cats': {",
+ " 'type': 'terms',",
+ " 'field': 'cat',",
+ " 'limit': 3",
+ " 'facet': {",
+ " 'top_manufacturers_for_cat': {",
+ " 'type': 'terms',",
+ " 'field': 'manu_id_s',",
+ " 'limit': 1",
+ " }",
+ " }",
+ " }",
+ " }",
+ "}");
+ final DirectJsonQueryRequest request = new DirectJsonQueryRequest(jsonBody);
+ QueryResponse response = request.process(cluster.getSolrClient(), COLLECTION_NAME);
+
+ assertEquals(0, response.getStatus());
+ final SolrDocumentList returnedDocs = response.getResults();
+ assertEquals(NUM_TECHPRODUCTS_DOCS, returnedDocs.getNumFound());
+ assertEquals(10, returnedDocs.size());
+ final NamedList<Object> rawResponse = response.getResponse();
+
+ // Test top level facets
+ assertHasFacetWithBucketValues(rawResponse,"top_cats", new FacetBucket("electronics",NUM_ELECTRONICS),
+ new FacetBucket("currency", NUM_CURRENCY), new FacetBucket("memory", NUM_MEMORY));
+
+ // Test subfacet values for each top-level facet bucket
+ final List<NamedList<Object>> topLevelFacetResponse = (List<NamedList<Object>>) rawResponse.findRecursive("facets", "top_cats", "buckets");
+ final NamedList<Object> electronicsSubFacet = topLevelFacetResponse.get(0);
+ assertFacetResponseHasFacetWithBuckets(electronicsSubFacet, "top_manufacturers_for_cat", new FacetBucket("corsair", 3));
+ final NamedList<Object> currencySubfacet = topLevelFacetResponse.get(1);
+ assertFacetResponseHasFacetWithBuckets(currencySubfacet, "top_manufacturers_for_cat", new FacetBucket("boa", 1));
+ final NamedList<Object> memorySubfacet = topLevelFacetResponse.get(2);
+ assertFacetResponseHasFacetWithBuckets(memorySubfacet, "top_manufacturers_for_cat", new FacetBucket("corsair", 3));
+ }
+
+ @Test
+ public void testNestedFacetsOfMixedTypes() throws Exception {
+ final String subfacetName = "avg_price_for_cat";
+ final String jsonBody = String.join("\n","{",
+ " 'query': '*:*',",
+ " 'facet': {",
+ " 'top_cats': {",
+ " 'type': 'terms',",
+ " 'field': 'cat',",
+ " 'limit': 3",
+ " 'facet': {",
+ " 'avg_price_for_cat': 'avg(price)'",
+ " }",
+ " }",
+ " }",
+ "}");
+ final DirectJsonQueryRequest request = new DirectJsonQueryRequest(jsonBody);
+ QueryResponse response = request.process(cluster.getSolrClient(), COLLECTION_NAME);
+
+ assertEquals(0, response.getStatus());
+ final SolrDocumentList returnedDocs = response.getResults();
+ assertEquals(NUM_TECHPRODUCTS_DOCS, returnedDocs.getNumFound());
+ assertEquals(10, returnedDocs.size());
+ final NamedList<Object> rawResponse = response.getResponse();
+
+ // Test top level facets
+ assertHasFacetWithBucketValues(rawResponse,"top_cats", new FacetBucket("electronics",NUM_ELECTRONICS),
+ new FacetBucket("currency", NUM_CURRENCY), new FacetBucket("memory", NUM_MEMORY));
+
+ // Test subfacet values for each top-level facet bucket
+ final List<NamedList<Object>> topLevelFacetResponse = (List<NamedList<Object>>) rawResponse.findRecursive("facets", "top_cats", "buckets");
+ final NamedList<Object> electronicsSubFacet = topLevelFacetResponse.get(0);
+ assertFacetResponseHasStatFacetWithValue(electronicsSubFacet, subfacetName, 252.02909261530095);
+ final NamedList<Object> currencySubfacet = topLevelFacetResponse.get(1);
+ assertFacetResponseHasStatFacetWithValue(currencySubfacet, subfacetName, 0.0);
+ final NamedList<Object> memorySubfacet = topLevelFacetResponse.get(2);
+ assertFacetResponseHasStatFacetWithValue(memorySubfacet, subfacetName, 129.99499893188477);
+ }
+
+ @Test
+ public void testFacetWithDomainFilteredBySimpleQueryString() throws Exception {
+ final String jsonBody = String.join("\n","{",
+ " 'query': '*:*',",
+ " 'facet': {",
+ " 'top_popular_cats': {",
+ " 'type': 'terms',",
+ " 'field': 'cat',",
+ " 'limit': 3",
+ " 'domain': {",
+ " 'filter': 'popularity:[5 TO 10]'",
+ " }",
+ " }",
+ " }",
+ "}");
+ final DirectJsonQueryRequest request = new DirectJsonQueryRequest(jsonBody);
+ QueryResponse response = request.process(cluster.getSolrClient(), COLLECTION_NAME);
+
+ assertEquals(0, response.getStatus());
+ final SolrDocumentList returnedDocs = response.getResults();
+ assertEquals(NUM_TECHPRODUCTS_DOCS, returnedDocs.getNumFound());
+ assertEquals(10, returnedDocs.size());
+ final NamedList<Object> rawResponse = response.getResponse();
+
+ // Test top level facets
+ assertHasFacetWithBucketValues(rawResponse,"top_popular_cats", new FacetBucket("electronics",9),
+ new FacetBucket("graphics card", 2), new FacetBucket("hard drive", 2));
+ }
+
+ @Test
+ public void testFacetWithDomainFilteredByLocalParamsQueryString() throws Exception {
+ final String jsonBody = String.join("\n","{",
+ " 'query': '*:*',",
+ " 'facet': {",
+ " 'top_popular_cats': {",
+ " 'type': 'terms',",
+ " 'field': 'cat',",
+ " 'limit': 3",
+ " 'domain': {",
+ " 'filter': '{!lucene df=\"popularity\" v=\"[5 TO 10]\"}'",
+ " }",
+ " }",
+ " }",
+ "}");
+ final DirectJsonQueryRequest request = new DirectJsonQueryRequest(jsonBody);
+ QueryResponse response = request.process(cluster.getSolrClient(), COLLECTION_NAME);
+
+ assertEquals(0, response.getStatus());
+ final SolrDocumentList returnedDocs = response.getResults();
+ assertEquals(NUM_TECHPRODUCTS_DOCS, returnedDocs.getNumFound());
+ assertEquals(10, returnedDocs.size());
+ final NamedList<Object> rawResponse = response.getResponse();
+
+ // Test top level facets
+ assertHasFacetWithBucketValues(rawResponse,"top_popular_cats", new FacetBucket("electronics",9),
+ new FacetBucket("graphics card", 2), new FacetBucket("hard drive", 2));
+ }
+
+ @Test
+ public void testFacetWithArbitraryDomainFromQueryString() throws Exception {
+ final String jsonBody = String.join("\n","{",
+ " 'query': 'cat:electronics',",
+ " 'facet': {",
+ " 'top_cats': {",
+ " 'type': 'terms',",
+ " 'field': 'cat',",
+ " 'limit': 3",
+ " 'domain': {",
+ " 'query': '*:*'",
+ " }",
+ " }",
+ " }",
+ "}");
+ final DirectJsonQueryRequest request = new DirectJsonQueryRequest(jsonBody);
+ QueryResponse response = request.process(cluster.getSolrClient(), COLLECTION_NAME);
+
+ assertEquals(0, response.getStatus());
+ final SolrDocumentList returnedDocs = response.getResults();
+ assertEquals(NUM_ELECTRONICS, returnedDocs.getNumFound());
+ assertEquals(10, returnedDocs.size());
+ final NamedList<Object> rawResponse = response.getResponse();
+
+ // Test top level facets
+ assertHasFacetWithBucketValues(rawResponse,"top_cats", new FacetBucket("electronics",NUM_ELECTRONICS),
+ new FacetBucket("currency", NUM_CURRENCY), new FacetBucket("memory", NUM_MEMORY));
+ }
+
+ @Test
+ public void testFacetWithArbitraryDomainFromLocalParamsQuery() throws Exception {
+ final String jsonBody = String.join("\n","{",
+ " 'query': 'cat:electronics',",
+ " 'facet': {",
+ " 'largest_search_cats': {",
+ " 'type': 'terms',",
+ " 'field': 'cat',",
+ " 'domain': {",
+ " 'query': '{!lucene df=\"cat\" v=\"search\"}'",
+ " }",
+ " }",
+ " }",
+ "}");
+ final DirectJsonQueryRequest request = new DirectJsonQueryRequest(jsonBody);
+ QueryResponse response = request.process(cluster.getSolrClient(), COLLECTION_NAME);
+
+ assertEquals(0, response.getStatus());
+ final SolrDocumentList returnedDocs = response.getResults();
+ assertEquals(NUM_ELECTRONICS, returnedDocs.getNumFound());
+ assertEquals(10, returnedDocs.size());
+ final NamedList<Object> rawResponse = response.getResponse();
+
+ assertHasFacetWithBucketValues(rawResponse,"largest_search_cats",
+ new FacetBucket("search",2),
+ new FacetBucket("software", 2));
+ }
+
+ /*
+ * Multiple query clauses are effectively AND'd together
+ */
+ public void testFacetWithMultipleSimpleQueryClausesInArbitraryDomain() throws Exception {
+ final String jsonBody = String.join("\n","{",
+ " 'query': 'cat:electronics',",
+ " 'facet': {",
+ " 'cats_matching_solr': {",
+ " 'type': 'terms',",
+ " 'field': 'cat',",
+ " 'domain': {",
+ " 'query': ['cat:search', 'name:Solr']",
+ " }",
+ " }",
+ " }",
+ "}");
+ final DirectJsonQueryRequest request = new DirectJsonQueryRequest(jsonBody);
+ QueryResponse response = request.process(cluster.getSolrClient(), COLLECTION_NAME);
+
+ assertEquals(0, response.getStatus());
+ final SolrDocumentList returnedDocs = response.getResults();
+ assertEquals(NUM_ELECTRONICS, returnedDocs.getNumFound());
+ assertEquals(10, returnedDocs.size());
+ final NamedList<Object> rawResponse = response.getResponse();
+
+ assertHasFacetWithBucketValues(rawResponse,"cats_matching_solr",
+ new FacetBucket("search",1),
+ new FacetBucket("software", 1));
+ }
+
+ public void testFacetWithMultipleLocalParamsQueryClausesInArbitraryDomain() throws Exception {
+ final String jsonBody = String.join("\n","{",
+ " 'query': 'cat:electronics',",
+ " 'facet': {",
+ " 'cats_matching_solr': {",
+ " 'type': 'terms',",
+ " 'field': 'cat',",
+ " 'domain': {",
+ " 'query': ['{!lucene df=\"cat\" v=\"search\"}', '{!lucene df=\"name\" v=\"Solr\"}']",
+ " }",
+ " }",
+ " }",
+ "}");
+ final DirectJsonQueryRequest request = new DirectJsonQueryRequest(jsonBody);
+ QueryResponse response = request.process(cluster.getSolrClient(), COLLECTION_NAME);
+
+ assertEquals(0, response.getStatus());
+ final SolrDocumentList returnedDocs = response.getResults();
+ assertEquals(NUM_ELECTRONICS, returnedDocs.getNumFound());
+ assertEquals(10, returnedDocs.size());
+ final NamedList<Object> rawResponse = response.getResponse();
+
+ // Test top level facets
+ assertHasFacetWithBucketValues(rawResponse,"cats_matching_solr",
+ new FacetBucket("search",1),
+ new FacetBucket("software", 1));
+ }
+
+ @Test
+ public void testFacetWithDomainWidenedUsingExcludeTagsToIgnoreFilters() throws Exception {
+ final String jsonBody = String.join("\n","{",
+ " 'query': '*:*',",
+ " 'filter': {'#on_shelf': 'inStock:true'},",
+ " 'facet': {",
+ " 'in_stock_only': {",
+ " 'type': 'terms',",
+ " 'field': 'cat',",
+ " 'limit': 2",
+ " }",
+ " 'all': {",
+ " 'type': 'terms',",
+ " 'field': 'cat',",
+ " 'limit': 2,",
+ " 'domain': {",
+ " 'excludeTags': 'on_shelf'",
+ " }",
+ " }",
+ " }",
+ "}");
+ final DirectJsonQueryRequest request = new DirectJsonQueryRequest(jsonBody);
+ QueryResponse response = request.process(cluster.getSolrClient(), COLLECTION_NAME);
+
+ assertEquals(0, response.getStatus());
+ final SolrDocumentList returnedDocs = response.getResults();
+ assertEquals(NUM_IN_STOCK, returnedDocs.getNumFound());
+ assertEquals(10, returnedDocs.size());
+ final NamedList<Object> rawResponse = response.getResponse();
+
+ assertHasFacetWithBucketValues(rawResponse,"in_stock_only",
+ new FacetBucket("electronics",8),
+ new FacetBucket("currency", 4));
+ assertHasFacetWithBucketValues(rawResponse,"all",
+ new FacetBucket("electronics",12),
+ new FacetBucket("currency", 4));
+ }
+
+ private class FacetBucket {
+ private final Object val;
+ private final int count;
+ FacetBucket(Object val, int count) {
+ this.val = val;
+ this.count = count;
+ }
+
+ public Object getVal() { return val; }
+ public int getCount() { return count; }
+ }
+
+ private void assertHasFacetWithBucketValues(NamedList<Object> rawResponse, String expectedFacetName, FacetBucket... expectedBuckets) {
+ final NamedList<Object> facetsTopLevel = assertHasFacetResponse(rawResponse);
+ assertFacetResponseHasFacetWithBuckets(facetsTopLevel, expectedFacetName, expectedBuckets);
+ }
+
+ private void assertHasStatFacetWithValue(NamedList<Object> rawResponse, String expectedFacetName, Double expectedStatValue) {
+ final NamedList<Object> facetsTopLevel = assertHasFacetResponse(rawResponse);
+ assertFacetResponseHasStatFacetWithValue(facetsTopLevel, expectedFacetName, expectedStatValue);
+ }
+
+ private NamedList<Object> assertHasFacetResponse(NamedList<Object> topLevelResponse) {
+ Object o = topLevelResponse.get("facets");
+ if (o == null) fail("Response has no top-level 'facets' property as expected");
+ if (!(o instanceof NamedList)) fail("Response has a top-level 'facets' property, but it is not a NamedList");
+
+ return (NamedList<Object>) o;
+ }
+
+ private void assertFacetResponseHasFacetWithBuckets(NamedList<Object> facetResponse, String expectedFacetName, FacetBucket... expectedBuckets) {
+ Object o = facetResponse.get(expectedFacetName);
+ if (o == null) fail("Response has no top-level facet named '" + expectedFacetName + "'");
+ if (!(o instanceof NamedList)) fail("Response has a property for the expected facet '" + expectedFacetName + "' property, but it is not a NamedList");
+
+ final NamedList<Object> expectedFacetTopLevel = (NamedList<Object>) o;
+ o = expectedFacetTopLevel.get("buckets");
+ if (o == null) fail("Response has no 'buckets' property under 'facets'");
+ if (!(o instanceof List)) fail("Response has no 'buckets' property containing actual facet information.");
+
+ final List<NamedList> bucketList = (List<NamedList>) o;
+ assertEquals("Expected " + expectedBuckets.length + " buckets, but found " + bucketList.size(),
+ expectedBuckets.length, bucketList.size());
+ for (int i = 0; i < expectedBuckets.length; i++) {
+ final FacetBucket expectedBucket = expectedBuckets[i];
+ final NamedList<Object> actualBucket = bucketList.get(i);
+ assertEquals(expectedBucket.getVal(), actualBucket.get("val"));
+ assertEquals(expectedBucket.getCount(), actualBucket.get("count"));
+ }
+ }
+
+ private void assertFacetResponseHasStatFacetWithValue(NamedList<Object> facetResponse, String expectedFacetName, Double expectedStatValue) {
+ Object o = facetResponse.get(expectedFacetName);
+ if (o == null) fail("Response has no top-level facet named '" + expectedFacetName + "'");
+ if (!(o instanceof Number)) fail("Response has a property for the expected facet '" + expectedFacetName + "' property, but it is not a Number");
+
+ final Number actualStatValueAsNumber = (Number) o;
+ final Double actualStatValueAsDouble = ((Number) o).doubleValue();
+ assertEquals(expectedStatValue, actualStatValueAsDouble, 0.5);
+ }
+}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/b502ba28/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/DomainMapTest.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/DomainMapTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/DomainMapTest.java
new file mode 100644
index 0000000..d437d14
--- /dev/null
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/DomainMapTest.java
@@ -0,0 +1,177 @@
+/*
+ * 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.
+ */
+
+package org.apache.solr.client.solrj.request.json;
+
+import java.util.List;
+import java.util.Map;
+
+import org.apache.solr.SolrTestCaseJ4;
+import org.junit.Test;
+
+import static org.junit.internal.matchers.StringContains.containsString;
+
+public class DomainMapTest extends SolrTestCaseJ4 {
+
+ @Test
+ public void testRejectsInvalidFilters() {
+ final Throwable thrown = expectThrows(IllegalArgumentException.class, () -> {
+ new DomainMap()
+ .withFilter(null);
+ });
+ assertThat(thrown.getMessage(), containsString("must be non-null"));
+ }
+
+ @Test
+ public void testStoresFilterWithCorrectKey() {
+ final DomainMap domain = new DomainMap()
+ .withFilter("name:Solr");
+ final List<String> filterList = (List<String>) domain.get("filter");
+
+ assertTrue("Expected filter list to contain provided filter", filterList.contains("name:Solr"));
+ }
+
+ @Test
+ public void testStoresMultipleFilters() {
+ final DomainMap domain = new DomainMap()
+ .withFilter("name:Solr")
+ .withFilter("cat:search");
+ final List<String> filterList = (List<String>) domain.get("filter");
+
+ assertTrue("Expected filter list to contain 1st provided filter", filterList.contains("name:Solr"));
+ assertTrue("Expected filter list to contain 2nd provided filter", filterList.contains("cat:search"));
+ }
+
+ @Test
+ public void testRejectsInvalidQueries() {
+ final Throwable thrown = expectThrows(IllegalArgumentException.class, () -> {
+ new DomainMap()
+ .withQuery(null);
+ });
+ assertThat(thrown.getMessage(), containsString("must be non-null"));
+ }
+
+ @Test
+ public void testStoresQueryWithCorrectKey() {
+ final DomainMap domain = new DomainMap()
+ .withQuery("name:Solr");
+ final List<String> queryList = (List<String>) domain.get("query");
+
+ assertTrue("Expected query list to contain provided query", queryList.contains("name:Solr"));
+ }
+
+ @Test
+ public void testStoresMultipleQueries() {
+ final DomainMap domain = new DomainMap()
+ .withQuery("name:Solr")
+ .withQuery("cat:search");
+ final List<String> queryList = (List<String>) domain.get("query");
+
+ assertTrue("Expected query list to contain 1st provided query", queryList.contains("name:Solr"));
+ assertTrue("Expected query list to contain 2nd provided query", queryList.contains("cat:search"));
+ }
+
+ @Test
+ public void testRejectsInvalidTagsToExclude() {
+ final Throwable thrown = expectThrows(IllegalArgumentException.class, () -> {
+ new DomainMap()
+ .withTagsToExclude(null);
+ });
+ assertThat(thrown.getMessage(), containsString("must be non-null"));
+ }
+
+ @Test
+ public void testStoresTagsToExcludeWithCorrectKey() {
+ final DomainMap domain = new DomainMap()
+ .withTagsToExclude("BRAND");
+ final List<String> exclusionList = (List<String>) domain.get("excludeTags");
+
+ assertTrue("Expected tag-exclusion list to contain provided tag", exclusionList.contains("BRAND"));
+ }
+
+ @Test
+ public void testStoresMultipleTagExclusionStrings() {
+ final DomainMap domain = new DomainMap()
+ .withTagsToExclude("BRAND")
+ .withTagsToExclude("COLOR");
+ final List<String> exclusionList = (List<String>) domain.get("excludeTags");
+
+ assertTrue("Expected tag-exclusion list to contain provided 1st tag", exclusionList.contains("BRAND"));
+ assertTrue("Expected tag-exclusion list to contain provided 2nd tag", exclusionList.contains("COLOR"));
+ }
+
+ @Test
+ public void testRejectsInvalidBlockParentQuery() {
+ final Throwable thrown = expectThrows(IllegalArgumentException.class, () -> {
+ new DomainMap()
+ .setBlockParentQuery(null);
+ });
+ assertThat(thrown.getMessage(), containsString("must be non-null"));
+ }
+
+ @Test
+ public void testStoresBlockParentQueryWithCorrectKey() {
+ final DomainMap domain = new DomainMap()
+ .setBlockParentQuery("content_type:product");
+ assertEquals("content_type:product", domain.get("blockParent"));
+ }
+
+ @Test
+ public void testRejectsInvalidBlockChildrenQuery() {
+ final Throwable thrown = expectThrows(IllegalArgumentException.class, () -> {
+ new DomainMap()
+ .setBlockChildQuery(null);
+ });
+ assertThat(thrown.getMessage(), containsString("must be non-null"));
+ }
+
+ @Test
+ public void testStoresBlockChildrenQueryWithCorrectKey() {
+ final DomainMap domain = new DomainMap()
+ .setBlockChildQuery("content_type:productColors");
+ assertEquals("content_type:productColors", domain.get("blockChildren"));
+ }
+
+ @Test
+ public void testRejectsInvalidJoinFromParam() {
+ final Throwable thrown = expectThrows(IllegalArgumentException.class, () -> {
+ new DomainMap()
+ .setJoinTransformation(null, "valid-to-field");
+ });
+ assertThat(thrown.getMessage(), containsString("must be non-null"));
+ }
+
+ @Test
+ public void testRejectsInvalidJoinToParam() {
+ final Throwable thrown = expectThrows(IllegalArgumentException.class, () -> {
+ new DomainMap()
+ .setJoinTransformation("valid-from-field", null);
+ });
+ assertThat(thrown.getMessage(), containsString("must be non-null"));
+ }
+
+ @Test
+ public void testStoresJoinValuesWithCorrectKey() {
+ final DomainMap domain = new DomainMap()
+ .setJoinTransformation("any-from-field", "any-to-field");
+
+ assertTrue(domain.containsKey("join"));
+ final Map<String, Object> joinParams = (Map<String, Object>) domain.get("join");
+ assertEquals("any-from-field", joinParams.get("from"));
+ assertEquals("any-to-field", joinParams.get("to"));
+ }
+}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/b502ba28/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/HeatmapFacetMapTest.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/HeatmapFacetMapTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/HeatmapFacetMapTest.java
new file mode 100644
index 0000000..9063714
--- /dev/null
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/HeatmapFacetMapTest.java
@@ -0,0 +1,130 @@
+/*
+ * 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.
+ */
+
+package org.apache.solr.client.solrj.request.json;
+
+import org.apache.solr.SolrTestCaseJ4;
+import org.junit.Test;
+
+import static org.junit.internal.matchers.StringContains.containsString;
+
+
+public class HeatmapFacetMapTest extends SolrTestCaseJ4 {
+
+ @Test
+ public void testRejectsInvalidFieldName() {
+ final Throwable thrown = expectThrows(IllegalArgumentException.class, () -> {
+ new HeatmapFacetMap(null);
+ });
+ assertThat(thrown.getMessage(), containsString("must be non-null"));
+ }
+
+ @Test
+ public void testStoresFieldNameWithCorrectKey() {
+ final HeatmapFacetMap heatmapFacet = new HeatmapFacetMap("ANY_FIELD_NAME");
+ assertEquals("ANY_FIELD_NAME", heatmapFacet.get("field"));
+ }
+
+ @Test
+ public void testDoesntSupportSubfacets() {
+ final Throwable thrown = expectThrows(UnsupportedOperationException.class, () -> {
+ new HeatmapFacetMap("ANY_FIELD_NAME")
+ .withSubFacet("ANY_NAME", new TermsFacetMap("ANY_OTHER_FIELD_NAME"));
+ });
+ assertThat(thrown.getMessage(), containsString("doesn't currently support subfacets"));
+ }
+
+ @Test
+ public void testRejectsInvalidRegionQueries() {
+ final Throwable thrown = expectThrows(IllegalArgumentException.class, () -> {
+ new HeatmapFacetMap("ANY_FIELD_NAME")
+ .setRegionQuery(null);
+ });
+ assertThat(thrown.getMessage(), containsString("must be non-null"));
+ }
+
+ @Test
+ public void testStoresRegionQueryWithCorrectKey() {
+ final HeatmapFacetMap heatmapFacet = new HeatmapFacetMap("ANY_FIELD_NAME")
+ .setRegionQuery("[-120,-35 TO 50,60]");
+ assertEquals("[-120,-35 TO 50,60]", heatmapFacet.get("geom"));
+ }
+
+ @Test
+ public void testRejectsInvalidCellSize() {
+ final Throwable thrown = expectThrows(IllegalArgumentException.class, () -> {
+ new HeatmapFacetMap("ANY_FIELD_NAME")
+ .setGridLevel(0);
+ });
+ assertThat(thrown.getMessage(), containsString("must be a positive integer"));
+ }
+
+ @Test
+ public void testStoresCellSizeWithCorrectKey() {
+ final HeatmapFacetMap heatmapFacet = new HeatmapFacetMap("ANY_FIELD_NAME")
+ .setGridLevel(42);
+ assertEquals(42, heatmapFacet.get("gridLevel"));
+ }
+
+ @Test
+ public void testRejectsInvalidDistanceError() {
+ final Throwable thrown = expectThrows(IllegalArgumentException.class, () -> {
+ new HeatmapFacetMap("ANY_FIELD_NAME")
+ .setDistErr(-1.0);
+ });
+ assertThat(thrown.getMessage(), containsString("must be non-negative"));
+ }
+
+ @Test
+ public void testStoresDistanceErrorWithCorrectKey() {
+ final HeatmapFacetMap heatmapFacet = new HeatmapFacetMap("ANY_FIELD_NAME")
+ .setDistErr(4.5);
+ assertEquals(4.5, heatmapFacet.get("distErr"));
+ }
+
+ @Test
+ public void testRejectsInvalidDistanceErrorPercentageWithCorrectKey() {
+ final Throwable thrown = expectThrows(IllegalArgumentException.class, () -> {
+ new HeatmapFacetMap("ANY_FIELD_NAME")
+ .setDistErrPct(2.0);
+ });
+ assertThat(thrown.getMessage(), containsString("must be between 0.0 and 1.0"));
+ }
+
+ @Test
+ public void testStoresDistanceErrorPercentageWithCorrectKey() {
+ final HeatmapFacetMap heatmapFacet = new HeatmapFacetMap("ANY_FIELD_NAME")
+ .setDistErrPct(0.45);
+ assertEquals(0.45, heatmapFacet.get("distErrPct"));
+ }
+
+ @Test
+ public void testRejectsInvalidHeatmapFormat() {
+ final Throwable thrown = expectThrows(IllegalArgumentException.class, () -> {
+ new HeatmapFacetMap("ANY_FIELD_NAME")
+ .setHeatmapFormat(null);
+ });
+ assertThat(thrown.getMessage(), containsString("must be non-null"));
+ }
+
+ @Test
+ public void testStoresHeatmapFormatWithCorrectKey() {
+ final HeatmapFacetMap heatmapFacet = new HeatmapFacetMap("ANY_FIELD_NAME")
+ .setHeatmapFormat(HeatmapFacetMap.HeatmapFormat.PNG);
+ assertEquals("png", heatmapFacet.get("format"));
+ }
+}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/b502ba28/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/JsonQueryRequestFacetingIntegrationTest.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/JsonQueryRequestFacetingIntegrationTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/JsonQueryRequestFacetingIntegrationTest.java
new file mode 100644
index 0000000..7717f7f
--- /dev/null
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/JsonQueryRequestFacetingIntegrationTest.java
@@ -0,0 +1,530 @@
+/*
+ * 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.
+ */
+
+package org.apache.solr.client.solrj.request.json;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.solr.client.solrj.request.AbstractUpdateRequest;
+import org.apache.solr.client.solrj.request.CollectionAdminRequest;
+import org.apache.solr.client.solrj.request.ContentStreamUpdateRequest;
+import org.apache.solr.client.solrj.response.QueryResponse;
+import org.apache.solr.client.solrj.response.UpdateResponse;
+import org.apache.solr.cloud.SolrCloudTestCase;
+import org.apache.solr.common.MapWriter;
+import org.apache.solr.common.SolrDocumentList;
+import org.apache.solr.common.util.NamedList;
+import org.apache.solr.util.ExternalPaths;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class JsonQueryRequestFacetingIntegrationTest extends SolrCloudTestCase {
+
+ private static final String COLLECTION_NAME = "techproducts";
+ private static final String CONFIG_NAME = "techproducts_config";
+ private static final int NUM_TECHPRODUCTS_DOCS = 32;
+ private static final int NUM_IN_STOCK = 17;
+ private static final int NUM_ELECTRONICS = 12;
+ private static final int NUM_CURRENCY = 4;
+ private static final int NUM_MEMORY = 3;
+ private static final int NUM_CORSAIR = 3;
+ private static final int NUM_BELKIN = 2;
+ private static final int NUM_CANON = 2;
+
+ @BeforeClass
+ public static void setupCluster() throws Exception {
+ configureCluster(1)
+ .addConfig(CONFIG_NAME, new File(ExternalPaths.TECHPRODUCTS_CONFIGSET).toPath())
+ .configure();
+
+ final List<String> solrUrls = new ArrayList<>();
+ solrUrls.add(cluster.getJettySolrRunner(0).getBaseUrl().toString());
+
+ CollectionAdminRequest.createCollection(COLLECTION_NAME, CONFIG_NAME, 1, 1).process(cluster.getSolrClient());
+
+ ContentStreamUpdateRequest up = new ContentStreamUpdateRequest("/update");
+ up.setParam("collection", COLLECTION_NAME);
+ up.addFile(getFile("solrj/techproducts.xml"), "application/xml");
+ up.setAction(AbstractUpdateRequest.ACTION.COMMIT, true, true);
+ UpdateResponse updateResponse = up.process(cluster.getSolrClient());
+ assertEquals(0, updateResponse.getStatus());
+ }
+
+ @Test
+ public void testSingleTermsFacet() throws Exception {
+ final TermsFacetMap categoriesFacetMap = new TermsFacetMap("cat")
+ .setLimit(3);
+ final JsonQueryRequest request = new JsonQueryRequest()
+ .setQuery("*:*")
+ .withFacet("top_cats", categoriesFacetMap);
+
+ QueryResponse response = request.process(cluster.getSolrClient(), COLLECTION_NAME);
+
+ assertEquals(0, response.getStatus());
+ final SolrDocumentList returnedDocs = response.getResults();
+ assertEquals(NUM_TECHPRODUCTS_DOCS, returnedDocs.getNumFound());
+ assertEquals(10, returnedDocs.size());
+ final NamedList<Object> rawResponse = response.getResponse();
+ assertHasFacetWithBucketValues(rawResponse,"top_cats",
+ new FacetBucket("electronics",NUM_ELECTRONICS),
+ new FacetBucket("currency", NUM_CURRENCY),
+ new FacetBucket("memory", NUM_MEMORY));
+ }
+
+ @Test
+ public void testFacetCanBeRepresentedByMapWriter() throws Exception {
+ final MapWriter categoriesFacet = new MapWriter() {
+ @Override
+ public void writeMap(EntryWriter ew) throws IOException {
+ ew.put("type", "terms");
+ ew.put("field", "cat");
+ ew.put("limit", 3);
+ }
+ };
+ final JsonQueryRequest request = new JsonQueryRequest()
+ .setQuery("*:*")
+ .withFacet("top_cats", categoriesFacet);
+
+ QueryResponse response = request.process(cluster.getSolrClient(), COLLECTION_NAME);
+
+ assertEquals(0, response.getStatus());
+ final SolrDocumentList returnedDocs = response.getResults();
+ assertEquals(NUM_TECHPRODUCTS_DOCS, returnedDocs.getNumFound());
+ assertEquals(10, returnedDocs.size());
+ final NamedList<Object> rawResponse = response.getResponse();
+ assertHasFacetWithBucketValues(rawResponse,"top_cats",
+ new FacetBucket("electronics",NUM_ELECTRONICS),
+ new FacetBucket("currency", NUM_CURRENCY),
+ new FacetBucket("memory", NUM_MEMORY));
+ }
+
+ @Test
+ public void testMultiTermsFacet() throws Exception {
+ final TermsFacetMap categoriesFacetMap = new TermsFacetMap("cat")
+ .setLimit(3);
+ final TermsFacetMap manufacturersFacetMap = new TermsFacetMap("manu_id_s")
+ .setLimit(3);
+ final JsonQueryRequest request = new JsonQueryRequest()
+ .setQuery("*:*")
+ .withFacet("top_cats", categoriesFacetMap)
+ .withFacet("top_manufacturers", manufacturersFacetMap);
+
+ QueryResponse response = request.process(cluster.getSolrClient(), COLLECTION_NAME);
+
+ assertEquals(0, response.getStatus());
+ final SolrDocumentList returnedDocs = response.getResults();
+ assertEquals(NUM_TECHPRODUCTS_DOCS, returnedDocs.getNumFound());
+ assertEquals(10, returnedDocs.size());
+ final NamedList<Object> rawResponse = response.getResponse();
+ assertHasFacetWithBucketValues(rawResponse,"top_cats",
+ new FacetBucket("electronics",NUM_ELECTRONICS),
+ new FacetBucket("currency", NUM_CURRENCY),
+ new FacetBucket("memory", NUM_MEMORY));
+ assertHasFacetWithBucketValues(rawResponse,"top_manufacturers",
+ new FacetBucket("corsair",NUM_CORSAIR),
+ new FacetBucket("belkin", NUM_BELKIN),
+ new FacetBucket("canon", NUM_CANON));
+ }
+
+ @Test
+ public void testSingleRangeFacet() throws Exception {
+ final RangeFacetMap pricesFacet = new RangeFacetMap("price", 0, 100, 20);
+ final JsonQueryRequest request = new JsonQueryRequest()
+ .setQuery("*:*")
+ .withFacet("prices", pricesFacet);
+
+ QueryResponse response = request.process(cluster.getSolrClient(), COLLECTION_NAME);
+
+ assertEquals(0, response.getStatus());
+ final SolrDocumentList returnedDocs = response.getResults();
+ assertEquals(NUM_TECHPRODUCTS_DOCS, returnedDocs.getNumFound());
+ assertEquals(10, returnedDocs.size());
+ final NamedList<Object> rawResponse = response.getResponse();
+ assertHasFacetWithBucketValues(rawResponse,"prices",
+ new FacetBucket(0.0f, 5),
+ new FacetBucket(20.0f, 0),
+ new FacetBucket(40.0f, 0),
+ new FacetBucket(60.0f, 1),
+ new FacetBucket(80.0f, 1));
+ }
+
+ @Test
+ public void testMultiRangeFacet() throws Exception {
+ final RangeFacetMap pricesFacet = new RangeFacetMap("price", 0, 100, 20);
+ final RangeFacetMap shippingWeightFacet = new RangeFacetMap("weight", 0, 200, 50);
+ final JsonQueryRequest request = new JsonQueryRequest()
+ .setQuery("*:*")
+ .withFacet("prices", pricesFacet)
+ .withFacet("shipping_weights", shippingWeightFacet);
+
+ QueryResponse response = request.process(cluster.getSolrClient(), COLLECTION_NAME);
+
+ assertEquals(0, response.getStatus());
+ final SolrDocumentList returnedDocs = response.getResults();
+ assertEquals(NUM_TECHPRODUCTS_DOCS, returnedDocs.getNumFound());
+ assertEquals(10, returnedDocs.size());
+ final NamedList<Object> rawResponse = response.getResponse();
+ assertHasFacetWithBucketValues(rawResponse,"prices",
+ new FacetBucket(0.0f, 5),
+ new FacetBucket(20.0f, 0),
+ new FacetBucket(40.0f, 0),
+ new FacetBucket(60.0f, 1),
+ new FacetBucket(80.0f, 1));
+ assertHasFacetWithBucketValues(rawResponse, "shipping_weights",
+ new FacetBucket(0.0f, 6),
+ new FacetBucket(50.0f, 0),
+ new FacetBucket(100.0f, 0),
+ new FacetBucket(150.0f,1));
+ }
+
+ @Test
+ public void testSingleStatFacet() throws Exception {
+ final JsonQueryRequest request = new JsonQueryRequest()
+ .setQuery("*:*")
+ .withStatFacet("sum_price", "sum(price)");
+
+ QueryResponse response = request.process(cluster.getSolrClient(), COLLECTION_NAME);
+
+ assertEquals(0, response.getStatus());
+ final SolrDocumentList returnedDocs = response.getResults();
+ assertEquals(NUM_TECHPRODUCTS_DOCS, returnedDocs.getNumFound());
+ assertEquals(10, returnedDocs.size());
+ final NamedList<Object> rawResponse = response.getResponse();
+ assertHasStatFacetWithValue(rawResponse,"sum_price", 5251.270030975342);
+ }
+
+ @Test
+ public void testMultiStatFacet() throws Exception {
+ final JsonQueryRequest request = new JsonQueryRequest()
+ .setQuery("*:*")
+ .withStatFacet("sum_price", "sum(price)")
+ .withStatFacet("avg_price", "avg(price)");
+
+ QueryResponse response = request.process(cluster.getSolrClient(), COLLECTION_NAME);
+
+ assertEquals(0, response.getStatus());
+ final SolrDocumentList returnedDocs = response.getResults();
+ assertEquals(NUM_TECHPRODUCTS_DOCS, returnedDocs.getNumFound());
+ assertEquals(10, returnedDocs.size());
+ final NamedList<Object> rawResponse = response.getResponse();
+ assertHasStatFacetWithValue(rawResponse,"sum_price", 5251.270030975342);
+ assertHasStatFacetWithValue(rawResponse,"avg_price", 328.20437693595886);
+ }
+
+ @Test
+ public void testMultiFacetsMixedTypes() throws Exception {
+ final TermsFacetMap categoryFacet = new TermsFacetMap("cat")
+ .setLimit(3);
+ final JsonQueryRequest request = new JsonQueryRequest()
+ .setQuery("*:*")
+ .withStatFacet("avg_price", "avg(price)")
+ .withFacet("top_cats", categoryFacet);
+
+ QueryResponse response = request.process(cluster.getSolrClient(), COLLECTION_NAME);
+
+ assertEquals(0, response.getStatus());
+ final SolrDocumentList returnedDocs = response.getResults();
+ assertEquals(NUM_TECHPRODUCTS_DOCS, returnedDocs.getNumFound());
+ assertEquals(10, returnedDocs.size());
+ final NamedList<Object> rawResponse = response.getResponse();
+ assertHasStatFacetWithValue(rawResponse,"avg_price", 328.20437693595886);
+ assertHasFacetWithBucketValues(rawResponse,"top_cats", new FacetBucket("electronics",NUM_ELECTRONICS),
+ new FacetBucket("currency", NUM_CURRENCY), new FacetBucket("memory", NUM_MEMORY));
+ }
+
+ @Test
+ public void testNestedTermsFacet() throws Exception {
+ final TermsFacetMap categoriesFacet = new TermsFacetMap("cat")
+ .setLimit(3)
+ .withSubFacet("top_manufacturers_for_cat", new TermsFacetMap("manu_id_s").setLimit(1));
+ final JsonQueryRequest request = new JsonQueryRequest()
+ .setQuery("*:*")
+ .withFacet("top_cats", categoriesFacet);
+
+ QueryResponse response = request.process(cluster.getSolrClient(), COLLECTION_NAME);
+
+ assertEquals(0, response.getStatus());
+ final SolrDocumentList returnedDocs = response.getResults();
+ assertEquals(NUM_TECHPRODUCTS_DOCS, returnedDocs.getNumFound());
+ assertEquals(10, returnedDocs.size());
+ final NamedList<Object> rawResponse = response.getResponse();
+ // Test top level facets
+ assertHasFacetWithBucketValues(rawResponse,"top_cats", new FacetBucket("electronics",NUM_ELECTRONICS),
+ new FacetBucket("currency", NUM_CURRENCY), new FacetBucket("memory", NUM_MEMORY));
+ // Test subfacet values for each top-level facet bucket
+ final List<NamedList<Object>> topLevelFacetResponse = (List<NamedList<Object>>) rawResponse.findRecursive("facets", "top_cats", "buckets");
+ final NamedList<Object> electronicsSubFacet = topLevelFacetResponse.get(0);
+ assertFacetResponseHasFacetWithBuckets(electronicsSubFacet, "top_manufacturers_for_cat", new FacetBucket("corsair", 3));
+ final NamedList<Object> currencySubfacet = topLevelFacetResponse.get(1);
+ assertFacetResponseHasFacetWithBuckets(currencySubfacet, "top_manufacturers_for_cat", new FacetBucket("boa", 1));
+ final NamedList<Object> memorySubfacet = topLevelFacetResponse.get(2);
+ assertFacetResponseHasFacetWithBuckets(memorySubfacet, "top_manufacturers_for_cat", new FacetBucket("corsair", 3));
+ }
+
+ @Test
+ public void testNestedFacetsOfMixedTypes() throws Exception {
+ final String subfacetName = "avg_price_for_cat";
+
+ final TermsFacetMap categoriesFacet = new TermsFacetMap("cat")
+ .setLimit(3)
+ .withStatSubFacet(subfacetName, "avg(price)");
+ final JsonQueryRequest request = new JsonQueryRequest()
+ .setQuery("*:*")
+ .withFacet("top_cats", categoriesFacet);
+
+ QueryResponse response = request.process(cluster.getSolrClient(), COLLECTION_NAME);
+
+ assertEquals(0, response.getStatus());
+ final SolrDocumentList returnedDocs = response.getResults();
+ assertEquals(NUM_TECHPRODUCTS_DOCS, returnedDocs.getNumFound());
+ assertEquals(10, returnedDocs.size());
+ final NamedList<Object> rawResponse = response.getResponse();
+ // Test top level facets
+ assertHasFacetWithBucketValues(rawResponse,"top_cats", new FacetBucket("electronics",NUM_ELECTRONICS),
+ new FacetBucket("currency", NUM_CURRENCY), new FacetBucket("memory", NUM_MEMORY));
+ // Test subfacet values for each top-level facet bucket
+ final List<NamedList<Object>> topLevelFacetResponse = (List<NamedList<Object>>) rawResponse.findRecursive("facets", "top_cats", "buckets");
+ final NamedList<Object> electronicsSubFacet = topLevelFacetResponse.get(0);
+ assertFacetResponseHasStatFacetWithValue(electronicsSubFacet, subfacetName, 252.02909261530095);
+ final NamedList<Object> currencySubfacet = topLevelFacetResponse.get(1);
+ assertFacetResponseHasStatFacetWithValue(currencySubfacet, subfacetName, 0.0);
+ final NamedList<Object> memorySubfacet = topLevelFacetResponse.get(2);
+ assertFacetResponseHasStatFacetWithValue(memorySubfacet, subfacetName, 129.99499893188477);
+ }
+
+ @Test
+ public void testFacetWithDomainFilteredBySimpleQueryString() throws Exception {
+ final TermsFacetMap popularCategoriesFacet = new TermsFacetMap("cat")
+ .setLimit(3)
+ .withDomain(new DomainMap()
+ .withFilter("popularity:[5 TO 10]"));
+ final JsonQueryRequest request = new JsonQueryRequest()
+ .setQuery("*:*")
+ .withFacet("top_popular_cats", popularCategoriesFacet);
+
+ QueryResponse response = request.process(cluster.getSolrClient(), COLLECTION_NAME);
+
+ assertEquals(0, response.getStatus());
+ final SolrDocumentList returnedDocs = response.getResults();
+ assertEquals(NUM_TECHPRODUCTS_DOCS, returnedDocs.getNumFound());
+ assertEquals(10, returnedDocs.size());
+ final NamedList<Object> rawResponse = response.getResponse();
+ assertHasFacetWithBucketValues(rawResponse,"top_popular_cats", new FacetBucket("electronics",9),
+ new FacetBucket("graphics card", 2), new FacetBucket("hard drive", 2));
+ }
+
+ @Test
+ public void testFacetWithDomainFilteredByLocalParamsQueryString() throws Exception {
+ final TermsFacetMap popularCategoriesFacet = new TermsFacetMap("cat")
+ .setLimit(3)
+ .withDomain(new DomainMap()
+ .withFilter("{!lucene df=\"popularity\" v=\"[5 TO 10]\"}"));
+
+ JsonQueryRequest request = new JsonQueryRequest()
+ .setQuery("*:*")
+ .withFacet("top_popular_cats", popularCategoriesFacet);
+
+ QueryResponse response = request.process(cluster.getSolrClient(), COLLECTION_NAME);
+
+ assertEquals(0, response.getStatus());
+ final SolrDocumentList returnedDocs = response.getResults();
+ assertEquals(NUM_TECHPRODUCTS_DOCS, returnedDocs.getNumFound());
+ assertEquals(10, returnedDocs.size());
+ final NamedList<Object> rawResponse = response.getResponse();
+ assertHasFacetWithBucketValues(rawResponse,"top_popular_cats", new FacetBucket("electronics",9),
+ new FacetBucket("graphics card", 2), new FacetBucket("hard drive", 2));
+ }
+
+ @Test
+ public void testFacetWithArbitraryDomainFromQueryString() throws Exception {
+ final TermsFacetMap categoriesFacet = new TermsFacetMap("cat")
+ .setLimit(3)
+ .withDomain(new DomainMap()
+ .withQuery("*:*"));
+ final JsonQueryRequest request = new JsonQueryRequest()
+ .setQuery("cat:electronics")
+ .withFacet("top_cats", categoriesFacet);
+
+ QueryResponse response = request.process(cluster.getSolrClient(), COLLECTION_NAME);
+
+ assertEquals(0, response.getStatus());
+ final SolrDocumentList returnedDocs = response.getResults();
+ assertEquals(NUM_ELECTRONICS, returnedDocs.getNumFound());
+ assertEquals(10, returnedDocs.size());
+ final NamedList<Object> rawResponse = response.getResponse();
+ assertHasFacetWithBucketValues(rawResponse,"top_cats", new FacetBucket("electronics",NUM_ELECTRONICS),
+ new FacetBucket("currency", NUM_CURRENCY), new FacetBucket("memory", NUM_MEMORY));
+ }
+
+ @Test
+ public void testFacetWithArbitraryDomainFromLocalParamsQuery() throws Exception {
+ final TermsFacetMap searchCategoriesFacet = new TermsFacetMap("cat")
+ .withDomain(new DomainMap()
+ .withQuery("{!lucene df=\"cat\" v=\"search\"}"));
+ final JsonQueryRequest request = new JsonQueryRequest()
+ .setQuery("cat:electronics")
+ .withFacet("largest_search_cats", searchCategoriesFacet);
+
+ QueryResponse response = request.process(cluster.getSolrClient(), COLLECTION_NAME);
+
+ assertEquals(0, response.getStatus());
+ final SolrDocumentList returnedDocs = response.getResults();
+ assertEquals(NUM_ELECTRONICS, returnedDocs.getNumFound());
+ assertEquals(10, returnedDocs.size());
+ final NamedList<Object> rawResponse = response.getResponse();
+ assertHasFacetWithBucketValues(rawResponse,"largest_search_cats",
+ new FacetBucket("search",2),
+ new FacetBucket("software", 2));
+ }
+
+ public void testFacetWithMultipleSimpleQueryClausesInArbitraryDomain() throws Exception {
+ final TermsFacetMap solrCategoriesFacet = new TermsFacetMap("cat")
+ .withDomain(new DomainMap()
+ .withQuery("cat:search")
+ .withQuery("name:Solr"));
+ final JsonQueryRequest request = new JsonQueryRequest()
+ .setQuery("cat:electronics")
+ .withFacet("cats_matching_solr", solrCategoriesFacet);
+
+ QueryResponse response = request.process(cluster.getSolrClient(), COLLECTION_NAME);
+
+ assertEquals(0, response.getStatus());
+ final SolrDocumentList returnedDocs = response.getResults();
+ assertEquals(NUM_ELECTRONICS, returnedDocs.getNumFound());
+ assertEquals(10, returnedDocs.size());
+ final NamedList<Object> rawResponse = response.getResponse();
+ assertHasFacetWithBucketValues(rawResponse,"cats_matching_solr",
+ new FacetBucket("search",1),
+ new FacetBucket("software", 1));
+ }
+
+ public void testFacetWithMultipleLocalParamsQueryClausesInArbitraryDomain() throws Exception {
+ final TermsFacetMap solrCategoriesFacet = new TermsFacetMap("cat")
+ .withDomain(new DomainMap()
+ .withQuery("{!lucene df=\"cat\" v=\"search\"}")
+ .withQuery("{!lucene df=\"name\" v=\"Solr\"}"));
+ final JsonQueryRequest request = new JsonQueryRequest()
+ .setQuery("cat:electronics")
+ .withFacet("cats_matching_solr", solrCategoriesFacet);
+
+ QueryResponse response = request.process(cluster.getSolrClient(), COLLECTION_NAME);
+
+ assertEquals(0, response.getStatus());
+ final SolrDocumentList returnedDocs = response.getResults();
+ assertEquals(NUM_ELECTRONICS, returnedDocs.getNumFound());
+ assertEquals(10, returnedDocs.size());
+ final NamedList<Object> rawResponse = response.getResponse();
+ assertHasFacetWithBucketValues(rawResponse,"cats_matching_solr",
+ new FacetBucket("search",1),
+ new FacetBucket("software", 1));
+ }
+
+ @Test
+ public void testFacetWithDomainWidenedUsingExcludeTagsToIgnoreFilters() throws Exception {
+ final TermsFacetMap inStockFacet = new TermsFacetMap("cat")
+ .setLimit(2);
+ final TermsFacetMap allProductsFacet = new TermsFacetMap("cat")
+ .setLimit(2).withDomain(new DomainMap().withTagsToExclude("on_shelf"));
+ final Map<String, Object> taggedFilterMap = new HashMap<>();
+ taggedFilterMap.put("#on_shelf", "inStock:true");
+ final JsonQueryRequest request = new JsonQueryRequest()
+ .setQuery("*:*")
+ .withFilter(taggedFilterMap)
+ .withFacet("in_stock_only", inStockFacet)
+ .withFacet("all", allProductsFacet);
+
+ QueryResponse response = request.process(cluster.getSolrClient(), COLLECTION_NAME);
+
+ assertEquals(0, response.getStatus());
+ final SolrDocumentList returnedDocs = response.getResults();
+ assertEquals(NUM_IN_STOCK, returnedDocs.getNumFound());
+ assertEquals(10, returnedDocs.size());
+ final NamedList<Object> rawResponse = response.getResponse();
+ assertHasFacetWithBucketValues(rawResponse,"in_stock_only",
+ new FacetBucket("electronics",8),
+ new FacetBucket("currency", 4));
+ assertHasFacetWithBucketValues(rawResponse,"all",
+ new FacetBucket("electronics",12),
+ new FacetBucket("currency", 4));
+ }
+
+ private class FacetBucket {
+ private final Object val;
+ private final int count;
+ FacetBucket(Object val, int count) {
+ this.val = val;
+ this.count = count;
+ }
+
+ public Object getVal() { return val; }
+ public int getCount() { return count; }
+ }
+
+ private void assertHasFacetWithBucketValues(NamedList<Object> rawResponse, String expectedFacetName, FacetBucket... expectedBuckets) {
+ final NamedList<Object> facetsTopLevel = assertHasFacetResponse(rawResponse);
+ assertFacetResponseHasFacetWithBuckets(facetsTopLevel, expectedFacetName, expectedBuckets);
+ }
+
+ private void assertHasStatFacetWithValue(NamedList<Object> rawResponse, String expectedFacetName, Double expectedStatValue) {
+ final NamedList<Object> facetsTopLevel = assertHasFacetResponse(rawResponse);
+ assertFacetResponseHasStatFacetWithValue(facetsTopLevel, expectedFacetName, expectedStatValue);
+ }
+
+ private NamedList<Object> assertHasFacetResponse(NamedList<Object> topLevelResponse) {
+ Object o = topLevelResponse.get("facets");
+ if (o == null) fail("Response has no top-level 'facets' property as expected");
+ if (!(o instanceof NamedList)) fail("Response has a top-level 'facets' property, but it is not a NamedList");
+
+ return (NamedList<Object>) o;
+ }
+
+ private void assertFacetResponseHasFacetWithBuckets(NamedList<Object> facetResponse, String expectedFacetName, FacetBucket... expectedBuckets) {
+ Object o = facetResponse.get(expectedFacetName);
+ if (o == null) fail("Response has no top-level facet named '" + expectedFacetName + "'");
+ if (!(o instanceof NamedList)) fail("Response has a property for the expected facet '" + expectedFacetName + "' property, but it is not a NamedList");
+
+ final NamedList<Object> expectedFacetTopLevel = (NamedList<Object>) o;
+ o = expectedFacetTopLevel.get("buckets");
+ if (o == null) fail("Response has no 'buckets' property under 'facets'");
+ if (!(o instanceof List)) fail("Response has no 'buckets' property containing actual facet information.");
+
+ final List<NamedList> bucketList = (List<NamedList>) o;
+ assertEquals("Expected " + expectedBuckets.length + " buckets, but found " + bucketList.size(),
+ expectedBuckets.length, bucketList.size());
+ for (int i = 0; i < expectedBuckets.length; i++) {
+ final FacetBucket expectedBucket = expectedBuckets[i];
+ final NamedList<Object> actualBucket = bucketList.get(i);
+ assertEquals(expectedBucket.getVal(), actualBucket.get("val"));
+ assertEquals(expectedBucket.getCount(), actualBucket.get("count"));
+ }
+ }
+
+ private void assertFacetResponseHasStatFacetWithValue(NamedList<Object> facetResponse, String expectedFacetName, Double expectedStatValue) {
+ Object o = facetResponse.get(expectedFacetName);
+ if (o == null) fail("Response has no top-level facet named '" + expectedFacetName + "'");
+ if (!(o instanceof Number)) fail("Response has a property for the expected facet '" + expectedFacetName + "' property, but it is not a Number");
+
+ final Number actualStatValueAsNumber = (Number) o;
+ final Double actualStatValueAsDouble = ((Number) o).doubleValue();
+ assertEquals(expectedStatValue, actualStatValueAsDouble, 0.5);
+ }
+}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/b502ba28/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/JsonQueryRequestUnitTest.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/JsonQueryRequestUnitTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/JsonQueryRequestUnitTest.java
index 84ef956..6e8c647 100644
--- a/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/JsonQueryRequestUnitTest.java
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/JsonQueryRequestUnitTest.java
@@ -25,7 +25,6 @@ import java.util.Map;
import org.apache.lucene.util.LuceneTestCase;
import org.apache.solr.client.solrj.request.RequestWriter;
-import org.apache.solr.client.solrj.request.json.JsonQueryRequest;
import org.apache.solr.client.solrj.util.ClientUtils;
import org.apache.solr.common.MapWriter;
import org.junit.Test;
@@ -98,6 +97,91 @@ public class JsonQueryRequestUnitTest extends LuceneTestCase {
}
@Test
+ public void testRejectsInvalidFacetName() {
+ Throwable thrown = expectThrows(IllegalArgumentException.class, () -> {
+ new JsonQueryRequest().withFacet(null, new HashMap<>());
+ });
+ assertThat(thrown.getMessage(),containsString("must be non-null"));
+
+ thrown = expectThrows(IllegalArgumentException.class, () -> {
+ new JsonQueryRequest().withStatFacet(null, "avg(price)");
+ });
+ assertThat(thrown.getMessage(),containsString("must be non-null"));
+ }
+
+ @Test
+ public void testRejectsInvalidFacetMap() {
+ Throwable thrown = expectThrows(IllegalArgumentException.class, () -> {
+ new JsonQueryRequest().withFacet("anyFacetName", (Map<String, Object>)null);
+ });
+ assertThat(thrown.getMessage(),containsString("must be non-null"));
+ }
+
+ @Test
+ public void testRejectsNullFacetMapWriter() {
+ Throwable thrown = expectThrows(IllegalArgumentException.class, () -> {
+ new JsonQueryRequest().withFacet("anyFacetName", (MapWriter)null);
+ });
+ assertThat(thrown.getMessage(),containsString("must be non-null"));
+ }
+
+ @Test
+ public void testRejectsInvalidStatFacetString() {
+ Throwable thrown = expectThrows(IllegalArgumentException.class, () -> {
+ new JsonQueryRequest().withStatFacet("anyFacetName", (String)null);
+ });
+ assertThat(thrown.getMessage(),containsString("must be non-null"));
+ }
+
+ @Test
+ public void testWritesProvidedFacetMapToJsonCorrectly() {
+ final Map<String, Object> categoryFacetMap = new HashMap<>();
+ categoryFacetMap.put("type", "terms");
+ categoryFacetMap.put("field", "category");
+ final JsonQueryRequest request = new JsonQueryRequest().withFacet("top_categories", categoryFacetMap);
+ final String requestBody = writeRequestToJson(request);
+ assertThat(requestBody, containsString("\"facet\":{\"top_categories\":{\"field\":\"category\",\"type\":\"terms\"}}"));
+ }
+
+ @Test
+ public void testWritesProvidedFacetMapWriterToJsonCorrectly() {
+ final MapWriter facetWriter = new MapWriter() {
+ @Override
+ public void writeMap(EntryWriter ew) throws IOException {
+ ew.put("type", "terms");
+ ew.put("field", "category");
+ }
+ };
+ final JsonQueryRequest request = new JsonQueryRequest().withFacet("top_categories", facetWriter);
+ final String requestBody = writeRequestToJson(request);
+ assertThat(requestBody, containsString("\"facet\":{\"top_categories\":{\"type\":\"terms\",\"field\":\"category\"}}"));
+ }
+
+ @Test
+ public void testWritesProvidedStatFacetToJsonCorrectly() {
+ final JsonQueryRequest request = new JsonQueryRequest().withStatFacet("avg_price", "avg(price)");
+ final String requestBody = writeRequestToJson(request);
+ assertThat(requestBody, containsString("\"facet\":{\"avg_price\":\"avg(price)\"}"));
+ }
+
+ @Test
+ public void testWritesMultipleFacetMapsToJsonCorrectly() {
+ final Map<String, Object> facetMap1 = new HashMap<>();
+ facetMap1.put("type", "terms");
+ facetMap1.put("field", "a");
+ final Map<String, Object> facetMap2 = new HashMap<>();
+ facetMap2.put("type", "terms");
+ facetMap2.put("field", "b");
+ final JsonQueryRequest request = new JsonQueryRequest();
+
+ request.withFacet("facet1", facetMap1);
+ request.withFacet("facet2", facetMap2);
+ final String requestBody = writeRequestToJson(request);
+
+ assertThat(requestBody, containsString("\"facet\":{\"facet2\":{\"field\":\"b\",\"type\":\"terms\"},\"facet1\":{\"field\":\"a\",\"type\":\"terms\"}}"));
+ }
+
+ @Test
public void testRejectsInvalidLimit() {
Throwable thrown = expectThrows(IllegalArgumentException.class, () -> {
new JsonQueryRequest().setLimit(-1);
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/b502ba28/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/QueryFacetMapTest.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/QueryFacetMapTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/QueryFacetMapTest.java
new file mode 100644
index 0000000..8d07a4f
--- /dev/null
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/QueryFacetMapTest.java
@@ -0,0 +1,45 @@
+/*
+ * 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.
+ */
+
+package org.apache.solr.client.solrj.request.json;
+
+import org.apache.solr.SolrTestCaseJ4;
+import org.junit.Test;
+
+import static org.junit.internal.matchers.StringContains.containsString;
+
+public class QueryFacetMapTest extends SolrTestCaseJ4 {
+ @Test
+ public void testSetsFacetTypeToQuery() {
+ final QueryFacetMap queryFacet = new QueryFacetMap("any:query");
+ assertEquals("query", queryFacet.get("type"));
+ }
+
+ @Test
+ public void testRejectsInvalidQueryString() {
+ final Throwable thrown = expectThrows(IllegalArgumentException.class, () -> {
+ final QueryFacetMap queryFacet = new QueryFacetMap(null);
+ });
+ assertThat(thrown.getMessage(), containsString("must be non-null"));
+ }
+
+ @Test
+ public void testSetsQueryWithCorrectKey() {
+ final QueryFacetMap queryFacet = new QueryFacetMap("any:query");
+ assertEquals("any:query", queryFacet.get("q"));
+ }
+}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/b502ba28/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/RangeFacetMapTest.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/RangeFacetMapTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/RangeFacetMapTest.java
new file mode 100644
index 0000000..43bb5f5
--- /dev/null
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/RangeFacetMapTest.java
@@ -0,0 +1,84 @@
+/*
+ * 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.
+ */
+
+package org.apache.solr.client.solrj.request.json;
+
+import org.apache.solr.SolrTestCaseJ4;
+import org.junit.Test;
+
+import static org.junit.internal.matchers.StringContains.containsString;
+
+/**
+ * Unit tests for {@link RangeFacetMap}
+ */
+public class RangeFacetMapTest extends SolrTestCaseJ4 {
+ @Test
+ public void testRejectsInvalidFieldName() {
+ final Throwable thrown = expectThrows(IllegalArgumentException.class, () -> {
+ new RangeFacetMap(null, 1, 2, 3);
+ });
+ assertThat(thrown.getMessage(), containsString("must be non-null"));
+ }
+
+ @Test
+ public void testRejectsInvalidStartEndBounds() {
+ final Throwable thrown = expectThrows(IllegalArgumentException.class, () -> {
+ new RangeFacetMap("ANY_FIELD_NAME", 1, -1, 3);
+ });
+ assertThat(thrown.getMessage(), containsString("'end' must be greater than parameter 'start'"));
+ }
+
+ @Test
+ public void testRejectsInvalidGap() {
+ final Throwable thrown = expectThrows(IllegalArgumentException.class, () -> {
+ new RangeFacetMap("ANY_FIELD_NAME", 1, 2, -1);
+ });
+ assertThat(thrown.getMessage(), containsString("must be a positive integer"));
+ }
+
+ @Test
+ public void testStoresRequiredValuesWithCorrectKeys() {
+ final RangeFacetMap rangeFacet = new RangeFacetMap("ANY_FIELD_NAME", 1, 2, 3);
+ assertEquals("ANY_FIELD_NAME", rangeFacet.get("field"));
+ assertEquals(1L, rangeFacet.get("start"));
+ assertEquals(2L, rangeFacet.get("end"));
+ assertEquals(3L, rangeFacet.get("gap"));
+ }
+
+ @Test
+ public void testStoresHardEndWithCorrectKey() {
+ final RangeFacetMap rangeFacet = new RangeFacetMap("ANY_FIELD_NAME", 1, 2, 3)
+ .setHardEnd(true);
+ assertEquals(true, rangeFacet.get("hardend"));
+ }
+
+ @Test
+ public void testRejectsInvalidOtherBuckets() {
+ final Throwable thrown = expectThrows(IllegalArgumentException.class, () -> {
+ new RangeFacetMap("ANY_FIELD_NAME", 1, 2, 3)
+ .setOtherBuckets(null);
+ });
+ assertThat(thrown.getMessage(), containsString("must be non-null"));
+ }
+
+ @Test
+ public void testStoresOtherBucketsValueWithCorrectKey() {
+ final RangeFacetMap rangeFacet = new RangeFacetMap("ANY_FIELD_NAME", 1, 2, 3)
+ .setOtherBuckets(RangeFacetMap.OtherBuckets.BETWEEN);
+ assertEquals("between", rangeFacet.get("other"));
+ }
+}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/b502ba28/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/TermsFacetMapTest.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/TermsFacetMapTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/TermsFacetMapTest.java
new file mode 100644
index 0000000..58f807d
--- /dev/null
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/TermsFacetMapTest.java
@@ -0,0 +1,189 @@
+/*
+ * 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.
+ */
+
+package org.apache.solr.client.solrj.request.json;
+
+import org.apache.solr.SolrTestCaseJ4;
+import org.junit.Test;
+import static org.junit.internal.matchers.StringContains.containsString;
+
+
+public class TermsFacetMapTest extends SolrTestCaseJ4 {
+ private static final String ANY_FIELD_NAME = "ANY_FIELD_NAME";
+
+ @Test
+ public void testSetsFacetTypeToTerm() {
+ final TermsFacetMap termsFacet = new TermsFacetMap(ANY_FIELD_NAME);
+ assertEquals("terms", termsFacet.get("type"));
+ }
+
+ @Test
+ public void testStoresFieldWithCorrectKey() {
+ final TermsFacetMap termsFacet = new TermsFacetMap(ANY_FIELD_NAME);
+ assertEquals(ANY_FIELD_NAME, termsFacet.get("field"));
+ }
+
+ @Test
+ public void testRejectsNegativeBucketOffset() {
+ final Throwable thrown = expectThrows(IllegalArgumentException.class, () -> {
+ final TermsFacetMap termsFacet = new TermsFacetMap(ANY_FIELD_NAME)
+ .setBucketOffset(-1);
+ });
+ assertThat(thrown.getMessage(), containsString("must be non-negative"));
+ }
+
+ @Test
+ public void testStoresBucketOffsetWithCorrectKey() {
+ final TermsFacetMap termsFacet = new TermsFacetMap(ANY_FIELD_NAME)
+ .setBucketOffset(2);
+ assertEquals(2, termsFacet.get("offset"));
+
+ }
+
+ @Test
+ public void testRejectsNegativeBucketLimit() {
+ final Throwable thrown = expectThrows(IllegalArgumentException.class, () -> {
+ final TermsFacetMap termsFacet = new TermsFacetMap(ANY_FIELD_NAME)
+ .setLimit(-1);
+ });
+ assertThat(thrown.getMessage(), containsString("must be non-negative"));
+ }
+
+ @Test
+ public void testStoresBucketLimitWithCorrectKey() {
+ final TermsFacetMap termsFacet = new TermsFacetMap(ANY_FIELD_NAME)
+ .setLimit(3);
+ assertEquals(3, termsFacet.get("limit"));
+ }
+
+ @Test
+ public void testRejectsInvalidSortString() {
+ final Throwable thrown = expectThrows(IllegalArgumentException.class, () -> {
+ final TermsFacetMap termsFacet = new TermsFacetMap(ANY_FIELD_NAME)
+ .setSort(null);
+ });
+ assertThat(thrown.getMessage(), containsString("must be non-null"));
+ }
+
+ @Test
+ public void testStoresSortWithCorrectKey() {
+ final TermsFacetMap termsFacet = new TermsFacetMap(ANY_FIELD_NAME)
+ .setSort("price asc");
+ assertEquals("price asc", termsFacet.get("sort"));
+ }
+
+ @Test
+ public void testRejectInvalidOverRequestBuckets() {
+ final Throwable thrown = expectThrows(IllegalArgumentException.class, () -> {
+ final TermsFacetMap termsFacet = new TermsFacetMap(ANY_FIELD_NAME)
+ .setOverRequest(-2);
+ });
+ assertThat(thrown.getMessage(), containsString("must be >= -1"));
+ }
+
+ @Test
+ public void testStoresOverRequestBucketsWithCorrectKey() {
+ final TermsFacetMap termsFacet = new TermsFacetMap(ANY_FIELD_NAME)
+ .setOverRequest(4);
+ assertEquals(4, termsFacet.get("overrequest"));
+ }
+
+ @Test
+ public void testStoresRefinementFlagWithCorrectKey() {
+ final TermsFacetMap termsFacet = new TermsFacetMap(ANY_FIELD_NAME)
+ .useDistributedFacetRefining(true);
+ assertEquals(true, termsFacet.get("refine"));
+ }
+
+ @Test
+ public void testRejectInvalidOverRefineBuckets() {
+ final Throwable thrown = expectThrows(IllegalArgumentException.class, () -> {
+ final TermsFacetMap termsFacet = new TermsFacetMap(ANY_FIELD_NAME)
+ .setOverRefine(-2);
+ });
+ assertThat(thrown.getMessage(), containsString("must be >= -1"));
+ }
+
+ @Test
+ public void testStoresOverRefineBucketsWithCorrectKey() {
+ final TermsFacetMap termsFacet = new TermsFacetMap(ANY_FIELD_NAME)
+ .setOverRefine(5);
+ assertEquals(5, termsFacet.get("overrefine"));
+ }
+
+ @Test
+ public void testRejectInvalidMinCount() {
+ final Throwable thrown = expectThrows(IllegalArgumentException.class, () -> {
+ final TermsFacetMap termsFacet = new TermsFacetMap(ANY_FIELD_NAME)
+ .setMinCount(0);
+ });
+ assertThat(thrown.getMessage(), containsString("must be a positive integer"));
+ }
+
+ @Test
+ public void testStoresMinCountWithCorrectKey() {
+ final TermsFacetMap termsFacet = new TermsFacetMap(ANY_FIELD_NAME)
+ .setMinCount(6);
+ assertEquals(6, termsFacet.get("mincount"));
+ }
+
+ @Test
+ public void testStoresNumBucketsFlagWithCorrectKey() {
+ final TermsFacetMap termsFacet = new TermsFacetMap(ANY_FIELD_NAME)
+ .includeTotalNumBuckets(true);
+ assertEquals(true, termsFacet.get("numBuckets"));
+ }
+
+ @Test
+ public void testStoresAllBucketsFlagWithCorrectKey() {
+ final TermsFacetMap termsFacet = new TermsFacetMap(ANY_FIELD_NAME)
+ .includeAllBucketsUnionBucket(true);
+ assertEquals(true, termsFacet.get("allBuckets"));
+ }
+
+ @Test
+ public void testRejectInvalidTermPrefix() {
+ final Throwable thrown = expectThrows(IllegalArgumentException.class, () -> {
+ final TermsFacetMap termsFacet = new TermsFacetMap(ANY_FIELD_NAME)
+ .setTermPrefix(null);
+ });
+ assertThat(thrown.getMessage(), containsString("must be non-null"));
+ }
+
+ @Test
+ public void testStoresTermPrefixWithCorrectKey() {
+ final TermsFacetMap termsFacet = new TermsFacetMap(ANY_FIELD_NAME)
+ .setTermPrefix("ANY_PREF");
+ assertEquals("ANY_PREF", termsFacet.get("prefix"));
+ }
+
+ @Test
+ public void testRejectsInvalidMethod() {
+ final Throwable thrown = expectThrows(IllegalArgumentException.class, () -> {
+ final TermsFacetMap termsFacet = new TermsFacetMap(ANY_FIELD_NAME)
+ .setFacetMethod(null);
+ });
+ assertThat(thrown.getMessage(), containsString("must be non-null"));
+ }
+
+ @Test
+ public void testStoresMethodWithCorrectKey() {
+ final TermsFacetMap termsFacet = new TermsFacetMap(ANY_FIELD_NAME)
+ .setFacetMethod(TermsFacetMap.FacetMethod.STREAM);
+ assertEquals("stream", termsFacet.get("method"));
+ }
+}
[2/2] lucene-solr:branch_7x: SOLR-12965: Add facet support to
JsonQueryRequest
Posted by ge...@apache.org.
SOLR-12965: Add facet support to JsonQueryRequest
Project: http://git-wip-us.apache.org/repos/asf/lucene-solr/repo
Commit: http://git-wip-us.apache.org/repos/asf/lucene-solr/commit/b502ba28
Tree: http://git-wip-us.apache.org/repos/asf/lucene-solr/tree/b502ba28
Diff: http://git-wip-us.apache.org/repos/asf/lucene-solr/diff/b502ba28
Branch: refs/heads/branch_7x
Commit: b502ba2882a86958ef8769c3cb2fd65cf2d9c7e1
Parents: 6faddfe
Author: Jason Gerlowski <ge...@apache.org>
Authored: Sat Nov 10 19:48:50 2018 -0500
Committer: Jason Gerlowski <ge...@apache.org>
Committed: Wed Nov 14 15:19:06 2018 -0500
----------------------------------------------------------------------
solr/solr-ref-guide/src/json-facet-api.adoc | 36 ++
.../client/solrj/request/json/DomainMap.java | 139 +++++
.../solrj/request/json/HeatmapFacetMap.java | 137 +++++
.../client/solrj/request/json/JsonFacetMap.java | 62 ++
.../solrj/request/json/JsonQueryRequest.java | 109 ++++
.../solrj/request/json/QueryFacetMap.java | 39 ++
.../solrj/request/json/RangeFacetMap.java | 105 ++++
.../solrj/request/json/TermsFacetMap.java | 204 ++++++
.../solrj/src/test-files/solrj/techproducts.xml | 421 +++++++++++++
.../ref_guide_examples/JsonRequestApiTest.java | 98 ++-
...JsonQueryRequestFacetingIntegrationTest.java | 615 +++++++++++++++++++
.../solrj/request/json/DomainMapTest.java | 177 ++++++
.../solrj/request/json/HeatmapFacetMapTest.java | 130 ++++
...JsonQueryRequestFacetingIntegrationTest.java | 530 ++++++++++++++++
.../request/json/JsonQueryRequestUnitTest.java | 86 ++-
.../solrj/request/json/QueryFacetMapTest.java | 45 ++
.../solrj/request/json/RangeFacetMapTest.java | 84 +++
.../solrj/request/json/TermsFacetMapTest.java | 189 ++++++
18 files changed, 3202 insertions(+), 4 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/b502ba28/solr/solr-ref-guide/src/json-facet-api.adoc
----------------------------------------------------------------------
diff --git a/solr/solr-ref-guide/src/json-facet-api.adoc b/solr/solr-ref-guide/src/json-facet-api.adoc
index d842517..fd40538 100644
--- a/solr/solr-ref-guide/src/json-facet-api.adoc
+++ b/solr/solr-ref-guide/src/json-facet-api.adoc
@@ -1,5 +1,7 @@
= JSON Facet API
:page-tocclass: right
+:solr-root-path: ../../
+:example-source-dir: {solr-root-path}solrj/src/test/org/apache/solr/client/ref_guide_examples/
// 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
@@ -74,6 +76,11 @@ The response to the facet request above will start with documents matching the r
Here's an example of a bucketing facet, that partitions documents into bucket based on the `cat` field (short for category), and returns the top 3 buckets:
+[.dynamic-tabs]
+--
+[example.tab-pane#curljsonsimpletermsfacet]
+====
+[.tab-label]*curl*
[source,bash]
----
curl http://localhost:8983/solr/techproducts/query -d 'q=*:*&
@@ -85,6 +92,18 @@ json.facet={
}
}'
----
+====
+
+[example.tab-pane#solrjjsonsimpletermsfacet]
+====
+[.tab-label]*SolrJ*
+
+[source,java,indent=0]
+----
+include::{example-source-dir}JsonRequestApiTest.java[tag=solrj-json-simple-terms-facet]
+----
+====
+--
The response below shows us that 32 documents match the default root domain. and 12 documents have `cat:electronics`, 4 documents have `cat:currency`, etc.
@@ -132,6 +151,11 @@ curl http://localhost:8983/solr/techproducts/query -d 'q=*:*&json.facet=
Another option is to use the JSON Request API to provide the entire request in JSON:
+[.dynamic-tabs]
+--
+[example.tab-pane#curljsontermsfacet2]
+====
+[.tab-label]*curl*
[source,bash]
----
curl http://localhost:8983/solr/techproducts/query -d '
@@ -144,6 +168,18 @@ curl http://localhost:8983/solr/techproducts/query -d '
}
'
----
+====
+
+[example.tab-pane#solrjjsontermsfacet2]
+====
+[.tab-label]*SolrJ*
+
+[source,java,indent=0]
+----
+include::{example-source-dir}JsonRequestApiTest.java[tag=solrj-json-terms-facet2]
+----
+====
+--
=== JSON Extensions
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/b502ba28/solr/solrj/src/java/org/apache/solr/client/solrj/request/json/DomainMap.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/request/json/DomainMap.java b/solr/solrj/src/java/org/apache/solr/client/solrj/request/json/DomainMap.java
new file mode 100644
index 0000000..c23cee9
--- /dev/null
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/request/json/DomainMap.java
@@ -0,0 +1,139 @@
+/*
+ * 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.
+ */
+
+package org.apache.solr.client.solrj.request.json;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class DomainMap extends HashMap<String, Object> {
+
+ /**
+ * Indicates that the domain should be narrowed by the specified filter
+ *
+ * May be called multiple times. Each added filter is retained and used to narrow the domain.
+ */
+ public DomainMap withFilter(String filter) {
+ if (filter == null) {
+ throw new IllegalArgumentException("Parameter 'filter' must be non-null");
+ }
+
+ if (! containsKey("filter")) {
+ put("filter", new ArrayList<String>());
+ }
+
+ final List<String> filterList = (List<String>) get("filter");
+ filterList.add(filter);
+ return this;
+ }
+
+ /**
+ * Indicates that the domain should be the following query
+ *
+ * May be called multiple times. Each specified query is retained and included in the domain.
+ */
+ public DomainMap withQuery(String query) {
+ if (query == null) {
+ throw new IllegalArgumentException("Parameter 'query' must be non-null");
+ }
+
+ if (! containsKey("query")) {
+ put("query", new ArrayList<String>());
+ }
+
+ final List<String> queryList = (List<String>) get("query");
+ queryList.add(query);
+ return this;
+ }
+
+ /**
+ * Provide a tag or tags that correspond to filters or queries to exclude from the domain
+ *
+ * May be called multiple times. Each exclude-string is retained and used for removing queries/filters from the
+ * domain specification.
+ *
+ * @param excludeTagsValue a comma-delimited String containing filter/query tags to exclude
+ */
+ public DomainMap withTagsToExclude(String excludeTagsValue) {
+ if (excludeTagsValue == null) {
+ throw new IllegalArgumentException("Parameter 'excludeTagValue' must be non-null");
+ }
+
+ if (! containsKey("excludeTags")) {
+ put("excludeTags", new ArrayList<String>());
+ }
+
+ final List<String> excludeTagsList = (List<String>) get("excludeTags");
+ excludeTagsList.add(excludeTagsValue);
+ return this;
+ }
+
+ /**
+ * Indicates that the resulting domain will contain all parent documents of the children in the existing domain
+ *
+ * @param allParentsQuery a query used to identify all parent documents in the collection
+ */
+ public DomainMap setBlockParentQuery(String allParentsQuery) {
+ if (allParentsQuery == null) {
+ throw new IllegalArgumentException("Parameter 'allParentsQuery' must be non-null");
+ }
+
+ put("blockParent", allParentsQuery);
+ return this;
+ }
+
+ /**
+ * Indicates that the resulting domain will contain all child documents of the parents in the current domain
+ *
+ * @param allChildrenQuery a query used to identify all child documents in the collection
+ */
+ public DomainMap setBlockChildQuery(String allChildrenQuery) {
+ if (allChildrenQuery == null) {
+ throw new IllegalArgumentException("Parameter 'allChildrenQuery' must be non-null");
+ }
+
+ put("blockChildren", allChildrenQuery);
+ return this;
+ }
+
+ /**
+ * Transforms the domain by running a join query with the provided {@code from} and {@code to} parameters
+ *
+ * Join modifies the current domain by selecting the documents whose values in field {@code to} match values for the
+ * field {@code from} in the current domain.
+ *
+ * @param from a field-name whose values are matched against {@code to} by the join
+ * @param to a field name whose values should match values specified by the {@code from} field
+ */
+ public DomainMap setJoinTransformation(String from, String to) {
+ if (from == null) {
+ throw new IllegalArgumentException("Parameter 'from' must be non-null");
+ }
+ if (to == null) {
+ throw new IllegalArgumentException("Parameter 'to' must be non-null");
+ }
+
+ final Map<String, Object> joinParameters = new HashMap<>();
+ joinParameters.put("from", from);
+ joinParameters.put("to", to);
+ put("join", joinParameters);
+
+ return this;
+ }
+}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/b502ba28/solr/solrj/src/java/org/apache/solr/client/solrj/request/json/HeatmapFacetMap.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/request/json/HeatmapFacetMap.java b/solr/solrj/src/java/org/apache/solr/client/solrj/request/json/HeatmapFacetMap.java
new file mode 100644
index 0000000..ed64e08
--- /dev/null
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/request/json/HeatmapFacetMap.java
@@ -0,0 +1,137 @@
+/*
+ * 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.
+ */
+
+package org.apache.solr.client.solrj.request.json;
+
+import java.util.Map;
+
+/**
+ * Represents a "heatmap" facet in a JSON request query.
+ *
+ * Ready for use with {@link JsonQueryRequest#withFacet(String, Map)}
+ */
+public class HeatmapFacetMap extends JsonFacetMap<HeatmapFacetMap> {
+ public HeatmapFacetMap(String fieldName) {
+ super("heatmap");
+
+ if (fieldName == null) {
+ throw new IllegalArgumentException("Parameter 'fieldName' must be non-null");
+ }
+
+ put("field", fieldName);
+ }
+
+ @Override
+ public HeatmapFacetMap getThis() { return this; }
+
+ @Override
+ public HeatmapFacetMap withSubFacet(String facetName, JsonFacetMap map) {
+ throw new UnsupportedOperationException(getClass().getName() + " doesn't currently support subfacets");
+ }
+
+ /**
+ * Indicate the region to compute the heatmap facet on.
+ *
+ * Defaults to the "world" ("[-180,-90 TO 180,90]")
+ */
+ public HeatmapFacetMap setRegionQuery(String queryString) {
+ if (queryString == null) {
+ throw new IllegalArgumentException("Parameter 'queryString' must be non-null");
+ }
+
+ put("geom", queryString);
+ return this;
+ }
+
+ /**
+ * Indicates the size of each cell in the computed heatmap grid
+ *
+ * If not set, defaults to being computed by {@code distErrPct} or {@code distErr}
+ *
+ * @param individualCellSize the forced size of each cell in the heatmap grid
+ *
+ * @see #setDistErr(double)
+ * @see #setDistErrPct(double)
+ */
+ public HeatmapFacetMap setGridLevel(int individualCellSize) {
+ if (individualCellSize <= 0) {
+ throw new IllegalArgumentException("Parameter 'individualCellSize' must be a positive integer");
+ }
+ put("gridLevel", individualCellSize);
+ return this;
+ }
+
+ /**
+ * A fraction of the heatmap region that is used to compute the cell size.
+ *
+ * Defaults to 0.15 if not specified.
+ *
+ * @see #setGridLevel(int)
+ * @see #setDistErr(double)
+ */
+ public HeatmapFacetMap setDistErrPct(double distErrPct) {
+ if (distErrPct < 0 || distErrPct > 1) {
+ throw new IllegalArgumentException("Parameter 'distErrPct' must be between 0.0 and 1.0");
+ }
+ put("distErrPct", distErrPct);
+ return this;
+ }
+
+ /**
+ * Indicates the maximum acceptable cell error distance.
+ *
+ * Used to compute the size of each cell in the heatmap grid rather than specifying {@link #setGridLevel(int)}
+ *
+ * @param distErr a positive value representing the maximum acceptable cell error.
+ *
+ * @see #setGridLevel(int)
+ * @see #setDistErrPct(double)
+ */
+ public HeatmapFacetMap setDistErr(double distErr) {
+ if (distErr < 0) {
+ throw new IllegalArgumentException("Parameter 'distErr' must be non-negative");
+ }
+ put("distErr", distErr);
+ return this;
+ }
+
+ public enum HeatmapFormat {
+ INTS2D("ints2D"), PNG("png");
+
+ private final String value;
+
+ HeatmapFormat(String value) {
+ this.value = value;
+ }
+
+ @Override
+ public String toString() { return value; }
+ }
+
+ /**
+ * Sets the format that the computed heatmap should be returned in.
+ *
+ * Defaults to 'ints2D' if not specified.
+ */
+ public HeatmapFacetMap setHeatmapFormat(HeatmapFormat format) {
+ if (format == null) {
+ throw new IllegalArgumentException("Parameter 'format' must be non-null");
+ }
+ put("format", format.toString());
+ return this;
+ }
+}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/b502ba28/solr/solrj/src/java/org/apache/solr/client/solrj/request/json/JsonFacetMap.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/request/json/JsonFacetMap.java b/solr/solrj/src/java/org/apache/solr/client/solrj/request/json/JsonFacetMap.java
new file mode 100644
index 0000000..3d3e6de
--- /dev/null
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/request/json/JsonFacetMap.java
@@ -0,0 +1,62 @@
+/*
+ * 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.
+ */
+
+package org.apache.solr.client.solrj.request.json;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A common parent for a small set of classes that allow easier composition of JSON facet objects.
+ *
+ * Designed for use with {@link JsonQueryRequest#withFacet(String, Map)}
+ */
+public abstract class JsonFacetMap<B extends JsonFacetMap<B>> extends HashMap<String, Object> {
+
+ public abstract B getThis(); // Allows methods shared here to return subclass type
+
+ public JsonFacetMap(String facetType) {
+ super();
+
+ put("type", facetType);
+ }
+
+ public B withDomain(DomainMap domain) {
+ put("domain", domain);
+ return getThis();
+ }
+
+ public B withSubFacet(String facetName, JsonFacetMap map) {
+ if (! containsKey("facet")) {
+ put("facet", new HashMap<String, Object>());
+ }
+
+ final Map<String, Object> subFacetMap = (Map<String, Object>) get("facet");
+ subFacetMap.put(facetName, map);
+ return getThis();
+ }
+
+ public B withStatSubFacet(String facetName, String statFacet) {
+ if (! containsKey("facet")) {
+ put("facet", new HashMap<String, Object>());
+ }
+
+ final Map<String, Object> subFacetMap = (Map<String, Object>) get("facet");
+ subFacetMap.put(facetName, statFacet);
+ return getThis();
+ }
+}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/b502ba28/solr/solrj/src/java/org/apache/solr/client/solrj/request/json/JsonQueryRequest.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/request/json/JsonQueryRequest.java b/solr/solrj/src/java/org/apache/solr/client/solrj/request/json/JsonQueryRequest.java
index 781d9c3..1c7b071 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/request/json/JsonQueryRequest.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/request/json/JsonQueryRequest.java
@@ -133,6 +133,115 @@ public class JsonQueryRequest extends QueryRequest {
}
/**
+ * Specify a facet sent as a part of this JSON request.
+ *
+ * This method may be called multiple times. Each call made with a different {@code facetName} value will add a new
+ * top-level facet. Repeating {@code facetName} values will cause previous facets with that {@code facetName} to be
+ * overwritten.
+ * <p>
+ * <b>Example:</b> You wish to send the JSON request: {"query": "*:*", "facet": { "top_cats":{"type": "terms", "field":"cat"}}}. You
+ * would represent (and attach) the facet in this request as follows:
+ * <pre>{@code
+ * final Map<String, Object> catFacetMap = new HashMap<>();
+ * catFacetMap.put("type", "terms");
+ * catFacetMap.put("field", "cat");
+ *
+ * jsonQueryRequest.withStatFacet("top_cats", catFacetMap);
+ * }</pre>
+ *
+ * @param facetName the name of the top-level facet you'd like to add.
+ * @param facetJson a Map of values representing the facet you wish to add to the request
+ */
+ public JsonQueryRequest withFacet(String facetName, Map<String, Object> facetJson) {
+ if (facetName == null) {
+ throw new IllegalArgumentException("'facetName' parameter must be non-null");
+ }
+ if (facetJson == null) {
+ throw new IllegalArgumentException("'facetMap' parameter must be non-null");
+ }
+
+ if (! jsonRequestMap.containsKey("facet")) {
+ jsonRequestMap.put("facet", new HashMap<String, Object>());
+ }
+
+ final Map<String, Object> facetMap = (Map<String, Object>) jsonRequestMap.get("facet");
+ facetMap.put(facetName, facetJson);
+ return this;
+ }
+
+ /**
+ * Specify a facet sent as a part of this JSON request.
+ *
+ * This method may be called multiple times. Each call made with a different {@code facetName} value will add a new
+ * top-level facet. Repeating {@code facetName} values will cause previous facets with that {@code facetName} to be
+ * overwritten.
+ * <p>
+ * <b>Example:</b> You wish to send the JSON request: {"query": "*:*", "facet": { "top_cats":{"type": "terms", "field":"cat"}}}. You
+ * would represent the facet in this request as follows:
+ * <pre>
+ * final MapWriter facetWriter = new MapWriter() {
+ * @Override
+ * public void writeMap(EntryWriter ew) throws IOException {
+ * ew.put("type", "terms");
+ * ew.put("field", "cat");
+ * }
+ * };
+ * </pre>
+ *
+ * @param facetName the name of the top-level facet you'd like to add.
+ * @param facetWriter a MapWriter representing the facet you wish to add to the request
+ */
+ public JsonQueryRequest withFacet(String facetName, MapWriter facetWriter) {
+ if (facetName == null) {
+ throw new IllegalArgumentException("'facetName' parameter must be non-null");
+ }
+ if (facetWriter == null) {
+ throw new IllegalArgumentException("'facetWriter' parameter must be non-null");
+ }
+
+ if (! jsonRequestMap.containsKey("facet")) {
+ jsonRequestMap.put("facet", new HashMap<String, Object>());
+ }
+
+ final Map<String, Object> facetMap = (Map<String, Object>) jsonRequestMap.get("facet");
+ facetMap.put(facetName, facetWriter);
+ return this;
+ }
+
+ /**
+ * Specify a simple stat or aggregation facet to be sent as a part of this JSON request.
+ *
+ * This method may be called multiple times. Each call made with a different {@code facetName} value will add a new
+ * top-level facet. Repeating {@code facetName} values will cause previous facets with that {@code facetName} to be
+ * overwritten.
+ * <p>
+ * <b>Example:</b> You wish to send the JSON request: {"query": "*:*", "facet": {"avg_price": "avg(price)"}}. You
+ * would represent the facet in this request as follows:
+ * <pre>{@code
+ * jsonQueryRequest.withStatFacet("avg_price", "avg(price)");
+ * }</pre>
+ *
+ * @param facetName the name of the top-level stat/agg facet you'd like to add.
+ * @param facetValue a String representing the stat/agg facet computation to perform.
+ */
+ public JsonQueryRequest withStatFacet(String facetName, String facetValue) {
+ if (facetName == null) {
+ throw new IllegalArgumentException("'facetName' parameter must be non-null");
+ }
+ if (facetValue == null) {
+ throw new IllegalArgumentException("'facetValue' parameter must be non-null");
+ }
+
+ if (! jsonRequestMap.containsKey("facet")) {
+ jsonRequestMap.put("facet", new HashMap<String, Object>());
+ }
+
+ final Map<String, Object> facetMap = (Map<String, Object>) jsonRequestMap.get("facet");
+ facetMap.put(facetName, facetValue);
+ return this;
+ }
+
+ /**
* Specify whether results should be fetched starting from a particular offset (or 'start').
*
* Defaults to 0 if not set.
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/b502ba28/solr/solrj/src/java/org/apache/solr/client/solrj/request/json/QueryFacetMap.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/request/json/QueryFacetMap.java b/solr/solrj/src/java/org/apache/solr/client/solrj/request/json/QueryFacetMap.java
new file mode 100644
index 0000000..7613183
--- /dev/null
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/request/json/QueryFacetMap.java
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ */
+
+package org.apache.solr.client.solrj.request.json;
+
+import java.util.Map;
+
+/**
+ * Represents a "query" facet in a JSON query request.
+ *
+ * Ready for use in {@link JsonQueryRequest#withFacet(String, Map)}
+ */
+public class QueryFacetMap extends JsonFacetMap<QueryFacetMap> {
+ public QueryFacetMap(String queryString) {
+ super("query");
+
+ if (queryString == null) {
+ throw new IllegalArgumentException("Parameter 'queryString' must be non-null");
+ }
+ put("q", queryString);
+ }
+
+ @Override
+ public QueryFacetMap getThis() { return this; }
+}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/b502ba28/solr/solrj/src/java/org/apache/solr/client/solrj/request/json/RangeFacetMap.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/request/json/RangeFacetMap.java b/solr/solrj/src/java/org/apache/solr/client/solrj/request/json/RangeFacetMap.java
new file mode 100644
index 0000000..24d5123
--- /dev/null
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/request/json/RangeFacetMap.java
@@ -0,0 +1,105 @@
+/*
+ * 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.
+ */
+
+package org.apache.solr.client.solrj.request.json;
+
+import java.util.Map;
+
+/**
+ * Represents a "range" facet in a JSON request query.
+ *
+ * Ready for use with {@link JsonQueryRequest#withFacet(String, Map)}
+ */
+public class RangeFacetMap extends JsonFacetMap<RangeFacetMap> {
+ public RangeFacetMap(String field, long start, long end, long gap) {
+ super("range");
+
+ if (field == null) {
+ throw new IllegalArgumentException("Parameter 'field' must be non-null");
+ }
+ if (end < start) {
+ throw new IllegalArgumentException("Parameter 'end' must be greater than parameter 'start'");
+ }
+ if (gap <= 0) {
+ throw new IllegalArgumentException("Parameter 'gap' must be a positive integer");
+ }
+
+ put("field", field);
+ put("start", start);
+ put("end", end);
+ put("gap", gap);
+ }
+
+ public RangeFacetMap(String field, double start, double end, double gap) {
+ super("range");
+
+ if (field == null) {
+ throw new IllegalArgumentException("Parameter 'field' must be non-null");
+ }
+ if (end < start) {
+ throw new IllegalArgumentException("Parameter 'end' must be greater than parameter 'start'");
+ }
+ if (gap <= 0) {
+ throw new IllegalArgumentException("Parameter 'gap' must be a positive value");
+ }
+
+ put("field", field);
+ put("start", start);
+ put("end", end);
+ put("gap", gap);
+ }
+
+ @Override
+ public RangeFacetMap getThis() { return this; }
+
+ /**
+ * Indicates whether the facet's last bucket should stop exactly at {@code end}, or be extended to be {@code gap} wide
+ *
+ * Defaults to false if not specified.
+ *
+ * @param hardEnd true if the final bucket should be truncated at {@code end}; false otherwise
+ */
+ public RangeFacetMap setHardEnd(boolean hardEnd) {
+ put("hardend", hardEnd);
+ return this;
+ }
+
+ public enum OtherBuckets {
+ BEFORE("before"), AFTER("after"), BETWEEN("between"), NONE("none"), ALL("all");
+
+ private final String value;
+
+ OtherBuckets(String value) {
+ this.value = value;
+ }
+
+ public String toString() { return value; }
+ }
+
+ /**
+ * Indicates that an additional range bucket(s) should be computed and added to those computed for {@code start} and {@code end}
+ *
+ * See {@link OtherBuckets} for possible options.
+ */
+ public RangeFacetMap setOtherBuckets(OtherBuckets bucketSpecifier) {
+ if (bucketSpecifier == null) {
+ throw new IllegalArgumentException("Parameter 'bucketSpecifier' must be non-null");
+ }
+ put("other", bucketSpecifier.toString());
+ return this;
+ }
+}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/b502ba28/solr/solrj/src/java/org/apache/solr/client/solrj/request/json/TermsFacetMap.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/request/json/TermsFacetMap.java b/solr/solrj/src/java/org/apache/solr/client/solrj/request/json/TermsFacetMap.java
new file mode 100644
index 0000000..e28f8a8
--- /dev/null
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/request/json/TermsFacetMap.java
@@ -0,0 +1,204 @@
+/*
+ * 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.
+ */
+
+package org.apache.solr.client.solrj.request.json;
+
+import java.util.Map;
+
+/**
+ * Represents a "terms" facet in a JSON query request.
+ *
+ * Ready for use in {@link JsonQueryRequest#withFacet(String, Map)}
+ */
+public class TermsFacetMap extends JsonFacetMap<TermsFacetMap> {
+ public TermsFacetMap(String fieldName) {
+ super("terms");
+
+ put("field", fieldName);
+ }
+
+ @Override
+ public TermsFacetMap getThis() { return this; }
+
+ /**
+ * Indicates that Solr should skip over the N buckets for this facet.
+ *
+ * Used for "paging" in facet results. Defaults to 0 if not provided.
+ *
+ * @param numToSkip the number of buckets to skip over before selecting the buckets to return
+ */
+ public TermsFacetMap setBucketOffset(int numToSkip) {
+ if (numToSkip < 0) {
+ throw new IllegalArgumentException("Parameter 'numToSkip' must be non-negative");
+ }
+ put("offset", numToSkip);
+ return this;
+ }
+
+ /**
+ * Indicates the maximum number of buckets to be returned by this facet.
+ *
+ * Defaults to 10 if not specified.
+ */
+ public TermsFacetMap setLimit(int maximumBuckets) {
+ if (maximumBuckets < 0) {
+ throw new IllegalArgumentException("Parameter 'maximumBuckets' must be non-negative");
+ }
+ put("limit", maximumBuckets);
+ return this;
+ }
+
+ /**
+ * Indicates the desired ordering for the returned buckets.
+ *
+ * Values can be based on 'count' (the number of results in each bucket), 'index' (the natural order of bucket values),
+ * or on any stat facet that occurs in the bucket. Defaults to "count desc" if not specified.
+ */
+ public TermsFacetMap setSort(String sortString) {
+ if (sortString == null) {
+ throw new IllegalArgumentException("Parameter 'sortString' must be non-null");
+ }
+ put("sort", sortString);
+ return this;
+ }
+
+ /**
+ * Indicates the number of additional buckets to request internally beyond those required by {@link #setLimit(int)}.
+ *
+ * Defaults to -1 if not specified, which triggers some heuristic guessing based on other settings.
+ */
+ public TermsFacetMap setOverRequest(int numExtraBuckets) {
+ if (numExtraBuckets < -1) {
+ throw new IllegalArgumentException("Parameter 'numExtraBuckets' must be >= -1");
+ }
+ put("overrequest", numExtraBuckets);
+ return this;
+ }
+
+ /**
+ * Indicates whether this facet should use distributed facet refining.
+ *
+ * "Distributed facet refining" is a second, optional stage in the facet process that ensures that counts for the
+ * returned buckets are exact. Enabling it is a tradeoff between precision and speed/performance. Defaults to false
+ * if not specified.
+ * @param useRefining true if distributed facet refining should be used; false otherwise
+ */
+ public TermsFacetMap useDistributedFacetRefining(boolean useRefining) {
+ put("refine", useRefining);
+ return this;
+ }
+
+ /**
+ * Indicates how many extra buckets to request during distributed-facet-refining beyond those required by {@link #setLimit(int)}
+ *
+ * Defaults to -1 if not specified, which triggers some heuristic guessing based on other settings.
+ */
+ public TermsFacetMap setOverRefine(int numExtraBuckets) {
+ if (numExtraBuckets < -1) {
+ throw new IllegalArgumentException("Parameter 'numExtraBuckets' must be >= -1");
+ }
+ put("overrefine", numExtraBuckets);
+ return this;
+ }
+
+ /**
+ * Indicates that the facet results should not include any buckets with a count less than {@code minCount}.
+ *
+ * Defaults to 1 if not specified.
+ */
+ public TermsFacetMap setMinCount(int minCount) {
+ if (minCount < 1) {
+ throw new IllegalArgumentException("Parameter 'minCount' must be a positive integer");
+ }
+ put("mincount", minCount);
+ return this;
+ }
+
+ /**
+ * Indicates that Solr should create a bucket corresponding to documents missing the field used by this facet.
+ *
+ * Defaults to false if not specified.
+ *
+ * @param missingBucket true if the special "missing" bucket should be created; false otherwise
+ */
+ public TermsFacetMap includeMissingBucket(boolean missingBucket) {
+ put("missing", missingBucket);
+ return this;
+ }
+
+ /**
+ * Indicates that Solr should include the total number of buckets for this facet.
+ *
+ * Note that this is different than the number of buckets returned. Defaults to false if not specified
+ *
+ * @param numBuckets true if the "numBuckets" field should be computed; false otherwise
+ */
+ public TermsFacetMap includeTotalNumBuckets(boolean numBuckets) {
+ put("numBuckets", numBuckets);
+ return this;
+ }
+
+ /**
+ * Creates a bucket representing the union of all other buckets.
+ *
+ * For multi-valued fields this is different than a bucket for the entire domain, since documents can belong to
+ * multiple buckets. Defaults to false if not specified.
+ *
+ * @param shouldInclude true if the union bucket "allBuckets" should be computed; false otherwise
+ */
+ public TermsFacetMap includeAllBucketsUnionBucket(boolean shouldInclude) {
+ put("allBuckets", shouldInclude);
+ return this;
+ }
+
+ /**
+ * Indicates that the facet should only produce buckets for terms that start with the specified prefix.
+ */
+ public TermsFacetMap setTermPrefix(String termPrefix) {
+ if (termPrefix == null) {
+ throw new IllegalArgumentException("Parameter 'termPrefix' must be non-null");
+ }
+ put("prefix", termPrefix);
+ return this;
+ }
+
+ public enum FacetMethod {
+ DV("dv"), UIF("uif"), DVHASH("dvhash"), ENUM("enum"), STREAM("stream"), SMART("smart");
+
+ private final String value;
+ FacetMethod(String value) {
+ this.value = value;
+ }
+
+ public String toString() {
+ return value;
+ }
+ }
+
+ /**
+ * Indicate which method should be used to compute the facet.
+ *
+ * Defaults to "smart" if not specified, which has Solr guess which computation method will be most efficient.
+ */
+ public TermsFacetMap setFacetMethod(FacetMethod method) {
+ if (method == null) {
+ throw new IllegalArgumentException("Parameter 'method' must be non-null");
+ }
+ put("method", method.toString());
+ return this;
+ }
+}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/b502ba28/solr/solrj/src/test-files/solrj/techproducts.xml
----------------------------------------------------------------------
diff --git a/solr/solrj/src/test-files/solrj/techproducts.xml b/solr/solrj/src/test-files/solrj/techproducts.xml
new file mode 100644
index 0000000..15650fa
--- /dev/null
+++ b/solr/solrj/src/test-files/solrj/techproducts.xml
@@ -0,0 +1,421 @@
+<!--
+ 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.
+-->
+<add>
+ <doc>
+ <field name="id">TWINX2048-3200PRO</field>
+ <field name="name">CORSAIR XMS 2GB (2 x 1GB) 184-Pin DDR SDRAM Unbuffered DDR 400 (PC 3200) Dual Channel Kit System Memory - Retail</field>
+ <field name="manu">Corsair Microsystems Inc.</field>
+ <!-- Join -->
+ <field name="manu_id_s">corsair</field>
+ <field name="cat">electronics</field>
+ <field name="cat">memory</field>
+ <field name="features">CAS latency 2, 2-3-3-6 timing, 2.75v, unbuffered, heat-spreader</field>
+ <field name="price">185.00</field>
+ <field name="popularity">5</field>
+ <field name="inStock">true</field>
+ <!-- San Francisco store -->
+ <field name="store">37.7752,-122.4232</field>
+ <field name="manufacturedate_dt">2006-02-13T15:26:37Z</field>
+ <!-- a field for testing payload tagged text via DelimitedPayloadTokenFilter -->
+ <field name="payloads">electronics|6.0 memory|3.0</field>
+ </doc>
+ <doc>
+ <field name="id">VS1GB400C3</field>
+ <field name="name">CORSAIR ValueSelect 1GB 184-Pin DDR SDRAM Unbuffered DDR 400 (PC 3200) System Memory - Retail</field>
+ <field name="manu">Corsair Microsystems Inc.</field>
+ <!-- Join -->
+ <field name="manu_id_s">corsair</field>
+ <field name="cat">electronics</field>
+ <field name="cat">memory</field>
+ <field name="price">74.99</field>
+ <field name="popularity">7</field>
+ <field name="inStock">true</field>
+ <!-- Dodge City store -->
+ <field name="store">37.7752,-100.0232</field>
+ <field name="manufacturedate_dt">2006-02-13T15:26:37Z</field>
+ <field name="payloads">electronics|4.0 memory|2.0</field>
+ </doc>
+ <doc>
+ <field name="id">VDBDB1A16</field>
+ <field name="name">A-DATA V-Series 1GB 184-Pin DDR SDRAM Unbuffered DDR 400 (PC 3200) System Memory - OEM</field>
+ <field name="manu">A-DATA Technology Inc.</field>
+ <!-- Join -->
+ <field name="manu_id_s">corsair</field>
+ <field name="cat">electronics</field>
+ <field name="cat">memory</field>
+ <field name="features">CAS latency 3, 2.7v</field>
+ <!-- note: price & popularity is missing on this one -->
+ <field name="popularity">0</field>
+ <field name="inStock">true</field>
+ <!-- Buffalo store -->
+ <field name="store">45.18414,-93.88141</field>
+ <field name="manufacturedate_dt">2006-02-13T15:26:37Z</field>
+ <field name="payloads">electronics|0.9 memory|0.1</field>
+ </doc>
+ <doc>
+ <field name="id">MA147LL/A</field>
+ <field name="name">Apple 60 GB iPod with Video Playback Black</field>
+ <field name="manu">Apple Computer Inc.</field>
+ <!-- Join -->
+ <field name="manu_id_s">apple</field>
+ <field name="cat">electronics</field>
+ <field name="cat">music</field>
+ <field name="features">iTunes, Podcasts, Audiobooks</field>
+ <field name="features">Stores up to 15,000 songs, 25,000 photos, or 150 hours of video</field>
+ <field name="features">2.5-inch, 320x240 color TFT LCD display with LED backlight</field>
+ <field name="features">Up to 20 hours of battery life</field>
+ <field name="features">Plays AAC, MP3, WAV, AIFF, Audible, Apple Lossless, H.264 video</field>
+ <field name="features">Notes, Calendar, Phone book, Hold button, Date display, Photo wallet, Built-in games, JPEG photo playback, Upgradeable firmware, USB 2.0 compatibility, Playback speed control, Rechargeable capability, Battery level indication</field>
+ <field name="includes">earbud headphones, USB cable</field>
+ <field name="weight">5.5</field>
+ <field name="price">399.00</field>
+ <field name="popularity">10</field>
+ <field name="inStock">true</field>
+ <!-- Dodge City store -->
+ <field name="store">37.7752,-100.0232</field>
+ <field name="manufacturedate_dt">2005-10-12T08:00:00Z</field>
+ </doc>
+ <doc>
+ <field name="id">F8V7067-APL-KIT</field>
+ <field name="name">Belkin Mobile Power Cord for iPod w/ Dock</field>
+ <field name="manu">Belkin</field>
+ <!-- Join -->
+ <field name="manu_id_s">belkin</field>
+ <field name="cat">electronics</field>
+ <field name="cat">connector</field>
+ <field name="features">car power adapter, white</field>
+ <field name="weight">4.0</field>
+ <field name="price">19.95</field>
+ <field name="popularity">1</field>
+ <field name="inStock">false</field>
+ <!-- Buffalo store -->
+ <field name="store">45.18014,-93.87741</field>
+ <field name="manufacturedate_dt">2005-08-01T16:30:25Z</field>
+ </doc>
+ <doc>
+ <field name="id">IW-02</field>
+ <field name="name">iPod & iPod Mini USB 2.0 Cable</field>
+ <field name="manu">Belkin</field>
+ <!-- Join -->
+ <field name="manu_id_s">belkin</field>
+ <field name="cat">electronics</field>
+ <field name="cat">connector</field>
+ <field name="features">car power adapter for iPod, white</field>
+ <field name="weight">2.0</field>
+ <field name="price">11.50</field>
+ <field name="popularity">1</field>
+ <field name="inStock">false</field>
+ <!-- San Francisco store -->
+ <field name="store">37.7752,-122.4232</field>
+ <field name="manufacturedate_dt">2006-02-14T23:55:59Z</field>
+ </doc>
+ <doc>
+ <field name="id">9885A004</field>
+ <field name="name">Canon PowerShot SD500</field>
+ <field name="manu">Canon Inc.</field>
+ <!-- Join -->
+ <field name="manu_id_s">canon</field>
+ <field name="cat">electronics</field>
+ <field name="cat">camera</field>
+ <field name="features">3x zoop, 7.1 megapixel Digital ELPH</field>
+ <field name="features">movie clips up to 640x480 @30 fps</field>
+ <field name="features">2.0" TFT LCD, 118,000 pixels</field>
+ <field name="features">built in flash, red-eye reduction</field>
+ <field name="includes">32MB SD card, USB cable, AV cable, battery</field>
+ <field name="weight">6.4</field>
+ <field name="price">329.95</field>
+ <field name="popularity">7</field>
+ <field name="inStock">true</field>
+ <field name="manufacturedate_dt">2006-02-13T15:26:37Z</field>
+ <!-- Buffalo store -->
+ <field name="store">45.19614,-93.90341</field>
+ </doc>
+ <doc>
+ <field name="id">VA902B</field>
+ <field name="name">ViewSonic VA902B - flat panel display - TFT - 19"</field>
+ <field name="manu">ViewSonic Corp.</field>
+ <!-- Join -->
+ <field name="manu_id_s">viewsonic</field>
+ <field name="cat">electronics and stuff2</field>
+ <field name="features">19" TFT active matrix LCD, 8ms response time, 1280 x 1024 native resolution</field>
+ <field name="weight">190.4</field>
+ <field name="price">279.95</field>
+ <field name="popularity">6</field>
+ <field name="inStock">true</field>
+ <!-- Buffalo store -->
+ <field name="store">45.18814,-93.88541</field>
+ </doc>
+ <doc>
+ <field name="id">EN7800GTX/2DHTV/256M</field>
+ <field name="name">ASUS Extreme N7800GTX/2DHTV (256 MB)</field>
+ <!-- Denormalized -->
+ <field name="manu">ASUS Computer Inc.</field>
+ <!-- Join -->
+ <field name="manu_id_s">asus</field>
+ <field name="cat">electronics</field>
+ <field name="cat">graphics card</field>
+ <field name="features">NVIDIA GeForce 7800 GTX GPU/VPU clocked at 486MHz</field>
+ <field name="features">256MB GDDR3 Memory clocked at 1.35GHz</field>
+ <field name="features">PCI Express x16</field>
+ <field name="features">Dual DVI connectors, HDTV out, video input</field>
+ <field name="features">OpenGL 2.0, DirectX 9.0</field>
+ <field name="weight">16.0</field>
+ <field name="price">479.95</field>
+ <field name="popularity">7</field>
+ <field name="store">40.7143,-74.006</field>
+ <field name="inStock">false</field>
+ <field name="manufacturedate_dt">2006-02-13T15:26:37Z/DAY</field>
+ </doc>
+ <!-- yes, you can add more than one document at a time -->
+ <doc>
+ <field name="id">100-435805</field>
+ <field name="name">ATI Radeon X1900 XTX 512 MB PCIE Video Card</field>
+ <field name="manu">ATI Technologies</field>
+ <!-- Join -->
+ <field name="manu_id_s">ati</field>
+ <field name="cat">electronics</field>
+ <field name="cat">graphics card</field>
+ <field name="features">ATI RADEON X1900 GPU/VPU clocked at 650MHz</field>
+ <field name="features">512MB GDDR3 SDRAM clocked at 1.55GHz</field>
+ <field name="features">PCI Express x16</field>
+ <field name="features">dual DVI, HDTV, svideo, composite out</field>
+ <field name="features">OpenGL 2.0, DirectX 9.0</field>
+ <field name="weight">48.0</field>
+ <field name="price">649.99</field>
+ <field name="popularity">7</field>
+ <field name="inStock">false</field>
+ <field name="manufacturedate_dt">2006-02-13T15:26:37Z/DAY</field>
+ <!-- NYC store -->
+ <field name="store">40.7143,-74.006</field>
+ </doc>
+ <doc>
+ <field name="id">0579B002</field>
+ <field name="name">Canon PIXMA MP500 All-In-One Photo Printer</field>
+ <field name="manu">Canon Inc.</field>
+ <!-- Join -->
+ <field name="manu_id_s">canon</field>
+ <field name="cat">electronics</field>
+ <field name="cat">multifunction printer</field>
+ <field name="cat">printer</field>
+ <field name="cat">scanner</field>
+ <field name="cat">copier</field>
+ <field name="features">Multifunction ink-jet color photo printer</field>
+ <field name="features">Flatbed scanner, optical scan resolution of 1,200 x 2,400 dpi</field>
+ <field name="features">2.5" color LCD preview screen</field>
+ <field name="features">Duplex Copying</field>
+ <field name="features">Printing speed up to 29ppm black, 19ppm color</field>
+ <field name="features">Hi-Speed USB</field>
+ <field name="features">memory card: CompactFlash, Micro Drive, SmartMedia, Memory Stick, Memory Stick Pro, SD Card, and MultiMediaCard</field>
+ <field name="weight">352.0</field>
+ <field name="price">179.99</field>
+ <field name="popularity">6</field>
+ <field name="inStock">true</field>
+ <!-- Buffalo store -->
+ <field name="store">45.19214,-93.89941</field>
+ </doc>
+ <doc>
+ <field name="id">3007WFP</field>
+ <field name="name">Dell Widescreen UltraSharp 3007WFP</field>
+ <field name="manu">Dell, Inc.</field>
+ <!-- Join -->
+ <field name="manu_id_s">dell</field>
+ <field name="cat">electronics and computer1</field>
+ <field name="features">30" TFT active matrix LCD, 2560 x 1600, .25mm dot pitch, 700:1 contrast</field>
+ <field name="includes">USB cable</field>
+ <field name="weight">401.6</field>
+ <field name="price">2199.0</field>
+ <field name="popularity">6</field>
+ <field name="inStock">true</field>
+ <!-- Buffalo store -->
+ <field name="store">43.17614,-90.57341</field>
+ </doc>
+ <doc>
+ <field name="id">adata</field>
+ <field name="compName_s">A-Data Technology</field>
+ <field name="address_s">46221 Landing Parkway Fremont, CA 94538</field>
+ </doc>
+ <doc>
+ <field name="id">apple</field>
+ <field name="compName_s">Apple</field>
+ <field name="address_s">1 Infinite Way, Cupertino CA</field>
+ </doc>
+ <doc>
+ <field name="id">asus</field>
+ <field name="compName_s">ASUS Computer</field>
+ <field name="address_s">800 Corporate Way Fremont, CA 94539</field>
+ </doc>
+ <doc>
+ <field name="id">ati</field>
+ <field name="compName_s">ATI Technologies</field>
+ <field name="address_s">33 Commerce Valley Drive East Thornhill, ON L3T 7N6 Canada</field>
+ </doc>
+ <doc>
+ <field name="id">belkin</field>
+ <field name="compName_s">Belkin</field>
+ <field name="address_s">12045 E. Waterfront Drive Playa Vista, CA 90094</field>
+ </doc>
+ <doc>
+ <field name="id">canon</field>
+ <field name="compName_s">Canon, Inc.</field>
+ <field name="address_s">One Canon Plaza Lake Success, NY 11042</field>
+ </doc>
+ <doc>
+ <field name="id">corsair</field>
+ <field name="compName_s">Corsair Microsystems</field>
+ <field name="address_s">46221 Landing Parkway Fremont, CA 94538</field>
+ </doc>
+ <doc>
+ <field name="id">dell</field>
+ <field name="compName_s">Dell, Inc.</field>
+ <field name="address_s">One Dell Way Round Rock, Texas 78682</field>
+ </doc>
+ <doc>
+ <field name="id">maxtor</field>
+ <field name="compName_s">Maxtor Corporation</field>
+ <field name="address_s">920 Disc Drive Scotts Valley, CA 95066</field>
+ </doc>
+ <doc>
+ <field name="id">samsung</field>
+ <field name="compName_s">Samsung Electronics Co. Ltd.</field>
+ <field name="address_s">105 Challenger Rd. Ridgefield Park, NJ 07660-0511</field>
+ </doc>
+ <doc>
+ <field name="id">viewsonic</field>
+ <field name="compName_s">ViewSonic Corp</field>
+ <field name="address_s">381 Brea Canyon Road Walnut, CA 91789-0708</field>
+ </doc>
+ <doc>
+ <field name="id">SP2514N</field>
+ <field name="name">Samsung SpinPoint P120 SP2514N - hard drive - 250 GB - ATA-133</field>
+ <field name="manu">Samsung Electronics Co. Ltd.</field>
+ <!-- Join -->
+ <field name="manu_id_s">samsung</field>
+ <field name="cat">electronics</field>
+ <field name="cat">hard drive</field>
+ <field name="features">7200RPM, 8MB cache, IDE Ultra ATA-133</field>
+ <field name="features">NoiseGuard, SilentSeek technology, Fluid Dynamic Bearing (FDB) motor</field>
+ <field name="price">92.0</field>
+ <field name="popularity">6</field>
+ <field name="inStock">true</field>
+ <field name="manufacturedate_dt">2006-02-13T15:26:37Z</field>
+ <!-- Near Oklahoma city -->
+ <field name="store">35.0752,-97.032</field>
+ </doc>
+ <doc>
+ <field name="id">6H500F0</field>
+ <field name="name">Maxtor DiamondMax 11 - hard drive - 500 GB - SATA-300</field>
+ <field name="manu">Maxtor Corp.</field>
+ <!-- Join -->
+ <field name="manu_id_s">maxtor</field>
+ <field name="cat">electronics</field>
+ <field name="cat">hard drive</field>
+ <field name="features">SATA 3.0Gb/s, NCQ</field>
+ <field name="features">8.5ms seek</field>
+ <field name="features">16MB cache</field>
+ <field name="price">350.0</field>
+ <field name="popularity">6</field>
+ <field name="inStock">true</field>
+ <!-- Buffalo store -->
+ <field name="store">45.17614,-93.87341</field>
+ <field name="manufacturedate_dt">2006-02-13T15:26:37Z</field>
+ </doc>
+ <doc>
+ <field name="id">USD</field>
+ <field name="name">One Dollar</field>
+ <field name="manu">Bank of America</field>
+ <field name="manu_id_s">boa</field>
+ <field name="cat">currency</field>
+ <field name="features">Coins and notes</field>
+ <field name="price_c">1,USD</field>
+ <field name="inStock">true</field>
+ </doc>
+ <doc>
+ <field name="id">EUR</field>
+ <field name="name">One Euro</field>
+ <field name="manu">European Union</field>
+ <field name="manu_id_s">eu</field>
+ <field name="cat">currency</field>
+ <field name="features">Coins and notes</field>
+ <field name="price_c">1,EUR</field>
+ <field name="inStock">true</field>
+ </doc>
+ <doc>
+ <field name="id">GBP</field>
+ <field name="name">One British Pound</field>
+ <field name="manu">U.K.</field>
+ <field name="manu_id_s">uk</field>
+ <field name="cat">currency</field>
+ <field name="features">Coins and notes</field>
+ <field name="price_c">1,GBP</field>
+ <field name="inStock">true</field>
+ </doc>
+ <doc>
+ <field name="id">NOK</field>
+ <field name="name">One Krone</field>
+ <field name="manu">Bank of Norway</field>
+ <field name="manu_id_s">nor</field>
+ <field name="cat">currency</field>
+ <field name="features">Coins and notes</field>
+ <field name="price_c">1,NOK</field>
+ <field name="inStock">true</field>
+ </doc>
+ <doc>
+ <field name="id">UTF8TEST</field>
+ <field name="name">Test with some UTF-8 encoded characters</field>
+ <field name="manu">Apache Software Foundation</field>
+ <field name="cat">software</field>
+ <field name="cat">search</field>
+ <field name="features">No accents here</field>
+ <field name="features">This is an e acute: é</field>
+ <field name="features">eaiou with circumflexes: êâîôû</field>
+ <field name="features">eaiou with umlauts: ëäïöü</field>
+ <field name="features">tag with escaped chars: <nicetag/></field>
+ <field name="features">escaped ampersand: Bonnie & Clyde</field>
+ <field name="features">Outside the BMP:𐌈 codepoint=10308, a circle with an x inside. UTF8=f0908c88 UTF16=d800 df08</field>
+ <field name="price">0.0</field>
+ <field name="inStock">true</field>
+ </doc>
+ <doc>
+ <field name="id">GB18030TEST</field>
+ <field name="name">Test with some GB18030 encoded characters</field>
+ <field name="features">No accents here</field>
+ <field name="features">ÕâÊÇÒ»¸ö¹¦ÄÜ</field>
+ <field name="features">This is a feature (translated)</field>
+ <field name="features">Õâ·ÝÎļþÊǺÜÓйâÔó</field>
+ <field name="features">This document is very shiny (translated)</field>
+ <field name="price">0.0</field>
+ <field name="inStock">true</field>
+ </doc>
+ <doc>
+ <field name="id">SOLR1000</field>
+ <field name="name">Solr, the Enterprise Search Server</field>
+ <field name="manu">Apache Software Foundation</field>
+ <field name="cat">software</field>
+ <field name="cat">search</field>
+ <field name="features">Advanced Full-Text Search Capabilities using Lucene</field>
+ <field name="features">Optimized for High Volume Web Traffic</field>
+ <field name="features">Standards Based Open Interfaces - XML and HTTP</field>
+ <field name="features">Comprehensive HTML Administration Interfaces</field>
+ <field name="features">Scalability - Efficient Replication to other Solr Search Servers</field>
+ <field name="features">Flexible and Adaptable with XML configuration and Schema</field>
+ <field name="features">Good unicode support: héllo (hello with an accent over the e)</field>
+ <field name="price">0.0</field>
+ <field name="popularity">10</field>
+ <field name="inStock">true</field>
+ <field name="incubationdate_dt">2006-01-17T00:00:00.000Z</field>
+ </doc>
+</add>
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/b502ba28/solr/solrj/src/test/org/apache/solr/client/ref_guide_examples/JsonRequestApiTest.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/test/org/apache/solr/client/ref_guide_examples/JsonRequestApiTest.java b/solr/solrj/src/test/org/apache/solr/client/ref_guide_examples/JsonRequestApiTest.java
index b941f2d..5fd3876 100644
--- a/solr/solrj/src/test/org/apache/solr/client/ref_guide_examples/JsonRequestApiTest.java
+++ b/solr/solrj/src/test/org/apache/solr/client/ref_guide_examples/JsonRequestApiTest.java
@@ -27,11 +27,13 @@ import org.apache.solr.client.solrj.request.AbstractUpdateRequest;
import org.apache.solr.client.solrj.request.CollectionAdminRequest;
import org.apache.solr.client.solrj.request.ContentStreamUpdateRequest;
import org.apache.solr.client.solrj.request.json.JsonQueryRequest;
+import org.apache.solr.client.solrj.request.json.TermsFacetMap;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.client.solrj.response.UpdateResponse;
import org.apache.solr.cloud.SolrCloudTestCase;
import org.apache.solr.common.SolrDocument;
import org.apache.solr.common.params.ModifiableSolrParams;
+import org.apache.solr.common.util.NamedList;
import org.apache.solr.util.ExternalPaths;
import org.junit.BeforeClass;
import org.junit.Test;
@@ -58,7 +60,7 @@ public class JsonRequestApiTest extends SolrCloudTestCase {
ContentStreamUpdateRequest up = new ContentStreamUpdateRequest("/update");
up.setParam("collection", COLLECTION_NAME);
- up.addFile(getFile("solrj/docs2.xml"), "application/xml"); // A subset of the 'techproducts' documents
+ up.addFile(getFile("solrj/techproducts.xml"), "application/xml");
up.setAction(AbstractUpdateRequest.ACTION.COMMIT, true, true);
UpdateResponse updateResponse = up.process(cluster.getSolrClient());
assertEquals(0, updateResponse.getStatus());
@@ -67,7 +69,7 @@ public class JsonRequestApiTest extends SolrCloudTestCase {
@Test
public void testSimpleJsonQuery() throws Exception {
SolrClient solrClient = cluster.getSolrClient();
- final int expectedResults = 3;
+ final int expectedResults = 4;
// tag::solrj-json-query-simple[]
final JsonQueryRequest simpleQuery = new JsonQueryRequest()
@@ -116,7 +118,97 @@ public class JsonRequestApiTest extends SolrCloudTestCase {
// end::solrj-json-query-macro-expansion[]
assertEquals(0, queryResponse.getStatus());
- assertEquals(3, queryResponse.getResults().size());
+ assertEquals(5, queryResponse.getResults().size());
+ }
+
+ @Test
+ public void testSimpleJsonTermsFacet() throws Exception {
+ SolrClient solrClient = cluster.getSolrClient();
+
+ //tag::solrj-json-simple-terms-facet[]
+ final TermsFacetMap categoryFacet = new TermsFacetMap("cat").setLimit(3);
+ final JsonQueryRequest request = new JsonQueryRequest()
+ .setQuery("*:*")
+ .withFacet("categories", categoryFacet);
+ QueryResponse queryResponse = request.process(solrClient, COLLECTION_NAME);
+ //end::solrj-json-simple-terms-facet[]
+
+ assertEquals(0, queryResponse.getStatus());
+ assertEquals(32, queryResponse.getResults().getNumFound());
+ assertEquals(10, queryResponse.getResults().size());
+ assertHasFacetWithBucketValues(queryResponse.getResponse(),"categories",
+ new FacetBucket("electronics",12),
+ new FacetBucket("currency", 4),
+ new FacetBucket("memory", 3));
+ }
+
+ @Test
+ public void testTermsFacet2() throws Exception {
+ SolrClient solrClient = cluster.getSolrClient();
+
+ //tag::solrj-json-terms-facet2[]
+ final TermsFacetMap categoryFacet = new TermsFacetMap("cat").setLimit(5);
+ final JsonQueryRequest request = new JsonQueryRequest()
+ .setQuery("*:*")
+ .withFacet("categories", categoryFacet);
+ QueryResponse queryResponse = request.process(solrClient, COLLECTION_NAME);
+ //end::solrj-json-terms-facet2[]
+
+ assertEquals(0, queryResponse.getStatus());
+ assertEquals(32, queryResponse.getResults().getNumFound());
+ assertEquals(10, queryResponse.getResults().size());
+ assertHasFacetWithBucketValues(queryResponse.getResponse(),"categories",
+ new FacetBucket("electronics",12),
+ new FacetBucket("currency", 4),
+ new FacetBucket("memory", 3),
+ new FacetBucket("connector", 2),
+ new FacetBucket("graphics card", 2));
+ }
+
+ private class FacetBucket {
+ private final Object val;
+ private final int count;
+ FacetBucket(Object val, int count) {
+ this.val = val;
+ this.count = count;
+ }
+
+ public Object getVal() { return val; }
+ public int getCount() { return count; }
+ }
+
+ private void assertHasFacetWithBucketValues(NamedList<Object> rawResponse, String expectedFacetName, FacetBucket... expectedBuckets) {
+ final NamedList<Object> facetsTopLevel = assertHasFacetResponse(rawResponse);
+ assertFacetResponseHasFacetWithBuckets(facetsTopLevel, expectedFacetName, expectedBuckets);
+ }
+
+ private NamedList<Object> assertHasFacetResponse(NamedList<Object> topLevelResponse) {
+ Object o = topLevelResponse.get("facets");
+ if (o == null) fail("Response has no top-level 'facets' property as expected");
+ if (!(o instanceof NamedList)) fail("Response has a top-level 'facets' property, but it is not a NamedList");
+
+ return (NamedList<Object>) o;
+ }
+
+ private void assertFacetResponseHasFacetWithBuckets(NamedList<Object> facetResponse, String expectedFacetName, FacetBucket... expectedBuckets) {
+ Object o = facetResponse.get(expectedFacetName);
+ if (o == null) fail("Response has no top-level facet named '" + expectedFacetName + "'");
+ if (!(o instanceof NamedList)) fail("Response has a property for the expected facet '" + expectedFacetName + "' property, but it is not a NamedList");
+
+ final NamedList<Object> expectedFacetTopLevel = (NamedList<Object>) o;
+ o = expectedFacetTopLevel.get("buckets");
+ if (o == null) fail("Response has no 'buckets' property under 'facets'");
+ if (!(o instanceof List)) fail("Response has no 'buckets' property containing actual facet information.");
+
+ final List<NamedList> bucketList = (List<NamedList>) o;
+ assertEquals("Expected " + expectedBuckets.length + " buckets, but found " + bucketList.size(),
+ expectedBuckets.length, bucketList.size());
+ for (int i = 0; i < expectedBuckets.length; i++) {
+ final FacetBucket expectedBucket = expectedBuckets[i];
+ final NamedList<Object> actualBucket = bucketList.get(i);
+ assertEquals(expectedBucket.getVal(), actualBucket.get("val"));
+ assertEquals(expectedBucket.getCount(), actualBucket.get("count"));
+ }
}
}