1 package com
.google
.appengine
.api
.search
;
3 import com
.google
.appengine
.api
.search
.SearchServicePb
.SearchParams
;
4 import com
.google
.appengine
.api
.search
.checkers
.Preconditions
;
5 import com
.google
.appengine
.api
.search
.checkers
.QueryChecker
;
6 import com
.google
.common
.collect
.ImmutableList
;
8 import java
.util
.ArrayList
;
12 * A query to search an index for documents which match,
13 * restricting the document fields returned to those given, and
14 * scoring and sorting the results, whilst supporting pagination.
16 * For example, the following query will search for documents where
17 * the tokens 'good' and 'story' occur in some fields,
18 * returns up to 20 results including the fields 'author' and 'date-sent'
19 * as well as snippeted fields 'subject' and 'body'. The results
20 * are sorted by 'author' in descending order, getting the next 20 results
21 * from the responseCursor in the previously returned results, giving
22 * back a single cursor in the {@link Results} to get the next
23 * batch of results after this.
26 * QueryOptions options = QueryOptions.newBuilder()
28 * .setFieldsToSnippet("subject", "body")
29 * .setScorer(CustomScorer.newBuilder()
30 * .addSortExpression(SortExpression.newBuilder()
31 * .setExpression("author")
32 * .setDirection(SortDirection.DESCENDING)
33 * .setDefaultValue("")))
34 * .setCursor(responseCursor)
36 * Query query = Query.newBuilder()
37 * .setOptions(options)
38 * .build("good story");
41 * The following query will return facet information with the query result:
43 * Query query = Query.newBuilder()
44 * .setOptions(options)
45 * .setEnableFacetDiscovery(true)
49 * To customize returned facet or refine the result using a previously returned
50 * {@link FacetResultValue#getRefinementToken}:
52 * Query query = Query.newBuilder()
53 * .setOptions(options)
54 * .setEnableFacetDiscovery(true)
55 * .setFacetOptions(FacetOptions.newBuilder().setDiscoveryLimit(5).build())
56 * .addReturnFacet("shipping")
57 * .addReturnFacet(FacetRequest.newBuilder().setName("department")
58 * .addValueConstraint("Computers")
59 * .addValueConstraint("Electronics")
61 * .addRefinementToken(refinementToken1)
62 * .addRefinementToken(refinementToken2)
69 * A builder which constructs Query objects.
71 public static class Builder
{
72 private String queryString
;
73 private boolean enableFacetDiscovery
;
74 private QueryOptions options
; private FacetOptions facetOptions
; private List
<FacetRequest
> returnFacets
= new ArrayList
<>(); private List
<FacetRefinement
> refinements
= new ArrayList
<>();
80 * Constructs a {@link Query} builder with the given query.
82 * @param query the query to populate the builder
84 private Builder(Query query
) {
85 this.queryString
= query
.getQueryString();
86 this.enableFacetDiscovery
= query
.getEnableFacetDiscovery();
87 this.options
= query
.getOptions();
88 this.facetOptions
= query
.getFacetOptions();
89 this.returnFacets
= new ArrayList
<>(query
.getReturnFacets());
90 this.refinements
= new ArrayList
<>(query
.getRefinements());
94 * Sets the query options.
96 * @param options the {@link QueryOptions} to apply to the search results
97 * @return this builder
99 public Builder
setOptions(QueryOptions options
) {
100 this.options
= options
;
105 * Sets the query options from a builder.
107 * @param optionsBuilder the {@link QueryOptions.Builder} build a
108 * {@link QueryOptions} to apply to the search results
109 * @return this builder
111 public Builder
setOptions(QueryOptions
.Builder optionsBuilder
) {
112 return setOptions(optionsBuilder
.build());
116 * Sets the facet options.
118 * @param options the {@link FacetOptions} to apply to the facet results
119 * @return this builder
121 public Builder
setFacetOptions(FacetOptions options
) {
122 this.facetOptions
= options
;
127 * Sets the facet options from a builder.
129 * @param builder the {@link FacetOptions.Builder} build a {@link FacetOptions}
130 * to apply to the facet results
131 * @return this builder
133 public Builder
setFacetOptions(FacetOptions
.Builder builder
) {
134 return this.setFacetOptions(builder
.build());
138 * Sets enable facet discovery flag.
140 * @return this builder
142 public Builder
setEnableFacetDiscovery(boolean value
) {
143 this.enableFacetDiscovery
= value
;
148 * Requests a facet to be returned with search results. The facet will be included in
149 * the result regardless of the number of values it has.
151 * @param facet the {@link FacetRequest} to be added to return facets.
152 * @return this builder
154 public Builder
addReturnFacet(FacetRequest facet
) {
155 this.returnFacets
.add(facet
);
160 * Adds a facet refinement token. The token is returned by each FacetResultValue. There will be
161 * disjunction between tokens for the same facet and conjunction between tokens for different
162 * facets. For example if the refinement tokens are (name=wine_type,value=red),
163 * (name=wine_type,value=white) and (name=year, Range(2000,2010)),
164 * the result will be refined according to:
166 * ((wine_type is red) OR (wine_type is white)) AND (year in Range(2000,2010))
169 * @param token the token returned by {@link FacetResultValue#getRefinementToken} or
170 * {@link FacetRefinement#toTokenString}.
171 * @return this builder
172 * @throws IllegalArgumentException if token is not valid.
174 public Builder
addFacetRefinementFromToken(String token
) {
175 return addFacetRefinement(FacetRefinement
.fromTokenString(token
));
179 * Adds a facet refinement. There will be disjunction between refinements for the same facet
180 * and conjunction between refinements for different facets. For example if the refinements are
181 * (name=wine_type,value=red), (name=wine_type,value=white) and (name=year, Range(2000,2010)),
182 * the result will be refined according to:
184 * ((wine_type is red) OR (wine_type is white)) AND (year in Range.closedOpen(2000,2010))
187 * @param refinement a {@link FacetRefinement} object.
188 * @return this builder
190 public Builder
addFacetRefinement(FacetRefinement refinement
) {
191 this.refinements
.add(refinement
);
196 * Adds a facet request from a builder.
198 * @param builder the {@link FacetRequest.Builder} build a {@link FacetRequest}
199 * to be added to return facets.
200 * @return this builder
202 public Builder
addReturnFacet(FacetRequest
.Builder builder
) {
203 return addReturnFacet(builder
.build());
207 * Adds a facet request by its name only.
209 * @param facetName the name of the facet to be added to return facets.
210 * @return this builder
212 public Builder
addReturnFacet(String facetName
) {
213 this.returnFacets
.add(FacetRequest
.newBuilder().setName(facetName
).build());
218 * Sets the query string used to construct the query.
220 * @param query a query string used to construct the query
221 * @return this Builder
222 * @throws SearchQueryException if the query string does not parse
224 protected Builder
setQueryString(String query
) {
225 this.queryString
= QueryChecker
.checkQuery(query
);
230 * Build a {@link Query} from the query string and the parameters set on
231 * the {@link Builder}. A query string can be as simple as a single term
232 * ("foo"), or as complex as a boolean expression, including field names
233 * ("title:hello OR body:important -october").
235 * @param queryString the query string to parse and apply to an index
236 * @return the Query built from the parameters entered on this
237 * Builder including the queryString
238 * @throws SearchQueryException if the query string is invalid
240 public Query
build(String queryString
) {
241 setQueryString(queryString
);
242 return new Query(this);
246 * Construct the message.
248 * @return the Query built from the parameters entered on this
250 * @throws IllegalArgumentException if the query string is invalid
252 public Query
build() {
253 return new Query(this);
257 private final String query
;
258 private final boolean enableFacetDiscovery
;
259 private final QueryOptions options
; private final FacetOptions facetOptions
; private final ImmutableList
<FacetRequest
> returnFacets
; private final ImmutableList
<FacetRefinement
> refinements
;
262 * Creates a query from the builder.
264 * @param builder the query builder to populate with
266 protected Query(Builder builder
) {
267 query
= builder
.queryString
;
268 enableFacetDiscovery
= builder
.enableFacetDiscovery
;
269 options
= builder
.options
;
270 facetOptions
= builder
.facetOptions
;
271 returnFacets
= ImmutableList
.copyOf(builder
.returnFacets
);
272 refinements
= ImmutableList
.copyOf(builder
.refinements
);
277 * The query can be as simple as a single term ("foo"), or as complex
278 * as a boolean expression, including field names ("title:hello OR
279 * body:important -october").
283 public String
getQueryString() {
288 * Returns the {@link QueryOptions} for controlling the what is returned
289 * in the result set matching the query
291 public QueryOptions
getOptions() {
296 * Returns the {@link FacetOptions} for controlling faceted search or null if unset.
298 public FacetOptions
getFacetOptions() {
303 * Returns true if facet discovery is enabled.
305 public boolean getEnableFacetDiscovery() {
306 return enableFacetDiscovery
;
310 * Returns an unmodifiable list of requests for facets to be returned with the search results.
312 public ImmutableList
<FacetRequest
> getReturnFacets() {
317 * Returns an unmodifiable list of facet refinements for the search.
319 public ImmutableList
<FacetRefinement
> getRefinements() {
324 * Creates and returns a {@link Query} builder. Set the query
325 * parameters and use the {@link Builder#build()} method to create a concrete
328 * @return a {@link Builder} which can construct a query
330 public static Builder
newBuilder() {
331 return new Builder();
335 * Creates a builder from the given query.
337 * @param query the query for the builder to use to build another query
338 * @return a new builder with values based on the given request
340 public static Builder
newBuilder(Query query
) {
341 return new Builder(query
);
345 * Checks the query is valid, specifically, has a non-null
348 * @return this checked Query
349 * @throws NullPointerException if query is null
351 private Query
checkValid() {
352 Preconditions
.checkNotNull(query
, "query cannot be null");
357 * Copies the contents of this {@link Query} object into a
358 * {@link SearchParams} protocol buffer builder.
360 * @return a search params protocol buffer builder initialized with
361 * the values from this query
362 * @throws IllegalArgumentException if the cursor type is
365 SearchParams
.Builder
copyToProtocolBuffer() {
366 SearchParams
.Builder builder
= SearchParams
.newBuilder().setQuery(query
);
367 if (options
== null) {
368 QueryOptions
.newBuilder().build().copyToProtocolBuffer(builder
, query
);
370 options
.copyToProtocolBuffer(builder
, query
);
372 if (facetOptions
== null) {
373 FacetOptions
.newBuilder().build().copyToProtocolBuffer(builder
, enableFacetDiscovery
);
375 facetOptions
.copyToProtocolBuffer(builder
, enableFacetDiscovery
);
377 for (FacetRequest facet
: returnFacets
) {
378 builder
.addIncludeFacet(facet
.copyToProtocolBuffer());
380 for (FacetRefinement ref
: refinements
) {
381 builder
.addFacetRefinement(ref
.toProtocolBuffer());
387 public String
toString() {
388 return new Util
.ToStringHelper("Query")
389 .addField("queryString", query
)
390 .addField("options", options
)
391 .addField("enableFacetDiscovery", enableFacetDiscovery ? Boolean
.TRUE
: null)
392 .addField("facetOptions", facetOptions
)
393 .addIterableField("returnFacets", returnFacets
)
394 .addIterableField("refinements", refinements
)