Revision created by MOE tool push_codebase.
[gae.git] / java / src / main / com / google / appengine / api / search / Query.java
blob88bebc840b844ea8d267cad819683a6062d6fe67
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;
9 import java.util.List;
11 /**
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.
15 * <p>
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.
24 * <p>
25 * <pre>
26 * QueryOptions options = QueryOptions.newBuilder()
27 * .setLimit(20)
28 * .setFieldsToSnippet("subject", "body")
29 * .setScorer(CustomScorer.newBuilder()
30 * .addSortExpression(SortExpression.newBuilder()
31 * .setExpression("author")
32 * .setDirection(SortDirection.DESCENDING)
33 * .setDefaultValue("")))
34 * .setCursor(responseCursor)
35 * .build();
36 * Query query = Query.newBuilder()
37 * .setOptions(options)
38 * .build("good story");
39 * </pre>
41 * The following query will return facet information with the query result:
42 * <pre>
43 * Query query = Query.newBuilder()
44 * .setOptions(options)
45 * .setEnableFacetDiscovery(true)
46 * .build("tablet");
47 * </pre>
49 * To customize returned facet or refine the result using a previously returned
50 * {@link FacetResultValue#getRefinementToken}:
51 * <pre>
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")
60 * .build())
61 * .addRefinementToken(refinementToken1)
62 * .addRefinementToken(refinementToken2)
63 * .build("tablet");
64 * </pre>
66 public class Query {
68 /**
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<>();
76 protected Builder() {
79 /**
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());
93 /**
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;
101 return this;
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;
123 return this;
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;
144 return this;
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);
156 return this;
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:
165 * <pre>
166 * ((wine_type is red) OR (wine_type is white)) AND (year in Range(2000,2010))
167 * </pre>
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:
183 * <pre>
184 * ((wine_type is red) OR (wine_type is white)) AND (year in Range.closedOpen(2000,2010))
185 * </pre>
187 * @param refinement a {@link FacetRefinement} object.
188 * @return this builder
190 public Builder addFacetRefinement(FacetRefinement refinement) {
191 this.refinements.add(refinement);
192 return this;
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());
214 return this;
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);
226 return this;
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
249 * Builder
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);
273 checkValid();
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").
281 * @return the query
283 public String getQueryString() {
284 return query;
288 * Returns the {@link QueryOptions} for controlling the what is returned
289 * in the result set matching the query
291 public QueryOptions getOptions() {
292 return options;
296 * Returns the {@link FacetOptions} for controlling faceted search or null if unset.
298 public FacetOptions getFacetOptions() {
299 return facetOptions;
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() {
313 return returnFacets;
317 * Returns an unmodifiable list of facet refinements for the search.
319 public ImmutableList<FacetRefinement> getRefinements() {
320 return refinements;
324 * Creates and returns a {@link Query} builder. Set the query
325 * parameters and use the {@link Builder#build()} method to create a concrete
326 * instance of Query.
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
346 * query string.
348 * @return this checked Query
349 * @throws NullPointerException if query is null
351 private Query checkValid() {
352 Preconditions.checkNotNull(query, "query cannot be null");
353 return this;
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
363 * unknown
365 SearchParams.Builder copyToProtocolBuffer() {
366 SearchParams.Builder builder = SearchParams.newBuilder().setQuery(query);
367 if (options == null) {
368 QueryOptions.newBuilder().build().copyToProtocolBuffer(builder, query);
369 } else {
370 options.copyToProtocolBuffer(builder, query);
372 if (facetOptions == null) {
373 FacetOptions.newBuilder().build().copyToProtocolBuffer(builder, enableFacetDiscovery);
374 } else {
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());
383 return builder;
386 @Override
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)
395 .finish();