SOLR-12965: Add facet support to JsonQueryRequest

This commit is contained in:
Jason Gerlowski 2018-11-10 19:48:50 -05:00
parent 4e2481b04b
commit 52998fa50e
18 changed files with 3202 additions and 4 deletions

View File

@ -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

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -132,6 +132,115 @@ public class JsonQueryRequest extends QueryRequest {
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 (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() {
* &#64;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').
*

View File

@ -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; }
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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 &amp; 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: &#xE9;</field>
<field name="features">eaiou with circumflexes: &#xEA;&#xE2;&#xEE;&#xF4;&#xFB;</field>
<field name="features">eaiou with umlauts: &#xEB;&#xE4;&#xEF;&#xF6;&#xFC;</field>
<field name="features">tag with escaped chars: &lt;nicetag/&gt;</field>
<field name="features">escaped ampersand: Bonnie &amp; Clyde</field>
<field name="features">Outside the BMP:&#x10308; 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">&#xD5;&#xE2;&#xCA;&#xC7;&#xD2;&#xBB;&#xB8;&#xF6;&#xB9;&#xA6;&#xC4;&#xDC;</field>
<field name="features">This is a feature (translated)</field>
<field name="features">&#xD5;&#xE2;&#xB7;&#xDD;&#xCE;&#xC4;&#xBC;&#xFE;&#xCA;&#xC7;&#xBA;&#xDC;&#xD3;&#xD0;&#xB9;&#xE2;&#xD4;&#xF3;</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&#xE9;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>

View File

@ -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"));
}
}
}

View File

@ -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);
}
}

View File

@ -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"));
}
}

View File

@ -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"));
}
}

View File

@ -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);
}
}

View File

@ -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;
@ -97,6 +96,91 @@ public class JsonQueryRequestUnitTest extends LuceneTestCase {
assertThat(requestBody, containsString("\"query\":{\"lucene\":{\"q\":\"*:*\",\"df\":\"text\"}}"));
}
@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, () -> {

View File

@ -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"));
}
}

View File

@ -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"));
}
}

View File

@ -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"));
}
}