SOLR-13042: Miscellaneous JSON Faceting ref-guide improvements

This commit is contained in:
Jason Gerlowski 2019-02-06 19:52:22 -05:00
parent 69b5a04a4d
commit 199725d5d5
7 changed files with 1739 additions and 585 deletions

View File

@ -20,7 +20,7 @@ BlockJoin facets allow you to aggregate children facet counts by their parents.
It is a common requirement that if a parent document has several children documents, all of them need to increment facet value count only once. This functionality is provided by `BlockJoinDocSetFacetComponent`, and `BlockJoinFacetComponent` just an alias for compatibility.
CAUTION: This functionality is considered deprecated. Users are encouraged to use `uniqueBlock(\_root_)` aggregation under a `terms` facet in the <<json-facet-api.adoc#block-join-counts,JSON Facet API>>.
CAUTION: This functionality is considered deprecated. Users are encouraged to use `uniqueBlock(\_root_)` aggregation under a `terms` facet in the <<json-faceting-domain-changes.adoc#block-join-domain-changes,JSON Facet API>>.
If this component is used, it must be explicitly enabled for a request handler in `solrconfig.xml`, in the same way as any other <<requesthandlers-and-searchcomponents-in-solrconfig.adoc#requesthandlers-and-searchcomponents-in-solrconfig,search component>>.
This example shows how you could add this search components to `solrconfig.xml` and define it in request handler:

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,250 @@
= JSON Faceting Domain Changes
: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
// 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.
Facet computation operates on a "domain" of documents. By default, this domain consists of the documents matched by the main query. For sub-facets, the domain consists of all documents placed in their bucket by the parent facet.
Users can also override the "domain" of a facet that partitions data, using an explicit `domain` attribute whose value is a JSON object that can support various options for restricting, expanding, or completely changing the original domain before the buckets are computed for the associated facet.
[NOTE]
====
`domain` changes can only be specified on individual facets that do data partitioning -- not statistical/metric facets, or groups of facets.
A `\*:*` query facet with a `domain` change can be used to group multiple sub-facets of any type, for the purpose of applying a common domain change.
====
== Adding Domain Filters
The simplest example of a domain change is to specify an additional filter which will be applied to the existing domain. This can be done via the `filter` keyword in the `domain` block of the facet.
[.dynamic-tabs]
--
[example.tab-pane#curl-json-facet-filtered-domain]
====
[.tab-label]*curl*
[source,bash]
----
curl http://localhost:8983/solr/techproducts/query -d '
{
"query": "*:*",
"facet": {
"categories": {
"type": "terms",
"field": "cat",
"limit": 3,
"domain": {
"filter": "popularity:[5 TO 10]"
}
}
}
}'
----
====
[example.tab-pane#solrj-json-facet-filtered-domain]
====
[.tab-label]*SolrJ*
[source,java,indent=0]
----
include::{example-source-dir}JsonRequestApiTest.java[tag=solrj-json-facet-filtered-domain]
----
====
--
The value of `filter` can be a single query to treat as a filter, or a JSON list of filter queries. Each query can be:
* a string containing a query in Solr query syntax
* a reference to a request parameter containing Solr query syntax, of the form: `{param : <request_param_name>}`
When a `filter` option is combined with other `domain` changing options, the filtering is applied _after_ the other domain changes take place.
== Filter Exclusions
Domains can also be expanded by using the `excludeTags` keyword to discard or ignore particular tagged query filters.
This is used in the example below to show the top two manufacturers matching a search. The search results match the filter `manu_id_s:apple`, but the computed facet discards this filter and operates a domain widened by discarding the `manu_id_s` filter.
[.dynamic-tabs]
--
[example.tab-pane#curl-json-facet-excludetags-domain]
====
[.tab-label]*curl*
[source,bash]
----
curl http://localhost:8983/solr/techproducts/query -d '
{
"query": "cat:electronics",
"filter": "{!tag=MANU}manu_id_s:apple",
"facet": {
"stock": {"type": "terms", "field": "inStock", "limit": 2},
"manufacturers": {
"type": "terms",
"field": "manu_id_s",
"limit": 2,
"domain": { "excludeTags":"MANU" }
}
}
}'
----
====
[example.tab-pane#solrj-json-facet-excludetags-domain]
====
[.tab-label]*SolrJ*
[source,java,indent=0]
----
include::{example-source-dir}JsonRequestApiTest.java[tag=solrj-json-facet-excludetags-domain]
----
====
--
The value of `excludeTags` can be a single string tag, array of string tags or comma-separated tags in the single string.
When an `excludeTags` option is combined with other `domain` changing options, it expands the domain _before_ any other domain changes take place.
See also the section on <<faceting.adoc#tagging-and-excluding-filters,multi-select faceting>>.
== Arbitrary Domain Query
A `query` domain can be specified when you wish to compute a facet against an arbitrary set of documents, regardless of the original domain. The most common use case would be to compute a top level facet against a specific subset of the collection, regardless of the main query. But it can also be useful on nested facets when building <<json-facet-api.adoc#relatedness-and-semantic-knowledge-graphs,Semantic Knowledge Graphs>>.
Example:
[.dynamic-tabs]
--
[example.tab-pane#curl-json-facet-query-domain]
====
[.tab-label]*curl*
[source,bash]
----
curl http://localhost:8983/solr/techproducts/query -d '
{
"query": "apple",
"facet": {
"popular_categories": {
"type": "terms",
"field": "cat",
"domain": { "query": "popularity:[8 TO 10]" },
"limit": 3
}
}
}'
----
====
[example.tab-pane#solrj-json-facet-query-domain]
====
[.tab-label]*SolrJ*
[source,java,indent=0]
----
include::{example-source-dir}JsonRequestApiTest.java[tag=solrj-json-facet-query-domain]
----
====
--
The value of `query` can be a single query, or a JSON list of queries. Each query can be:
* a string containing a query in Solr query syntax
* a reference to a request parameter containing Solr query syntax, of the form: `{param: <request_param_name>}`
NOTE: While a `query` domain can be combined with an additional domain `filter`, It is not possible to also use `excludeTags`, because the tags would be meaningless: The `query` domain already completely ignores the top-level query and all previous filters.
== Block Join Domain Changes
When a collection contains <<uploading-data-with-index-handlers.adoc#nested-child-documents, Block Join child documents>>, the `blockChildren` or `blockParent` domain options can be used transform an existing domain containing one type of document, into a domain containing the documents with the specified relationship (child or parent of) to the documents from the original domain.
Both of these options work similarly to the corresponding <<other-parsers.adoc#block-join-query-parsers,Block Join Query Parsers>> by taking in a single String query that exclusively matches all parent documents in the collection. If `blockParent` is used, then the resulting domain will contain all parent documents of the children from the original domain. If `blockChildren` is used, then the resulting domain will contain all child documents of the parents from the original domain.
[source,json,subs="verbatim,callouts"]]
----
{
"colors": { // <1>
"type": "terms",
"field": "sku_color", // <2>
"facet" : {
"brands" : {
"type": "terms",
"field": "product_brand", // <3>
"domain": {
"blockParent": "doc_type:product"
}
}}}}
----
<1> This example assumes we parent documents corresponding to Products, with child documents corresponding to individual SKUs with unique colors, and that our original query was against SKU documents.
<2> The `colors` facet will be computed against all of the original SKU documents matching our search.
<3> For each bucket in the `colors` facet, the set of all matching SKU documents will be transformed into the set of corresponding parent Product documents. The resulting `brands` sub-facet will count how many Product documents (that have SKUs with the associated color) exist for each Brand.
== Join Query Domain Changes
A `join` domain change option can be used to specify arbitrary `from` and `to` fields to use in transforming from the existing domain to a related set of documents.
This works very similar to the <<other-parsers.adoc#join-query-parser,Join Query Parser>>, and has the same limitations when dealing with multi-shard collections.
Example:
[source,json]
----
{
"colors": {
"type": "terms",
"field": "sku_color",
"facet": {
"brands": {
"type": "terms",
"field": "product_brand",
"domain" : {
"join" : {
"from": "product_id_of_this_sku",
"to": "id"
},
"filter": "doc_type:product"
}
}
}
}
}
----
== Graph Traversal Domain Changes
A `graph` domain change option works similarly to the `join` domain option, but can do traversal multiple hops `from` the existing domain `to` other documents.
This works very similar to the <<other-parsers.adoc#graph-query-parser,Graph Query Parser>>, supporting all of it's optional parameters, and has the same limitations when dealing with multi-shard collections.
Example:
[source,json]
----
{
"related_brands": {
"type": "terms",
"field": "brand",
"domain": {
"graph": {
"from": "related_product_ids",
"to": "id",
"maxDepth": 3
}
}
}
}
----

View File

@ -1,4 +1,6 @@
= JSON Query DSL
: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
@ -16,172 +18,362 @@
// specific language governing permissions and limitations
// under the License.
The JSON Query DSL provides a simple yet powerful query language for the JSON Request API.
Queries and filters provided in JSON requests can be specified using a rich, powerful query DSL.
== Structure of JSON Query DSL
A JSON query can be:
== Query DSL Structure
The JSON Request API accepts query values in three different formats:
* A valid <<the-standard-query-parser.adoc#the-standard-query-parser,query string>> for default `deftype` (the standard query parser in most cases), as in, `title:solr`.
* A valid <<the-standard-query-parser.adoc#the-standard-query-parser,query string>> that uses the default `deftype` (`lucene`, in most cases). e.g. `title:solr`.
* A valid <<local-parameters-in-queries.adoc#local-parameters-in-queries,local parameters>> query string, as in, `{!dismax qf=myfield}solr rocks`.
* A valid <<local-parameters-in-queries.adoc#local-parameters-in-queries,local parameters query string>> that specifies its `deftype` explicitly. e.g. `{!dismax qf=title}solr`.
* A JSON object with query parser name and arguments.
The special key `v` in local parameters is replaced by key `query` in JSON object query, as in this example:
* A valid JSON object with the name of the query parser and any relevant parameters. e.g. `{ "lucene": {"df":"title", "query":"solr"}}`.
** The top level "query" JSON block generally only has a single property representing the name of the query parser to use. The value for the query parser property is a child block containing any relevant parameters as JSON properties. The whole structure is analogous to a "local-params" query string. The query itself (often represented in local params using the name `v`) is specified with the key `query` instead.
[source,json]
All of these syntaxes can be used to specify queries for either the JSON Request API's `query` or `filter` properties.
=== Query DSL Examples
The examples below show how to use each of the syntaxes discussed above to represent a query. Each snippet represents the same basic search: the term `iPod` in a field called `name`:
. Using the standard query API, with a simple query string
+
[.dynamic-tabs]
--
[example.tab-pane#curl-ipod-query-basic]
====
[.tab-label]*curl*
[source,bash]
----
curl -X GET "http://localhost:8983/solr/techproducts/query?q=name:iPod"
----
====
[example.tab-pane#solrj-ipod-query-basic]
====
[.tab-label]*SolrJ*
[source,java,indent=0]
----
include::{example-source-dir}JsonRequestApiTest.java[tag=solrj-ipod-query-basic]
----
====
--
. Using the JSON Request API, with a simple query string
+
[.dynamic-tabs]
--
[example.tab-pane#curl-ipod-query-dsl-1]
====
[.tab-label]*curl*
[source,bash]
----
curl -X POST http://localhost:8983/solr/techproducts/query -d '
{
"query-parser-name" : {
"param1": "value1",
"param2": "value2",
"query": "a-json-query",
"another-param": "another-json-query"
"query" : "name:iPod"
}'
----
====
[example.tab-pane#solrj-ipod-query-dsl-1]
====
[.tab-label]*SolrJ*
[source,java,indent=0]
----
include::{example-source-dir}JsonRequestApiTest.java[tag=solrj-ipod-query-dsl-1]
----
====
--
. Using the JSON Request API, with a local-params string
+
[.dynamic-tabs]
--
[example.tab-pane#curl-ipod-query-dsl-2]
====
[.tab-label]*curl*
[source,bash]
----
curl -X POST http://localhost:8983/solr/techproducts/query -d '
{
"query": "{!lucene df=name v=iPod}"
}'
----
====
[example.tab-pane#solrj-ipod-query-dsl-2]
====
[.tab-label]*SolrJ*
[source,java,indent=0]
----
include::{example-source-dir}JsonRequestApiTest.java[tag=solrj-ipod-query-dsl-2]
----
====
--
. Using the JSON Request API, with a fully expanded JSON object
+
[.dynamic-tabs]
--
[example.tab-pane#curl-ipod-query-dsl-3]
====
[.tab-label]*curl*
[source,bash]
----
curl -X POST http://localhost:8983/solr/techproducts/query -d '
{
"query": {
"lucene": {
"df": "name",
"query": "iPod"
}
}
}
== Basic Examples
The four requests below are equivalent for searching for `solr lucene` in a field named `content`:
. Passing all parameters on URI, with "lucene" as the default query parser.
+
[source,bash]
curl -XGET "http://localhost:8983/solr/books/query?q=content:(solr lucene)"
. Using the JSON Query DSL with valid query string for default `deftype`, with "lucene" as default query parser.
+
[source,bash]
curl -XGET http://localhost:8983/solr/books/query -d '
{"query": "content:(solr lucene)"}'
. Using JSON Query DSL with valid local parameters query defining the "lucene" query parser.
+
[source,bash]
curl -XGET http://localhost:8983/solr/books/query -d '
{"query": "{!lucene df=content v='solr lucene'}"}'
. Using JSON Query DSL in verbose way, as a valid JSON object with parser name and arguments.
+
[source,bash]
curl -XGET http://localhost:8983/solr/books/query -d '
{"query": {"lucene": {
"df": "content",
"query": "solr lucene"
}
}}'
Note that the JSON query in the examples above is provided under the key `query` of <<json-request-api.adoc#json-request-api,JSON Request API>>.
}'
----
====
[example.tab-pane#solrj-ipod-query-dsl-3]
====
[.tab-label]*SolrJ*
[source,java,indent=0]
----
include::{example-source-dir}JsonRequestApiTest.java[tag=solrj-ipod-query-dsl-3]
----
====
--
== Nested Queries
Some query parsers accept a query as an argument. JSON Query DSL makes it easier to write and read such complex query.
Many of Solr's query parsers allow queries to be nested within one another. When these are used, requests using the standard query API quickly become hard to write, read, and understand. These sorts of queries are often much easier to work with in the JSON Request API.
The three requests below are equivalent for wrapping the above example query (searching for `solr lucene` in field `content`) with a boost query:
=== Nested Boost Query Example
As an example, consider the three requests below, which wrap a simple query (the term `iPod` in the field `name`) within a boost query:
. Passing all parameters on URI.
. Using the standard query API.
+
[.dynamic-tabs]
--
[example.tab-pane#curl-ipod-query-boosted-basic]
====
[.tab-label]*curl*
[source,bash]
http://localhost:8983/solr/books/query?q={!boost b=log(popularity) v='{!lucene df=content}(lucene solr)'}
----
curl -X GET "http://localhost:8983/solr/techproducts/query?q={!boost b=log(popularity) v=\'{!lucene df=name}iPod\'}"
----
====
[example.tab-pane#solrj-ipod-query-boosted-basic]
====
[.tab-label]*SolrJ*
[source,java,indent=0]
----
include::{example-source-dir}JsonRequestApiTest.java[tag=solrj-ipod-query-boosted-basic]
----
====
--
. Converted into JSON Query DSL with use of local parameters.
As you can see, the special key `v` is replaced by key `query`.
. Using the JSON Request API, with a mix of fully-expanded and local-params queries.
As you can see, the special key `v` is replaced with the key `query`.
+
[.dynamic-tabs]
--
[example.tab-pane#curl-ipod-query-boosted-dsl-1]
====
[.tab-label]*curl*
[source,bash]
curl -XGET http://localhost:8983/solr/books/query -d '
----
curl -X POST http://localhost:8983/solr/techproducts/query -d '
{
"query" : {
"boost": {
"query": {!lucene df=content}(lucene solr),
"query": {!lucene df=name}iPod,
"b": "log(popularity)"
}
}
}'
----
====
[example.tab-pane#solrj-ipod-query-boosted-dsl-1]
====
[.tab-label]*SolrJ*
[source,java,indent=0]
----
include::{example-source-dir}JsonRequestApiTest.java[tag=solrj-ipod-query-boosted-dsl-1]
----
====
--
. Using a verbose JSON Query DSL without local parameters.
. Using the JSON Request API, with all queries fully expanded as JSON
+
[.dynamic-tabs]
--
[example.tab-pane#curl-ipod-query-boosted-dsl-2]
====
[.tab-label]*curl*
[source,bash]
curl -XGET http://localhost:8983/solr/books/query -d '
----
curl -X POST http://localhost:8983/solr/techproducts/query -d '
{
"query": {
"boost": {
"query": {
"lucene": {
"df": "content",
"query": "solr lucene"
"df": "name",
"query": "iPod"
}
},
"b": "log(popularity)"
}
}
}'
----
====
[example.tab-pane#solrj-ipod-query-boosted-dsl-2]
====
[.tab-label]*SolrJ*
[source,java,indent=0]
----
include::{example-source-dir}JsonRequestApiTest.java[tag=solrj-ipod-query-boosted-dsl-2]
----
====
--
== Compound Queries
With the support of the <<other-parsers.adoc#boolean-query-parser,BoolQParser>>, the JSON Query DSL can create a very powerful nested query.
=== Nested Boolean Query Example
Query nesting is commonly seen when combining multiple query clauses together using pseudo-boolean logic with the <<other-parsers.adoc#boolean-query-parser,BoolQParser>>.
This query searches for books where `content` contains `lucene` or `solr`, `title` contains `solr` and their `ranking` must larger than 3.0:
The example below shows how the `BoolQParser` can be used to create powerful nested queries. In this example, a user searches for results with `iPod` in the field `name` which are _not_ in the bottom half of the `popularity` rankings.
[.dynamic-tabs]
--
[example.tab-pane#curl-ipod-query-bool]
====
[.tab-label]*curl*
[source,bash]
curl -XGET http://localhost:8983/solr/books/query -d '
----
curl -X POST http://localhost:8983/solr/techproducts/query -d '
{
"query": {
"bool": {
"must": [
"title:solr",
{"lucene": {"df: "content", query: "lucene solr"}}
{"lucene": {"df": "name", query: "iPod"}}
],
"must_not": [
{"frange": {"u": "3.0", query: "ranking"}}
{"frange": {"l": "0", "u": "5", "query": "popularity"}}
]
}
}
}'
If lucene is the default query parser query, the above can be rewritten in much less verbose way as in:
----
====
[example.tab-pane#solrj-ipod-query-bool]
====
[.tab-label]*SolrJ*
[source,java,indent=0]
----
include::{example-source-dir}JsonRequestApiTest.java[tag=solrj-ipod-query-bool]
----
====
--
If lucene is the default query parser, the example above can be simplified to:
[.dynamic-tabs]
--
[example.tab-pane#curl-ipod-query-bool-condensed]
====
[.tab-label]*curl*
[source,bash]
curl -XGET http://localhost:8983/solr/books/query -d '
----
curl -X POST http://localhost:8983/solr/techproducts/query -d '
{
"query": {
"bool": {
"must": [
"title:solr",
"content:(lucene solr)"
"name:iPod"
],
"must_not": "{!frange u:3.0}ranking"
"must_not": "{!frange l=0 u=5}popularity"
}
}
}'
== Use JSON Query DSL in JSON Request API
JSON Query DSL is not only supported with the key `query` but also with the key `filter` of the <<json-request-api.adoc#json-request-api,JSON Request API>>.
----
====
[example.tab-pane#solrj-ipod-query-bool-condensed]
====
[.tab-label]*SolrJ*
[source,java,indent=0]
----
include::{example-source-dir}JsonRequestApiTest.java[tag=solrj-ipod-query-bool-condensed]
----
====
--
For example, the above query can be rewritten using filter clause like this:
== Filter Queries
The syntaxes discussed above can also be used to specify query filters (under the `filter` key) in addition to the main query itself.
For example, the above query can be rewritten using a filter clause as:
[.dynamic-tabs]
--
[example.tab-pane#curl-ipod-query-bool-filter]
====
[.tab-label]*curl*
[source,bash]
curl -XGET http://localhost:8983/solr/books/query -d '
----
curl -X POST http://localhost:8983/solr/techproducts/query -d '
{
"query": {
"bool": {
"must_not": "{!frange u:3.0}ranking"
"must_not": "{!frange l=0 u=5}popularity"
}
},
"filter: [
"title:solr",
{ "lucene" : {"df: "content", query : "lucene solr" }}
"name:iPod"
]
}'
----
====
[example.tab-pane#solrj-ipod-query-bool-filter]
====
[.tab-label]*SolrJ*
[source,java,indent=0]
----
include::{example-source-dir}JsonRequestApiTest.java[tag=solrj-ipod-query-bool-filter]
----
====
--
== Tagging in JSON Query DSL
Queries and filters might be tagged by inserting JSON object with the single property with name starting with a hash (`#`). Tags can be applied to the single string syntax and to a full JSON object query as well:
Query and filter clauses can also be individually "tagged". Tags serve as handles for query clauses, allowing them to be referenced from elsewhere in the request. This is most commonly used by the filter-exclusion functionality offered by both <<faceting.adoc#tagging-and-excluding-filters,traditional>> and <<json-faceting-domain-changes.adoc#filter-exclusions,JSON>> faceting.
Queries and filters are tagged by wrapping them in a surrounding JSON object. The name of the tag is specified as a JSON key, with the query string (or object) becoming the value associated with that key. Tag name properties are prefixed with a hash, and may include multiple tags, separated by commas. For example: `{"\#title,tag2,tag3":"title:solr"}`. Note that unlike the rest of the JSON request API which uses lax JSON parsing rules, tags must be surrounded by double-quotes because of the leading `#` character. The example below creates two tagged clauses: `titleTag` and `inStockTag`.
[.dynamic-tabs]
--
[example.tab-pane#curl-tagged-query]
====
[.tab-label]*curl*
[source,bash]
curl -XGET http://localhost:8983/solr/books/query -d '
----
curl -X POST http://localhost:8983/solr/techproducts/select -d '
{
"filter: [ // this applies `titletag`
{"#titletag": "title:solr"},
{ // this applies `tagcontent`
"#tagcontent":
{"lucene": {"df:"content", query :"lucene solr"}}
}
]
"query": "*:*",
"filter": [
{
"#titleTag": "name:Solr"
},
{
"#inStockTag": "inStock:true"
}
]
}'
There might be many comma separated tags like `{"#title,tag2,tag3":"title:solr"}`. Tags can be used in <<faceting.adoc#tagging-and-excluding-filters,old facets>> and in <<json-facet-api.adoc#filter-exclusions,JSON Facets'>> `excludeTags` as well. Notice that the leading hash requires to use double quotes as in regular JSON.
----
====
[example.tab-pane#solrj-tagged-query]
====
[.tab-label]*SolrJ*
[source,java,indent=0]
----
include::{example-source-dir}JsonRequestApiTest.java[tag=solrj-tagged-query]
----
====
--
Note that the tags created in the example above have no impact in how the search is executed. Tags will not affect a query unless they are referenced by some other part of the request that uses them.

View File

@ -19,18 +19,13 @@
// specific language governing permissions and limitations
// under the License.
The JSON Request API allows a JSON body to be passed for the entire search request.
Solr supports an alternate request API which accepts requests composed in part or entirely of JSON objects. This alternate API can be preferable in some situations, where its increased readability and flexibility make it easier to use than the entirely query-param driven alternative. There is also some functionality which can only be accessed through this JSON request API, such as much of the analytics capabilities of <<json-facet-api.adoc#json-facet-api,JSON Faceting>>
The <<json-facet-api.adoc#json-facet-api,JSON Facet API>> is part of the JSON Request API, and allows specification of faceted analytics in JSON.
Here's an example of a search request using query parameters only:
[source,bash]
curl "http://localhost:8983/solr/techproducts/query?q=memory&fq=inStock:true"
The same request when passed as JSON in the body:
== Building JSON Requests
The core of the JSON Request API is its ability to specify request parameters as JSON in the request body, as shown by the example below:
[.dynamic-tabs]
--
[example.tab-pane#curlsimplejsonquery]
[example.tab-pane#curl-simple-json-query]
====
[.tab-label]*curl*
[source,bash]
@ -42,11 +37,9 @@ curl http://localhost:8983/solr/techproducts/query -d '
}'
----
====
[example.tab-pane#solrjsimplejsonquery]
[example.tab-pane#solrj-simple-json-query]
====
[.tab-label]*SolrJ*
[source,java,indent=0]
----
include::{example-source-dir}JsonRequestApiTest.java[tag=solrj-json-query-simple]
@ -54,56 +47,109 @@ include::{example-source-dir}JsonRequestApiTest.java[tag=solrj-json-query-simple
====
--
== Passing JSON via Request Parameter
It may sometimes be more convenient to pass the JSON body as a request parameter rather than in the actual body of the HTTP request. Solr treats a `json` parameter the same as a JSON body.
JSON objects are typically sent in the request body, but they can also be sent as values for `json`-prefixed query parameters. This can be used to override or supplement values specified in the request body. For example the query parameter `json.limit=5` will override any `limit` value provided in the JSON request body. You can also specify the entire JSON body in a single `json` query parameter, as shown in the example below:
[source,bash]
curl http://localhost:8983/solr/techproducts/query -d 'json={"query":"memory"}'
== Smart Merging of Multiple JSON Parameters
Multiple `json` parameters in a single request are merged before being interpreted.
=== JSON Parameter Merging
If multiple `json` parameters are provided in a single request, Solr attempts to merge the parameter values together before processing the request.
* Single-valued elements are overwritten by the last value.
The JSON Request API has several properties (`filter`, `fields`, etc) which accept multiple values. During the merging process, all values for these "multivalued" properties are retained. Many properties though (`query`, `limit`, etc.) can have only a single value. When multiple parameter values conflict with one another a single value is chosen based on the following precedence rules:
* Multi-valued elements like fields and `filter` are appended.
* Traditional query parameters (`q`, `rows`, etc.) take first precedence and are used over any other specified values.
* `json`-prefixed query parameters are considered next.
* Values specified in the JSON request body have the lowest precedence and are only used if specified nowhere else.
* Parameters of the form `json.<path>=<json_value>` are merged in the appropriate place in the hierarchy. For example a `json.facet` parameter is the same as `facet` within the JSON body.
This layered merging gives the best of both worlds. Requests can be specified using readable, structured JSON. But users also have the flexibility to separate out parts of the request that change often. This can be seen at work in the following example, which combines `json.`-style parameters to override and supplement values found in the main JSON body.
* A JSON body, or straight `json` parameters are always parsed first, meaning that other request parameters come after, and overwrite single valued elements.
Smart merging gives the best of both worlds…the structure of JSON with the ability to selectively separate out / decompose parts of the request!
=== Simple Example
[.dynamic-tabs]
--
[example.tab-pane#curl-json-query-param-overrides]
====
[.tab-label]*curl*
[source,bash]
----
curl 'http://localhost:8983/solr/techproducts/query?json.limit=5&json.filter="cat:electronics"' -d '
{
query: "memory",
limit: 10,
filter: "inStock:true"
}'
----
====
[example.tab-pane#solrj-json-query-param-overrides]
====
[.tab-label]*SolrJ*
[source,java,indent=0]
----
include::{example-source-dir}JsonRequestApiTest.java[tag=solrj-json-query-param-overrides]
----
====
--
Is equivalent to:
This is equivalent to:
[.dynamic-tabs]
--
[example.tab-pane#curl-json-query-param-overrides-equivalent]
====
[.tab-label]*curl*
[source,bash]
----
curl http://localhost:8983/solr/techproducts/query -d '
{
query: "memory",
limit: 5, // this single-valued parameter was overwritten.
filter: ["inStock:true","cat:electronics"] // this multi-valued parameter was appended to.
"query": "memory",
"limit": 5, // this single-valued parameter was overwritten.
"filter": ["inStock:true","cat:electronics"] // this multi-valued parameter was appended to.
}'
----
====
[example.tab-pane#solrj-json-query-param-overrides-equivalent]
====
[.tab-label]*SolrJ*
[source,java,indent=0]
----
include::{example-source-dir}JsonRequestApiTest.java[tag=solrj-json-query-param-overrides-equivalent]
----
====
--
=== Facet Example
In fact, you dont even need to start with a JSON body for smart merging to be very useful. Consider the following request composed entirely of request params:
Similarly, smart merging can be used to create JSON API requests which have no proper request body at all, such as the example below:
[.dynamic-tabs]
--
[example.tab-pane#curl-json-facet-all-query-params]
====
[.tab-label]*curl*
[source,bash]
----
curl http://localhost:8983/solr/techproducts/query -d 'q=*:*&rows=1&
json.facet.avg_price="avg(price)"&
json.facet.top_cats={type:terms,field:"cat",limit:5}'
json.facet.top_cats={type:terms,field:"cat",limit:3}'
----
====
[example.tab-pane#solrj-json-facet-all-query-params]
====
[.tab-label]*SolrJ*
[source,java,indent=0]
----
include::{example-source-dir}JsonRequestApiTest.java[tag=solrj-json-facet-all-query-params]
----
====
--
That is equivalent to having the following JSON body or `json` parameter:
[source,json]
That is equivalent to the following request:
[.dynamic-tabs]
--
[example.tab-pane#curl-json-facet-all-query-params-equivalent]
====
[.tab-label]*curl*
[source,bash]
----
curl http://localhost:8983/solr/techproducts/query -d '
{
"query": "*:*",
"limit": 1,
"facet": {
"avg_price": "avg(price)",
"top_cats": {
@ -113,53 +159,24 @@ That is equivalent to having the following JSON body or `json` parameter:
}
}
}
See the <<json-facet-api.adoc#json-facet-api,JSON Facet API>> for more on faceting and analytics commands in specified in JSON.
=== Debugging
If you want to see what your merged/parsed JSON looks like, you can turn on debugging (`debug=timing`), and it will come back under the "json" key along with the other debugging information.
Note: `debug=true` as well as `debugQuery=true` might have too much performance implication and `debug=query` makes no effect on JSON facet in SolrCloud.
== Passing Parameters via JSON
We can also pass normal query request parameters in the JSON body within the params block:
[.dynamic-tabs]
--
[example.tab-pane#curljsonqueryparamsblock]
====
[.tab-label]*curl*
[source,bash]
----
curl "http://localhost:8983/solr/techproducts/query?fl=name,price"-d '
{
params: {
q: "memory",
rows: 1
}
}'
'
----
====
[example.tab-pane#solrjjsonqueryparamsblock]
[example.tab-pane#solrj-json-facet-all-query-params-equivalent]
====
[.tab-label]*SolrJ*
[source,java,indent=0]
----
include::{example-source-dir}JsonRequestApiTest.java[tag=solrj-json-query-params-block]
include::{example-source-dir}JsonRequestApiTest.java[tag=solrj-json-facet-all-query-params-equivalent]
----
====
--
Which is equivalent to:
See the <<json-facet-api.adoc#json-facet-api,JSON Facet API>> for more on faceting and analytics commands.
[source,bash]
curl "http://localhost:8983/solr/techproducts/query?fl=name,price&q=memory&rows=1"
== Supported Properties and Syntax
== Parameters Mapping
Right now only some standard query parameters have JSON equivalents. Unmapped parameters can be passed through request parameters or `params` block as shown above.
Right now, only some of Solr's traditional query parameters have first class JSON equivalents. Those that do are shown in the table below:
.Standard query parameters to JSON field
|===
@ -190,29 +207,49 @@ Right now only some standard query parameters have JSON equivalents. Unmapped pa
|`<param_name>`
|===
== Error Detection
Parameters not specified in the table above can still be used in the main body of JSON API requests, but they must be put within a `params` block as shown in the example below.
Because we didnt pollute the root body of the JSON request with the normal Solr request parameters (they are all contained in the params block), we now have the ability to validate requests and return an error for unknown JSON keys.
[.dynamic-tabs]
--
[example.tab-pane#curl-json-query-params-block]
====
[.tab-label]*curl*
[source,bash]
----
curl "http://localhost:8983/solr/techproducts/query?fl=name,price"-d '
{
params: {
q: "memory",
rows: 1
}
}'
----
====
[example.tab-pane#solrj-json-query-params-block]
====
[.tab-label]*SolrJ*
[source,java,indent=0]
----
include::{example-source-dir}JsonRequestApiTest.java[tag=solrj-json-query-params-block]
----
====
--
Parameters placed in a `params` block act as if they were added verbatim to the query-parameters of the request. The request above is equivalent to:
[source,bash]
curl http://localhost:8983/solr/techproducts/query -d '
{
query : "memory",
fulter : "inStock:true" // oops, we misspelled "filter"
}'
curl "http://localhost:8983/solr/techproducts/query?fl=name,price&q=memory&rows=1"
And we get an error back containing the error string:
[source,text]
"Unknown top-level key in JSON request : fulter"
== Parameter Substitution / Macro Expansion
=== Parameter Substitution / Macro Expansion
Of course request templating via parameter substitution works fully with JSON request bodies or parameters as well.
For example:
[.dynamic-tabs]
--
[example.tab-pane#curljsonquerymacroexpansion]
[example.tab-pane#curl-json-query-macro-expansion]
====
[.tab-label]*curl*
[source,bash]
@ -224,7 +261,7 @@ curl "http://localhost:8983/solr/techproducts/query?FIELD=text&TERM=memory" -d '
----
====
[example.tab-pane#solrjjsonquerymacroexpansion]
[example.tab-pane#solrj-json-query-macro-expansion]
====
[.tab-label]*SolrJ*
@ -234,3 +271,20 @@ include::{example-source-dir}JsonRequestApiTest.java[tag=solrj-json-query-macro-
----
====
--
=== JSON Extensions
Solr uses the *Noggit* JSON parser in its request API. Noggit is capable of more relaxed JSON parsing, and allows a number of deviations from the JSON standard:
* bare words can be left unquoted
* single line comments can be inserted using either `//` or `#`
* Multi-line ("C style") comments can be inserted using `/\*` and `*/`
* strings can be single-quoted
* special characters can be backslash-escaped
* trailing (extra) commas are silently ignored (e.g. `[9,4,3,]`)
* nbsp (non-break space, \u00a0) is treated as whitespace.
== Debugging
If you want to see what your merged/parsed JSON looks like, you can turn on debugging (`debug=timing`), and it will come back under the "json" key along with the other debugging information.
Note that `debug=true` and `debugQuery=true` can often have significant performance implications, and should be reserved for debugging. Also note that `debug=query` has no effect on JSON facets in SolrCloud.

View File

@ -0,0 +1,108 @@
/*
* 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.ref_guide_examples;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.apache.solr.client.solrj.request.CollectionAdminRequest;
import org.apache.solr.client.solrj.request.json.HeatmapFacetMap;
import org.apache.solr.client.solrj.request.json.JsonQueryRequest;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.client.solrj.response.json.HeatmapJsonFacet;
import org.apache.solr.client.solrj.response.json.NestableJsonFacet;
import org.apache.solr.cloud.SolrCloudTestCase;
import org.apache.solr.common.SolrInputDocument;
import org.apache.solr.util.ExternalPaths;
import org.junit.BeforeClass;
import org.junit.Test;
/**
* Example SolrJ usage Heatmap facets in the JSON Request API.
*
* Snippets surrounded by "tag" and "end" comments are extracted and used in the Solr Reference Guide.
* <p>
* This class is mostly copied from {@link org.apache.solr.client.solrj.request.json.JsonQueryRequestHeatmapFacetingTest}.
* The test was duplicated here as the community has previously decided that it's best to keep all buildable ref-guide
* snippets together in the same package.
*/
public class JsonRequestApiHeatmapFacetingTest extends SolrCloudTestCase {
private static final String COLLECTION_NAME = "spatialdata";
private static final String CONFIG_NAME = "spatialdata_config";
private static final String FIELD = "location_srpt";
@BeforeClass
public static void setupCluster() throws Exception {
configureCluster(1)
.addConfig(CONFIG_NAME, new File(ExternalPaths.SOURCE_HOME, "solrj/src/test-files/solrj/solr/configsets/spatial/conf").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());
indexSpatialData();
}
private static void indexSpatialData() throws Exception {
final SolrInputDocument doc1 = new SolrInputDocument("id", "0", FIELD, "ENVELOPE(100, 120, 80, 40)");
final SolrInputDocument doc2 = new SolrInputDocument("id", "1", FIELD, "ENVELOPE(-120, -110, 80, 20)");
final SolrInputDocument doc3 = new SolrInputDocument("id", "3", FIELD, "POINT(70 60)");
final SolrInputDocument doc4 = new SolrInputDocument("id", "4", FIELD, "POINT(91 89)");
final List<SolrInputDocument> docs = new ArrayList<>();
docs.add(doc1);
docs.add(doc2);
docs.add(doc3);
docs.add(doc4);
cluster.getSolrClient().add(COLLECTION_NAME, docs);
cluster.getSolrClient().commit(COLLECTION_NAME);
}
@Test
public void testHeatmapFacet() throws Exception {
final List<List<Integer>> expectedHeatmapGrid = Arrays.asList(
Arrays.asList(0, 0, 2, 1, 0, 0),
Arrays.asList(0, 0, 1, 1, 0, 0),
Arrays.asList(0, 1, 1, 1, 0, 0),
Arrays.asList(0, 0, 1, 1, 0, 0),
Arrays.asList(0, 0, 1, 1, 0, 0),
null,
null
);
//tag::solrj-json-heatmap-facet-1[]
final JsonQueryRequest request = new JsonQueryRequest()
.setQuery("*:*")
.setLimit(0)
.withFacet("locations", new HeatmapFacetMap("location_srpt")
.setHeatmapFormat(HeatmapFacetMap.HeatmapFormat.INTS2D)
.setRegionQuery("[\"50 20\" TO \"180 90\"]")
.setGridLevel(4)
);
//end::solrj-json-heatmap-facet-1[]
QueryResponse response = request.process(cluster.getSolrClient(), COLLECTION_NAME);
final NestableJsonFacet topLevelFacet = response.getJsonFacetingResponse();
final HeatmapJsonFacet heatmap = topLevelFacet.getHeatmapFacetByName("locations");
final List<List<Integer>> actualHeatmapGrid = heatmap.getCountGrid();
assertEquals(expectedHeatmapGrid, actualHeatmapGrid);
}
}

View File

@ -20,13 +20,19 @@ package org.apache.solr.client.ref_guide_examples;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.SolrQuery;
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.DomainMap;
import org.apache.solr.client.solrj.request.json.JsonQueryRequest;
import org.apache.solr.client.solrj.request.json.QueryFacetMap;
import org.apache.solr.client.solrj.request.json.RangeFacetMap;
import org.apache.solr.client.solrj.request.json.TermsFacetMap;
import org.apache.solr.client.solrj.response.json.BucketJsonFacet;
import org.apache.solr.client.solrj.response.json.NestableJsonFacet;
@ -79,8 +85,82 @@ public class JsonRequestApiTest extends SolrCloudTestCase {
QueryResponse queryResponse = simpleQuery.process(solrClient, COLLECTION_NAME);
// end::solrj-json-query-simple[]
assertEquals(0, queryResponse.getStatus());
assertEquals(expectedResults, queryResponse.getResults().size());
assertResponseFoundNumDocs(queryResponse, expectedResults);
}
@Test
public void testJsonQueryWithJsonQueryParamOverrides() throws Exception {
SolrClient solrClient = cluster.getSolrClient();
final int expectedResults = 4;
// This subtest has its own scope so that it and its twin below can can have identical variable declarations (as they appear as separate snippets in the ref-guide)
{
// tag::solrj-json-query-param-overrides[]
final ModifiableSolrParams overrideParams = new ModifiableSolrParams();
final JsonQueryRequest queryWithParamOverrides = new JsonQueryRequest(overrideParams)
.setQuery("memory")
.setLimit(10)
.withFilter("inStock:true");
overrideParams.set("json.limit", 5);
overrideParams.add("json.filter", "\"cat:electronics\"");
QueryResponse queryResponse = queryWithParamOverrides.process(solrClient, COLLECTION_NAME);
// end::solrj-json-query-param-overrides[]
assertResponseFoundNumDocs(queryResponse, expectedResults);
}
// tag::solrj-json-query-param-overrides-equivalent[]
final JsonQueryRequest query = new JsonQueryRequest()
.setQuery("memory")
.setLimit(5)
.withFilter("inStock:true")
.withFilter("cat:electronics");
QueryResponse queryResponse = query.process(solrClient, COLLECTION_NAME);
// end::solrj-json-query-param-overrides-equivalent[]
assertResponseFoundNumDocs(queryResponse, expectedResults);
}
@Test
public void testJsonFacetWithAllQueryParams() throws Exception {
SolrClient solrClient = cluster.getSolrClient();
// This subtest has its own scope so that it and its twin below can can have identical variable declarations (as they appear as separate snippets in the ref-guide)
{
//tag::solrj-json-facet-all-query-params[]
final ModifiableSolrParams params = new ModifiableSolrParams();
final SolrQuery query = new SolrQuery("*:*");
query.setRows(1);
query.setParam("json.facet.avg_price", "\"avg(price)\"");
query.setParam("json.facet.top_cats", "{type:terms,field:\"cat\",limit:3}");
QueryResponse queryResponse = solrClient.query(COLLECTION_NAME, query);
//end::solrj-json-facet-all-query-params[]
NestableJsonFacet topLevelFacet = queryResponse.getJsonFacetingResponse();
assertResponseFoundNumDocs(queryResponse, 1);
assertHasFacetWithBucketValues(topLevelFacet, "top_cats",
new FacetBucket("electronics", 12),
new FacetBucket("currency", 4),
new FacetBucket("memory", 3));
}
{
//tag::solrj-json-facet-all-query-params-equivalent[]
final JsonQueryRequest jsonQueryRequest = new JsonQueryRequest()
.setQuery("*:*")
.setLimit(1)
.withStatFacet("avg_price", "avg(price)")
.withFacet("top_cats", new TermsFacetMap("cat").setLimit(3));
QueryResponse queryResponse = jsonQueryRequest.process(solrClient, COLLECTION_NAME);
//end::solrj-json-facet-all-query-params-equivalent[]
NestableJsonFacet topLevelFacet = queryResponse.getJsonFacetingResponse();
assertResponseFoundNumDocs(queryResponse, 1);
assertHasFacetWithBucketValues(topLevelFacet, "top_cats",
new FacetBucket("electronics", 12),
new FacetBucket("currency", 4),
new FacetBucket("memory", 3));
}
}
@Test
@ -118,8 +198,206 @@ public class JsonRequestApiTest extends SolrCloudTestCase {
QueryResponse queryResponse = simpleQuery.process(solrClient, COLLECTION_NAME);
// end::solrj-json-query-macro-expansion[]
assertEquals(0, queryResponse.getStatus());
assertEquals(5, queryResponse.getResults().size());
assertResponseFoundNumDocs(queryResponse, 5);
}
@Test
public void testJsonQueryDslBasicEquivalents() throws Exception {
SolrClient solrClient = cluster.getSolrClient();
{
//tag::solrj-ipod-query-basic[]
final SolrQuery query = new SolrQuery("name:iPod");
final QueryResponse response = solrClient.query(COLLECTION_NAME, query);
//end::solrj-ipod-query-basic[]
assertResponseFoundNumDocs(response, 3);
}
{
//tag::solrj-ipod-query-dsl-1[]
final JsonQueryRequest query = new JsonQueryRequest()
.setQuery("name:iPod");
final QueryResponse response = query.process(solrClient, COLLECTION_NAME);
//end::solrj-ipod-query-dsl-1[]
assertResponseFoundNumDocs(response, 3);
}
{
//tag::solrj-ipod-query-dsl-2[]
final JsonQueryRequest query = new JsonQueryRequest()
.setQuery("{!lucene df=name}iPod");
final QueryResponse response = query.process(solrClient, COLLECTION_NAME);
//end::solrj-ipod-query-dsl-2[]
assertResponseFoundNumDocs(response, 3);
}
{
//tag::solrj-ipod-query-dsl-3[]
final Map<String, Object> queryTopLevel = new HashMap<>();
final Map<String, Object> luceneQueryProperties = new HashMap<>();
queryTopLevel.put("lucene", luceneQueryProperties);
luceneQueryProperties.put("df", "name");
luceneQueryProperties.put("query", "iPod");
final JsonQueryRequest query = new JsonQueryRequest()
.setQuery(queryTopLevel);
final QueryResponse response = query.process(solrClient, COLLECTION_NAME);
//end::solrj-ipod-query-dsl-3[]
assertResponseFoundNumDocs(response, 3);
}
}
@Test
public void testJsonQueryDslBoostEquivalents() throws Exception {
SolrClient solrClient = cluster.getSolrClient();
QueryResponse[] responses = new QueryResponse[3];
{
//tag::solrj-ipod-query-boosted-basic[]
final SolrQuery query = new SolrQuery("{!boost b=log(popularity) v=\'{!lucene df=name}iPod\'}");
final QueryResponse response = solrClient.query(COLLECTION_NAME, query);
//end::solrj-ipod-query-boosted-basic[]
responses[0] = response;
}
{
//tag::solrj-ipod-query-boosted-dsl-1[]
final Map<String, Object> queryTopLevel = new HashMap<>();
final Map<String, Object> boostQuery = new HashMap<>();
queryTopLevel.put("boost", boostQuery);
boostQuery.put("b", "log(popularity)");
boostQuery.put("query", "{!lucene df=name}iPod");
final JsonQueryRequest query = new JsonQueryRequest()
.setQuery(queryTopLevel);
final QueryResponse response = query.process(solrClient, COLLECTION_NAME);
//end::solrj-ipod-query-boosted-dsl-1[]
responses[1] = response;
}
{
//tag::solrj-ipod-query-boosted-dsl-2[]
final Map<String, Object> queryTopLevel = new HashMap<>();
final Map<String, Object> boostProperties = new HashMap<>();
final Map<String, Object> luceneTopLevel = new HashMap();
final Map<String, Object> luceneProperties = new HashMap<>();
queryTopLevel.put("boost", boostProperties);
boostProperties.put("b", "log(popularity)");
boostProperties.put("query", luceneTopLevel);
luceneTopLevel.put("lucene", luceneProperties);
luceneProperties.put("df", "name");
luceneProperties.put("query", "iPod");
final JsonQueryRequest query = new JsonQueryRequest()
.setQuery(queryTopLevel);
final QueryResponse response = query.process(solrClient, COLLECTION_NAME);
//end::solrj-ipod-query-boosted-dsl-2[]
responses[2] = response;
}
for (QueryResponse response : responses) {
assertResponseFoundNumDocs(response, 3);
assertEquals("MA147LL/A", response.getResults().get(0).get("id"));
assertEquals("F8V7067-APL-KIT", response.getResults().get(1).get("id"));
assertEquals("IW-02", response.getResults().get(2).get("id"));
}
}
@Test
public void testJsonBooleanQuery() throws Exception {
SolrClient solrClient = cluster.getSolrClient();
QueryResponse[] responses = new QueryResponse[3];
{
//tag::solrj-ipod-query-bool[]
final Map<String, Object> queryTopLevel = new HashMap<>();
final Map<String, Object> boolProperties = new HashMap<>();
final List<Object> mustClauses = new ArrayList<>();
final List<Object> mustNotClauses = new ArrayList<>();
final Map<String, Object> frangeTopLevel = new HashMap<>();
final Map<String, Object> frangeProperties = new HashMap<>();
queryTopLevel.put("bool", boolProperties);
boolProperties.put("must", mustClauses);
mustClauses.add("name:iPod");
boolProperties.put("must_not", mustNotClauses);
frangeTopLevel.put("frange", frangeProperties);
frangeProperties.put("l", 0);
frangeProperties.put("u", 5);
frangeProperties.put("query", "popularity");
mustNotClauses.add(frangeTopLevel);
final JsonQueryRequest query = new JsonQueryRequest()
.setQuery(queryTopLevel);
final QueryResponse response = query.process(solrClient, COLLECTION_NAME);
//end::solrj-ipod-query-bool[]
responses[0] = response;
}
{
//tag::solrj-ipod-query-bool-condensed[]
final Map<String, Object> queryTopLevel = new HashMap<>();
final Map<String, Object> boolProperties = new HashMap<>();
final List<Object> mustClauses = new ArrayList<>();
final List<Object> mustNotClauses = new ArrayList<>();
queryTopLevel.put("bool", boolProperties);
boolProperties.put("must", "name:iPod");
boolProperties.put("must_not", "{!frange l=0 u=5}popularity");
final JsonQueryRequest query = new JsonQueryRequest()
.setQuery(queryTopLevel);
final QueryResponse response = query.process(solrClient, COLLECTION_NAME);
//end::solrj-ipod-query-bool-condensed[]
responses[1] = response;
}
{
//tag::solrj-ipod-query-bool-filter[]
final Map<String, Object> queryTopLevel = new HashMap<>();
final Map<String, Object> boolProperties = new HashMap<>();
queryTopLevel.put("bool", boolProperties);
boolProperties.put("must_not","{!frange l=0 u=5}popularity");
final JsonQueryRequest query = new JsonQueryRequest()
.setQuery(queryTopLevel)
.withFilter("name:iPod");
final QueryResponse response = query.process(solrClient, COLLECTION_NAME);
//end::solrj-ipod-query-bool-filter[]
responses[2] = response;
}
for (QueryResponse response : responses) {
assertResponseFoundNumDocs(response, 1);
assertEquals("MA147LL/A", response.getResults().get(0).get("id"));
}
}
@Test
public void testJsonTaggedQuery() throws Exception {
SolrClient solrClient = cluster.getSolrClient();
//tag::solrj-tagged-query[]
final Map<String, Object> titleTaggedQuery = new HashMap<>();
titleTaggedQuery.put("#titleTag", "name:Solr");
final Map<String, Object> inStockTaggedQuery = new HashMap<>();
inStockTaggedQuery.put("#inStockTag", "inStock:true");
final JsonQueryRequest query = new JsonQueryRequest()
.setQuery("*:*")
.withFilter(titleTaggedQuery)
.withFilter(inStockTaggedQuery);
final QueryResponse response = query.process(solrClient, COLLECTION_NAME);
//end::solrj-tagged-query[]
assertResponseFoundNumDocs(response, 1);
assertEquals("SOLR1000", response.getResults().get(0).get("id"));
}
@Test
@ -148,13 +426,13 @@ public class JsonRequestApiTest extends SolrCloudTestCase {
public void testTermsFacet2() throws Exception {
SolrClient solrClient = cluster.getSolrClient();
//tag::solrj-json-terms-facet2[]
//tag::solrj-json-terms-facet-2[]
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[]
//end::solrj-json-terms-facet-2[]
assertEquals(0, queryResponse.getStatus());
assertEquals(32, queryResponse.getResults().getNumFound());
@ -168,6 +446,274 @@ public class JsonRequestApiTest extends SolrCloudTestCase {
new FacetBucket("graphics card", 2));
}
@Test
public void testStatFacet1() throws Exception {
SolrClient solrClient = cluster.getSolrClient();
//tag::solrj-json-metrics-facet-1[]
final JsonQueryRequest request = new JsonQueryRequest()
.setQuery("memory")
.withFilter("inStock:true")
.withStatFacet("avg_price", "avg(price)")
.withStatFacet("num_suppliers", "unique(manu_exact)")
.withStatFacet("median_weight", "percentile(weight,50)");
QueryResponse queryResponse = request.process(solrClient, COLLECTION_NAME);
//end::solrj-json-metrics-facet-1[]
assertEquals(0, queryResponse.getStatus());
assertEquals(4, queryResponse.getResults().getNumFound());
assertEquals(4, queryResponse.getResults().size());
final NestableJsonFacet topLevelFacetingData = queryResponse.getJsonFacetingResponse();
assertEquals(146.66, (double) topLevelFacetingData.getStatFacetValue("avg_price"), 0.5);
assertEquals(3, topLevelFacetingData.getStatFacetValue("num_suppliers"));
assertEquals(352.0, (double) topLevelFacetingData.getStatFacetValue("median_weight"), 0.5);
}
@Test
public void testStatFacetSimple() throws Exception {
SolrClient solrClient = cluster.getSolrClient();
//tag::solrj-json-metrics-facet-simple[]
final JsonQueryRequest request = new JsonQueryRequest()
.setQuery("*:*")
.withFilter("price:[1.0 TO *]")
.withFilter("popularity:[0 TO 10]")
.withStatFacet("avg_value", "avg(div(popularity,price))");
QueryResponse queryResponse = request.process(solrClient, COLLECTION_NAME);
//end::solrj-json-metrics-facet-simple[]
assertEquals(0, queryResponse.getStatus());
assertEquals(13, queryResponse.getResults().getNumFound());
assertEquals(10, queryResponse.getResults().size());
final NestableJsonFacet topLevelFacetingData = queryResponse.getJsonFacetingResponse();
assertEquals(0.036, (double) topLevelFacetingData.getStatFacetValue("avg_value"), 0.1);
}
@Test
public void testStatFacetExpanded() throws Exception {
SolrClient solrClient = cluster.getSolrClient();
//tag::solrj-json-metrics-facet-expanded[]
final Map<String, Object> expandedStatFacet = new HashMap<>();
expandedStatFacet.put("type", "func");
expandedStatFacet.put("func", "avg(div($numer,$denom))");
expandedStatFacet.put("numer", "mul(popularity,3.0)");
expandedStatFacet.put("denom", "price");
final JsonQueryRequest request = new JsonQueryRequest()
.setQuery("*:*")
.withFilter("price:[1.0 TO *]")
.withFilter("popularity:[0 TO 10]")
.withFacet("avg_value", expandedStatFacet);
QueryResponse queryResponse = request.process(solrClient, COLLECTION_NAME);
//end::solrj-json-metrics-facet-expanded[]
assertEquals(0, queryResponse.getStatus());
assertEquals(13, queryResponse.getResults().getNumFound());
assertEquals(10, queryResponse.getResults().size());
final NestableJsonFacet topLevelFacetingData = queryResponse.getJsonFacetingResponse();
assertEquals(0.108, (double) topLevelFacetingData.getStatFacetValue("avg_value"), 0.1);
}
@Test
public void testQueryFacetSimple() throws Exception {
SolrClient solrClient = cluster.getSolrClient();
//tag::solrj-json-query-facet-simple[]
QueryFacetMap queryFacet = new QueryFacetMap("popularity:[8 TO 10]");
final JsonQueryRequest request = new JsonQueryRequest()
.setQuery("*:*")
.withFacet("high_popularity", queryFacet);
QueryResponse queryResponse = request.process(solrClient, COLLECTION_NAME);
//end::solrj-json-query-facet-simple[]
assertEquals(0, queryResponse.getStatus());
assertEquals(32, queryResponse.getResults().getNumFound());
assertEquals(10, queryResponse.getResults().size());
final NestableJsonFacet topLevelFacetingData = queryResponse.getJsonFacetingResponse();
assertEquals(2, topLevelFacetingData.getQueryFacet("high_popularity").getCount());
}
@Test
public void testQueryFacetExpanded() throws Exception {
SolrClient solrClient = cluster.getSolrClient();
//tag::solrj-json-query-facet-expanded[]
QueryFacetMap queryFacet = new QueryFacetMap("popularity:[8 TO 10]")
.withStatSubFacet("average_price", "avg(price)");
final JsonQueryRequest request = new JsonQueryRequest()
.setQuery("*:*")
.withFacet("high_popularity", queryFacet);
QueryResponse queryResponse = request.process(solrClient, COLLECTION_NAME);
//end::solrj-json-query-facet-expanded[]
assertEquals(0, queryResponse.getStatus());
assertEquals(32, queryResponse.getResults().getNumFound());
assertEquals(10, queryResponse.getResults().size());
final NestableJsonFacet topLevelFacetingData = queryResponse.getJsonFacetingResponse();
assertEquals(2, topLevelFacetingData.getQueryFacet("high_popularity").getCount());
assertEquals(199.5, topLevelFacetingData.getQueryFacet("high_popularity").getStatFacetValue("average_price"));
}
@Test
public void testRangeFacetSimple() throws Exception {
SolrClient solrClient = cluster.getSolrClient();
//tag::solrj-json-range-facet-simple[]
RangeFacetMap rangeFacet = new RangeFacetMap("price", 0.0, 100.0, 20.0);
final JsonQueryRequest request = new JsonQueryRequest()
.setQuery("*:*")
.withFacet("prices", rangeFacet);
QueryResponse queryResponse = request.process(solrClient, COLLECTION_NAME);
//end::solrj-json-range-facet-simple[]
assertEquals(0, queryResponse.getStatus());
assertEquals(32, queryResponse.getResults().getNumFound());
assertEquals(10, queryResponse.getResults().size());
final NestableJsonFacet topLevelFacetingData = queryResponse.getJsonFacetingResponse();
assertHasFacetWithBucketValues(topLevelFacetingData,"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 testNestedFacetSimple() throws Exception {
SolrClient solrClient = cluster.getSolrClient();
//tag::solrj-json-nested-cat-facet[]
final TermsFacetMap topCategoriesFacet = new TermsFacetMap("cat").setLimit(3);
final TermsFacetMap topManufacturerFacet = new TermsFacetMap("manu_id_s").setLimit(1);
topCategoriesFacet.withSubFacet("top_manufacturers", topManufacturerFacet);
final JsonQueryRequest request = new JsonQueryRequest()
.setQuery("*:*")
.withFacet("categories", topCategoriesFacet);
QueryResponse queryResponse = request.process(solrClient, COLLECTION_NAME);
//end::solrj-json-nested-cat-facet[]
assertEquals(0, queryResponse.getStatus());
assertEquals(32, queryResponse.getResults().getNumFound());
assertEquals(10, queryResponse.getResults().size());
final NestableJsonFacet topLevelFacetingData = queryResponse.getJsonFacetingResponse();
assertHasFacetWithBucketValues(topLevelFacetingData, "categories",
new FacetBucket("electronics", 12),
new FacetBucket("currency", 4),
new FacetBucket("memory", 3));
// Check the top manufacturer for each category
List<BucketJsonFacet> catBuckets = topLevelFacetingData.getBucketBasedFacets("categories").getBuckets();
assertHasFacetWithBucketValues(catBuckets.get(0), "top_manufacturers",
new FacetBucket("corsair", 3));
assertHasFacetWithBucketValues(catBuckets.get(1), "top_manufacturers",
new FacetBucket("boa", 1));
assertHasFacetWithBucketValues(catBuckets.get(2), "top_manufacturers",
new FacetBucket("corsair", 3));
}
@Test
public void testFacetSortedByNestedMetric() throws Exception {
SolrClient solrClient = cluster.getSolrClient();
//tag::solrj-json-nested-cat-facet-sorted[]
final TermsFacetMap topCategoriesFacet = new TermsFacetMap("cat")
.setLimit(3)
.withStatSubFacet("avg_price", "avg(price)")
.setSort("avg_price desc");
final JsonQueryRequest request = new JsonQueryRequest()
.setQuery("*:*")
.withFacet("categories", topCategoriesFacet);
QueryResponse queryResponse = request.process(solrClient, COLLECTION_NAME);
//end::solrj-json-nested-cat-facet-sorted[]
assertEquals(0, queryResponse.getStatus());
assertEquals(32, queryResponse.getResults().getNumFound());
assertEquals(10, queryResponse.getResults().size());
final NestableJsonFacet topLevelFacetingData = queryResponse.getJsonFacetingResponse();
assertHasFacetWithBucketValues(topLevelFacetingData, "categories",
new FacetBucket("electronics and computer1", 1),
new FacetBucket("graphics card", 2),
new FacetBucket("music", 1));
}
@Test
public void testFacetFilteredDomain() throws Exception {
SolrClient solrClient = cluster.getSolrClient();
//tag::solrj-json-facet-filtered-domain[]
final TermsFacetMap categoryFacet = new TermsFacetMap("cat")
.setLimit(3)
.withDomain(new DomainMap().withFilter("popularity:[5 TO 10]"));
final JsonQueryRequest request = new JsonQueryRequest()
.setQuery("*:*")
.withFacet("categories", categoryFacet);
QueryResponse queryResponse = request.process(solrClient, COLLECTION_NAME);
//end::solrj-json-facet-filtered-domain[]
assertEquals(0, queryResponse.getStatus());
assertEquals(32, queryResponse.getResults().getNumFound());
assertEquals(10, queryResponse.getResults().size());
final NestableJsonFacet topLevelFacetingData = queryResponse.getJsonFacetingResponse();
assertHasFacetWithBucketValues(topLevelFacetingData, "categories",
new FacetBucket("electronics", 9),
new FacetBucket("graphics card", 2),
new FacetBucket("hard drive", 2));
}
@Test
public void testFacetWidenedExcludeTagsDomain() throws Exception {
SolrClient solrClient = cluster.getSolrClient();
//tag::solrj-json-facet-excludetags-domain[]
final TermsFacetMap inStockFacet = new TermsFacetMap("inStock").setLimit(2);
final TermsFacetMap allManufacturersFacet = new TermsFacetMap("manu_id_s")
.setLimit(2)
.withDomain(new DomainMap().withTagsToExclude("MANU"));
final JsonQueryRequest request = new JsonQueryRequest()
.setQuery("cat:electronics")
.withFilter("{!tag=MANU}manu_id_s:apple")
.withFacet("stock", inStockFacet)
.withFacet("manufacturers", allManufacturersFacet);
QueryResponse queryResponse = request.process(solrClient, COLLECTION_NAME);
//end::solrj-json-facet-excludetags-domain[]
assertEquals(0, queryResponse.getStatus());
assertEquals(1, queryResponse.getResults().getNumFound());
assertEquals(1, queryResponse.getResults().size());
final NestableJsonFacet topLevelFacetingData = queryResponse.getJsonFacetingResponse();
assertHasFacetWithBucketValues(topLevelFacetingData, "stock",
new FacetBucket(true, 1));
assertHasFacetWithBucketValues(topLevelFacetingData, "manufacturers",
new FacetBucket("corsair", 3),
new FacetBucket("belkin", 2));
}
@Test
public void testFacetWidenedUsingQueryDomain() throws Exception {
SolrClient solrClient = cluster.getSolrClient();
//tag::solrj-json-facet-query-domain[]
final TermsFacetMap inStockFacet = new TermsFacetMap("inStock").setLimit(2);
final TermsFacetMap popularCategoriesFacet = new TermsFacetMap("cat")
.withDomain(new DomainMap().withQuery("popularity:[8 TO 10]"))
.setLimit(3);
final JsonQueryRequest request = new JsonQueryRequest()
.setQuery("apple")
.withFacet("popular_categories", popularCategoriesFacet);
QueryResponse queryResponse = request.process(solrClient, COLLECTION_NAME);
//end::solrj-json-facet-query-domain[]
assertEquals(0, queryResponse.getStatus());
assertEquals(1, queryResponse.getResults().getNumFound());
assertEquals(1, queryResponse.getResults().size());
final NestableJsonFacet topLevelFacetingData = queryResponse.getJsonFacetingResponse();
assertHasFacetWithBucketValues(topLevelFacetingData, "popular_categories",
new FacetBucket("electronics", 1),
new FacetBucket("music", 1),
new FacetBucket("search", 1));
}
private class FacetBucket {
private final Object val;
private final int count;
@ -192,4 +738,9 @@ public class JsonRequestApiTest extends SolrCloudTestCase {
assertEquals(expectedBucket.getCount(), actualBucket.getCount());
}
}
private void assertResponseFoundNumDocs(QueryResponse response, int expectedNumDocs) {
assertEquals(0, response.getStatus());
assertEquals(expectedNumDocs, response.getResults().size());
}
}