1 /*
2  * Copyright 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 // @exportToFramework:skipFile()
17 package androidx.appsearch.app;
18 
19 import android.annotation.SuppressLint;
20 
21 import androidx.annotation.RequiresFeature;
22 import androidx.appsearch.flags.FlaggedApi;
23 import androidx.appsearch.flags.Flags;
24 
25 import com.google.common.util.concurrent.ListenableFuture;
26 
27 import org.jspecify.annotations.NonNull;
28 
29 import java.io.Closeable;
30 import java.util.List;
31 import java.util.Set;
32 
33 /**
34  * Provides a connection to a single AppSearch database.
35  *
36  * <p>An {@link AppSearchSession} instance provides access to database operations such as setting
37  * a schema, adding documents, and searching.
38  *
39  * <p>Instances of this interface are usually obtained from a storage implementation, e.g.
40  * {@code LocalStorage.createSearchSessionAsync()} or
41  * {@code PlatformStorage.createSearchSessionAsync()}.
42  *
43  * <p>All implementations of this interface must be thread safe.
44  *
45  * @see GlobalSearchSession
46  */
47 public interface AppSearchSession extends Closeable {
48 
49     /**
50      * Sets the schema that represents the organizational structure of data within the AppSearch
51      * database.
52      *
53      * <p>Upon creating an {@link AppSearchSession}, {@link #setSchemaAsync} should be called. If
54      * the schema needs to be updated, or it has not been previously set, then the provided schema
55      * will be saved and persisted to disk. Otherwise, {@link #setSchemaAsync} is handled
56      * efficiently as a no-op call.
57      *
58      * @param  request the schema to set or update the AppSearch database to.
59      * @return a {@link ListenableFuture} which resolves to a {@link SetSchemaResponse} object.
60      */
setSchemaAsync(@onNull SetSchemaRequest request)61     @NonNull ListenableFuture<SetSchemaResponse> setSchemaAsync(@NonNull SetSchemaRequest request);
62 
63     /**
64      * Retrieves the schema most recently successfully provided to {@link #setSchemaAsync}.
65      *
66      * @return The pending {@link GetSchemaResponse} of performing this operation.
67      */
68     // This call hits disk; async API prevents us from treating these calls as properties.
69     @SuppressLint("KotlinPropertyAccess")
getSchemaAsync()70     @NonNull ListenableFuture<GetSchemaResponse> getSchemaAsync();
71 
72     /**
73      * Retrieves the set of all namespaces in the current database with at least one document.
74      *
75      * @return The pending result of performing this operation. */
getNamespacesAsync()76     @NonNull ListenableFuture<Set<String>> getNamespacesAsync();
77 
78     /**
79      * Indexes documents into the {@link AppSearchSession} database.
80      *
81      * <p>Each {@link GenericDocument} object must have a {@code schemaType} field set to an
82      * {@link AppSearchSchema} type that has been previously registered by calling the
83      * {@link #setSchemaAsync} method.
84      *
85      * @param request containing documents to be indexed.
86      * @return a {@link ListenableFuture} which resolves to an {@link AppSearchBatchResult}.
87      * The keys of the returned {@link AppSearchBatchResult} are the IDs of the input documents.
88      * The values are either {@code null} if the corresponding document was successfully indexed,
89      * or a failed {@link AppSearchResult} otherwise.
90      */
putAsync( @onNull PutDocumentsRequest request)91     @NonNull ListenableFuture<AppSearchBatchResult<String, Void>> putAsync(
92             @NonNull PutDocumentsRequest request);
93 
94     /**
95      * Gets {@link GenericDocument} objects by document IDs in a namespace from the
96      * {@link AppSearchSession} database.
97      *
98      * @param request a request containing a namespace and IDs to get documents for.
99      * @return A {@link ListenableFuture} which resolves to an {@link AppSearchBatchResult}.
100      * The keys of the {@link AppSearchBatchResult} represent the input document IDs from the
101      * {@link GetByDocumentIdRequest} object. The values are either the corresponding
102      * {@link GenericDocument} object for the ID on success, or an {@link AppSearchResult}
103      * object on failure. For example, if an ID is not found, the value for that ID will be set
104      * to an {@link AppSearchResult} object with result code:
105      * {@link AppSearchResult#RESULT_NOT_FOUND}.
106      */
getByDocumentIdAsync( @onNull GetByDocumentIdRequest request)107     @NonNull ListenableFuture<AppSearchBatchResult<String, GenericDocument>> getByDocumentIdAsync(
108             @NonNull GetByDocumentIdRequest request);
109 
110     /**
111      * Opens a batch of AppSearch Blobs for writing.
112      *
113      * <p>A "blob" is a large binary object. It is used to store a significant amount of data that
114      * is not searchable, such as images, videos, audio files, or other binary data. Unlike other
115      * fields in AppSearch, blobs are stored as blob files on disk rather than in memory, and use
116      * {@link android.os.ParcelFileDescriptor} to read and write. This allows for efficient handling
117      * of large, non-searchable content.
118      *
119      * <p> Once done writing, call {@link #commitBlobAsync} to commit blob files.
120      *
121      * <p> This call will create a empty blob file for each given {@link AppSearchBlobHandle}, and
122      * a {@link android.os.ParcelFileDescriptor} of that blob file will be returned in the
123      * {@link OpenBlobForWriteResponse}.
124      *
125      * <p> If the blob file is already stored in AppSearch and committed. A failed
126      * {@link AppSearchResult} with error code {@link AppSearchResult#RESULT_ALREADY_EXISTS} will be
127      * associated with the {@link AppSearchBlobHandle}.
128      *
129      * <p> If the blob file is already stored in AppSearch but not committed. A
130      * {@link android.os.ParcelFileDescriptor} of that blob file will be returned for continue
131      * writing.
132      *
133      * <p> For given duplicate {@link AppSearchBlobHandle}, the same
134      * {@link android.os.ParcelFileDescriptor} pointing to the same blob file will be returned.
135      *
136      * <p> Pending blob files won't be lost or auto-commit if {@link AppSearchSession} closed.
137      * Pending blob files will be stored in disk rather than memory. You can re-open
138      * {@link AppSearchSession} and re-write the pending blob files.
139      *
140      * <p> A committed blob file will be considered as an orphan if no {@link GenericDocument}
141      * references it. Uncommitted pending blob files and orphan blobs files will be cleaned up if
142      * they has been created for an extended period (default is 1 week).
143      *
144      * <p> Both pending blob files and committed blob files can be manually removed via
145      * {@link #removeBlobAsync}.
146      *
147      * <p class="caution">
148      * The returned {@link OpenBlobForWriteResponse} must be closed after use to avoid
149      * resource leaks. Failing to close it will result in system file descriptor exhaustion.
150      * </p>
151      *
152      * @param handles The {@link AppSearchBlobHandle}s that identifies the blobs.
153      * @return a response containing the writeable file descriptors.
154      *
155      * @see GenericDocument.Builder#setPropertyBlobHandle
156      */
157     @RequiresFeature(
158             enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
159             name = Features.BLOB_STORAGE)
160     @FlaggedApi(Flags.FLAG_ENABLE_BLOB_STORE)
161     @ExperimentalAppSearchApi
openBlobForWriteAsync( @onNull Set<AppSearchBlobHandle> handles)162     default @NonNull ListenableFuture<OpenBlobForWriteResponse> openBlobForWriteAsync(
163             @NonNull Set<AppSearchBlobHandle> handles) {
164         throw new UnsupportedOperationException(Features.BLOB_STORAGE
165                 + " is not available on this AppSearch implementation.");
166     }
167 
168     /**
169      * Removes the blob data from AppSearch.
170      *
171      * <p> After this call, the blob data is removed immediately and cannot be recovered. It will
172      * not accessible via {@link #openBlobForReadAsync}. {@link #openBlobForWriteAsync} could reopen
173      * and rewrite it.
174      *
175      * <p> This API can be used to remove pending blob data and committed blob data.
176      *
177      * <p class="caution">
178      * Removing a committed blob data that is still referenced by documents will leave those
179      * documents with no readable blob content. It is highly recommended to let AppSearch control
180      * the blob data's life cycle. AppSearch automatically recycles orphaned and pending blob data.
181      * The default time to recycle pending and orphan blob file is 1 week. A blob file will be
182      * considered as an orphan if no {@link GenericDocument} references it. If you want to remove a
183      * committed blob data, you should remove the reference documents first.
184      * </p>
185      *
186      * @param handles The {@link AppSearchBlobHandle}s that identifies the blob data.
187      * @return a response containing the remove results.
188      */
189     @RequiresFeature(
190             enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
191             name = Features.BLOB_STORAGE)
192     @FlaggedApi(Flags.FLAG_ENABLE_BLOB_STORE)
193     @ExperimentalAppSearchApi
removeBlobAsync( @onNull Set<AppSearchBlobHandle> handles)194     default @NonNull ListenableFuture<RemoveBlobResponse> removeBlobAsync(
195             @NonNull Set<AppSearchBlobHandle> handles) {
196         throw new UnsupportedOperationException(Features.BLOB_STORAGE
197                 + " is not available on this AppSearch implementation.");
198     }
199 
200     /**
201      * Commits the blobs to make it retrievable and immutable.
202      *
203      * <p>After this call, the blob is readable via {@link #openBlobForReadAsync}. Any change to
204      * the content or rewrite via {@link #openBlobForWriteAsync} of this blob won't be allowed.
205      *
206      * <p> If the blob is already stored in AppSearch and committed. A failed
207      * {@link AppSearchResult} with error code {@link AppSearchResult#RESULT_ALREADY_EXISTS} will be
208      * associated with the {@link AppSearchBlobHandle}.
209      *
210      * <p>If the blob content doesn't match the digest in {@link AppSearchBlobHandle}, a failed
211      * {@link AppSearchResult} with error code {@link AppSearchResult#RESULT_INVALID_ARGUMENT} will
212      * be associated with the {@link AppSearchBlobHandle}. The pending Blob file will be removed
213      * from AppSearch.
214      *
215      * <p> Pending blobs won't be lost or auto-commit if {@link AppSearchSession} closed.
216      * Pending blobs will store in disk rather than memory. You can re-open {@link AppSearchSession}
217      * and re-write the pending blobs.
218      *
219      * <p> The default time to recycle pending and orphan blobs is 1 week. A blob will be considered
220      * as an orphan if no {@link GenericDocument} references it.
221      *
222      * <p> Both pending blob files and committed blob files can be manually removed via
223      * {@link #removeBlobAsync}.
224      *
225      * @param handles The {@link AppSearchBlobHandle}s that identifies the blobs.
226      * @return a response containing the commit results.
227      *
228      * @see GenericDocument.Builder#setPropertyBlobHandle
229      */
230     @RequiresFeature(
231             enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
232             name = Features.BLOB_STORAGE)
233     @FlaggedApi(Flags.FLAG_ENABLE_BLOB_STORE)
234     @ExperimentalAppSearchApi
commitBlobAsync( @onNull Set<AppSearchBlobHandle> handles)235     default @NonNull ListenableFuture<CommitBlobResponse> commitBlobAsync(
236             @NonNull Set<AppSearchBlobHandle> handles) {
237         throw new UnsupportedOperationException(Features.BLOB_STORAGE
238                 + " is not available on this AppSearch implementation.");
239     }
240 
241     /**
242      * Opens a batch of AppSearch Blobs for reading.
243      *
244      * <p> Only blobs committed via {@link #commitBlobAsync} are available for reading.
245      *
246      * <p class="caution">
247      * The returned {@link OpenBlobForReadResponse} must be closed after use to avoid
248      * resource leaks. Failing to close it will result in system file descriptor exhaustion.
249      * </p>
250      *
251      * @param handles The {@link AppSearchBlobHandle}s that identifies the blobs.
252      * @return a response containing the readable file descriptors.
253      *
254      * @see GenericDocument.Builder#setPropertyBlobHandle
255      */
256     @RequiresFeature(
257             enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
258             name = Features.BLOB_STORAGE)
259     @FlaggedApi(Flags.FLAG_ENABLE_BLOB_STORE)
260     @ExperimentalAppSearchApi
openBlobForReadAsync( @onNull Set<AppSearchBlobHandle> handles)261     default @NonNull ListenableFuture<OpenBlobForReadResponse> openBlobForReadAsync(
262             @NonNull Set<AppSearchBlobHandle> handles) {
263         throw new UnsupportedOperationException(Features.BLOB_STORAGE
264                 + " is not available on this AppSearch implementation.");
265     }
266 
267     /**
268      * Sets the visibility configuration for all blob namespaces within an appsearch database.
269      *
270      * <p> Blobs under the same namespace will share same visibility settings.
271      *
272      * <p> The default setting is blobs will be only visible to the owner package and System. To
273      * configure other kinds of sharing, set {@link SchemaVisibilityConfig} via
274      * {@link SetBlobVisibilityRequest}.
275      *
276      * @param request The request holds visibility settings for all blob namespaces
277      * @return The pending result of performing this operation which resolves to {@code null} on
278      *     success.
279      */
280     @RequiresFeature(
281             enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
282             name = Features.BLOB_STORAGE)
283     @FlaggedApi(Flags.FLAG_ENABLE_BLOB_STORE)
284     @ExperimentalAppSearchApi
setBlobVisibilityAsync( @onNull SetBlobVisibilityRequest request)285     default @NonNull ListenableFuture<Void> setBlobVisibilityAsync(
286             @NonNull SetBlobVisibilityRequest request) {
287         throw new UnsupportedOperationException(Features.BLOB_STORAGE
288                 + " is not available on this AppSearch implementation.");
289     }
290 
291     /**
292      * Retrieves documents from the open {@link AppSearchSession} that match a given query string
293      * and type of search provided.
294      *
295      * <p>Query strings can be empty, contain one term with no operators, or contain multiple
296      * terms and operators.
297      *
298      * <p>For query strings that are empty, all documents that match the {@link SearchSpec} will be
299      * returned.
300      *
301      * <p>For query strings with a single term and no operators, documents that match the
302      * provided query string and {@link SearchSpec} will be returned.
303      *
304      * <p>The following operators are supported:
305      *
306      * <ul>
307      *     <li>AND (implicit)
308      *     <p>AND is an operator that matches documents that contain <i>all</i>
309      *     provided terms.
310      *     <p><b>NOTE:</b> A space between terms is treated as an "AND" operator. Explicitly
311      *     including "AND" in a query string will treat "AND" as a term, returning documents that
312      *     also contain "AND".
313      *     <p>Example: "apple AND banana" matches documents that contain the
314      *     terms "apple", "and", "banana".
315      *     <p>Example: "apple banana" matches documents that contain both "apple" and
316      *     "banana".
317      *     <p>Example: "apple banana cherry" matches documents that contain "apple", "banana", and
318      *     "cherry".
319      *
320      *     <li>OR
321      *     <p>OR is an operator that matches documents that contain <i>any</i> provided term.
322      *     <p>Example: "apple OR banana" matches documents that contain either "apple" or "banana".
323      *     <p>Example: "apple OR banana OR cherry" matches documents that contain any of
324      *     "apple", "banana", or "cherry".
325      *
326      *     <li>Exclusion (-)
327      *     <p>Exclusion (-) is an operator that matches documents that <i>do not</i> contain the
328      *     provided term.
329      *     <p>Example: "-apple" matches documents that do not contain "apple".
330      *
331      *     <li>Grouped Terms
332      *     <p>For queries that require multiple operators and terms, terms can be grouped into
333      *     subqueries. Subqueries are contained within an open "(" and close ")" parenthesis.
334      *     <p>Example: "(donut OR bagel) (coffee OR tea)" matches documents that contain
335      *     either "donut" or "bagel" and either "coffee" or "tea".
336      *
337      *     <li>Property Restricts
338      *     <p>For queries that require a term to match a specific {@link AppSearchSchema}
339      *     property of a document, a ":" must be included between the property name and the term.
340      *     <p>Example: "subject:important" matches documents that contain the term "important" in
341      *     the "subject" property.
342      * </ul>
343      *
344      * <p>The above description covers the query operators that are supported on all versions of
345      * AppSearch. Additional operators and their required features are described below.
346      *
347      * <p>{@link Features#LIST_FILTER_QUERY_LANGUAGE}: This feature covers the expansion of the
348      * query language to conform to the definition of the list filters language (https://aip
349      * .dev/160). This includes:
350      * <ul>
351      *     <li>addition of explicit 'AND' and 'NOT' operators</li>
352      *     <li>property restricts are allowed with groupings (ex. "prop:(a OR b)")</li>
353      *     <li>addition of custom functions to control matching</li>
354      * </ul>
355      *
356      * <p>The newly added custom functions covered by this feature are:
357      * <ul>
358      *     <li>createList(String...)</li>
359      *     <li>search(String, {@code List<String>})</li>
360      *     <li>propertyDefined(String)</li>
361      * </ul>
362      *
363      * <p>createList takes a variable number of strings and returns a list of strings.
364      * It is for use with search.
365      *
366      * <p>search takes a query string that will be parsed according to the supported
367      * query language and an optional list of strings that specify the properties to be
368      * restricted to. This exists as a convenience for multiple property restricts. So,
369      * for example, the query `(subject:foo OR body:foo) (subject:bar OR body:bar)`
370      * could be rewritten as `search("foo bar", createList("subject", "body"))`.
371      *
372      * <p>propertyDefined takes a string specifying the property of interest and matches all
373      * documents of any type that defines the specified property
374      * (ex. `propertyDefined("sender.name")`). Note that propertyDefined will match so long as
375      * the document's type defines the specified property. Unlike the "hasProperty" function
376      * below, this function does NOT require that the document actually hold any values for this
377      * property.
378      *
379      * <p>{@link Features#NUMERIC_SEARCH}: This feature covers numeric search expressions. In the
380      * query language, the values of properties that have
381      * {@link AppSearchSchema.LongPropertyConfig#INDEXING_TYPE_RANGE} set can be matched with a
382      * numeric search expression (the property, a supported comparator and an integer value).
383      * Supported comparators are <, <=, ==, >= and >.
384      *
385      * <p>Ex. `price < 10` will match all documents that has a numeric value in its price
386      * property that is less than 10.
387      *
388      * <p>{@link Features#VERBATIM_SEARCH}: This feature covers the verbatim string operator
389      * (quotation marks).
390      *
391      * <p>Ex. `"foo/bar" OR baz` will ensure that 'foo/bar' is treated as a single 'verbatim' token.
392      *
393      * <p>{@link Features#LIST_FILTER_HAS_PROPERTY_FUNCTION}: This feature covers the
394      * "hasProperty" function in query expressions, which takes a string specifying the property
395      * of interest and matches all documents that hold values for this property. Not to be
396      * confused with the "propertyDefined" function, which checks whether a document's schema
397      * has defined the property, instead of whether a document itself has this property.
398      *
399      * <p>Ex. `foo hasProperty("sender.name")` will return all documents that have the term "foo"
400      * AND have values in the property "sender.name". Consider two documents, documentA and
401      * documentB, of the same schema with an optional property "sender.name". If documentA sets
402      * "foo" in this property but documentB does not, then `hasProperty("sender.name")` will only
403      * match documentA. However, `propertyDefined("sender.name")` will match both documentA and
404      * documentB, regardless of whether a value is actually set.
405      *
406      * <p>{@link Features#LIST_FILTER_MATCH_SCORE_EXPRESSION_FUNCTION}: This feature covers the
407      * "matchScoreExpression" function in query expressions.
408      *
409      * <p>Usage: matchScoreExpression({score_expression}, {low}, {high})
410      * <ul>
411      *     <li>matchScoreExpression matches all documents with scores falling within the
412      *     specified range. These scores are calculated using the provided score expression,
413      *     which adheres to the syntax defined in
414      *     {@link SearchSpec.Builder#setRankingStrategy(String)}.</li>
415      *     <li>"score_expression" is a string value that specifies the score expression.</li>
416      *     <li>"low" and "high" are floating point numbers that specify the score range. The
417      *     "high" parameter is optional; if not provided, it defaults to positive infinity.</li>
418      * </ul>
419      *
420      * <p>Ex. `matchScoreExpression("this.documentScore()", 3, 4)` will return all documents that
421      * have document scores from 3 to 4.
422      *
423      * <p>{@link Features#SCHEMA_EMBEDDING_PROPERTY_CONFIG}: This feature covers the
424      * "semanticSearch" and "getEmbeddingParameter" functions in query expressions, which are
425      * used for semantic search.
426      *
427      * <p>Usage: semanticSearch(getEmbeddingParameter({embedding_index}), {low}, {high}, {metric})
428      * <ul>
429      *     <li>semanticSearch matches all documents that have at least one embedding vector with
430      *     a matching model signature (see {@link EmbeddingVector#getModelSignature()}) and a
431      *     similarity score within the range specified based on the provided metric.</li>
432      *     <li>getEmbeddingParameter({embedding_index}) retrieves the embedding search passed in
433      *     {@link SearchSpec.Builder#addEmbeddingParameters} based on the index specified, which
434      *     starts from 0.</li>
435      *     <li>"low" and "high" are floating point numbers that specify the similarity score
436      *     range. If omitted, they default to negative and positive infinity, respectively.</li>
437      *     <li>"metric" is a string value that specifies how embedding similarities should be
438      *     calculated. If omitted, it defaults to the metric specified in
439      *     {@link SearchSpec.Builder#setDefaultEmbeddingSearchMetricType(int)}. Possible
440      *     values:</li>
441      *     <ul>
442      *         <li>"COSINE"</li>
443      *         <li>"DOT_PRODUCT"</li>
444      *         <li>"EUCLIDEAN"</li>
445      *     </ul>
446      * </ul>
447      *
448      * <p>Examples:
449      * <ul>
450      *     <li>Basic: semanticSearch(getEmbeddingParameter(0), 0.5, 1, "COSINE")</li>
451      *     <li>With a property restriction:
452      *     property1:semanticSearch(getEmbeddingParameter(0), 0.5, 1)</li>
453      *     <li>Hybrid: foo OR semanticSearch(getEmbeddingParameter(0), 0.5, 1)</li>
454      *     <li>Complex: (foo OR semanticSearch(getEmbeddingParameter(0), 0.5, 1)) AND bar</li>
455      * </ul>
456      *
457      * <p>{@link Features#SEARCH_SPEC_SEARCH_STRING_PARAMETERS}: This feature covers the
458      * "getSearchStringParameter" function in query expressions, which substitutes the string
459      * provided at the same index in {@link SearchSpec.Builder#addSearchStringParameters} into the
460      * query as plain text. This string is then segmented, normalized and stripped of
461      * punctuation-only segments. The remaining tokens are then AND'd together. This function is
462      * useful for callers who wish to provide user input, but want to ensure that that user input
463      * does not invoke any query operators.
464      *
465      * <p>Usage: getSearchStringParameter({search_parameter_strings_index})
466      *
467      * <p>Ex. `foo OR getSearchStringParameter(0)` with {@link SearchSpec#getSearchStringParameters}
468      * returning {"bar OR baz."}. The string "bar OR baz." will be segmented into "bar", "OR",
469      * "baz", ".". Punctuation is removed and the segments are normalized to "bar", "or", "baz".
470      * This query will be equivalent to `foo OR (bar AND or AND baz)`.
471      *
472      * <p>The availability of each of these features can be checked by calling
473      * {@link Features#isFeatureSupported} with the desired feature.
474      *
475      * <p>Additional search specifications, such as filtering by {@link AppSearchSchema} type or
476      * adding projection, can be set by calling the corresponding {@link SearchSpec.Builder} setter.
477      *
478      * <p>This method is lightweight. The heavy work will be done in
479      * {@link SearchResults#getNextPageAsync}.
480      *
481      * @param queryExpression query string to search.
482      * @param searchSpec      spec for setting document filters, adding projection, setting term
483      *                        match type, etc.
484      * @return a {@link SearchResults} object for retrieved matched documents.
485      */
486     // TODO(b/326656531): Refine the javadoc to provide guidance on the best practice of
487     //  embedding searches and how to select an appropriate metric.
search(@onNull String queryExpression, @NonNull SearchSpec searchSpec)488     @NonNull SearchResults search(@NonNull String queryExpression, @NonNull SearchSpec searchSpec);
489 
490     /**
491      * Retrieves suggested Strings that could be used as {@code queryExpression} in
492      * {@link #search(String, SearchSpec)} API.
493      *
494      * <p>The {@code suggestionQueryExpression} can contain one term with no operators, or contain
495      * multiple terms and operators. Operators will be considered as a normal term. Please see the
496      * operator examples below. The {@code suggestionQueryExpression} must end with a valid term,
497      * the suggestions are generated based on the last term. If the input
498      * {@code suggestionQueryExpression} doesn't have a valid token, AppSearch will return an
499      * empty result list. Please see the invalid examples below.
500      *
501      * <p>Example: if there are following documents with content stored in AppSearch.
502      * <ul>
503      *     <li>document1: "term1"
504      *     <li>document2: "term1 term2"
505      *     <li>document3: "term1 term2 term3"
506      *     <li>document4: "org"
507      * </ul>
508      *
509      * <p>Search suggestions with the single term {@code suggestionQueryExpression} "t", the
510      * suggested results are:
511      * <ul>
512      *     <li>"term1" - Use it to be queryExpression in {@link #search} could get 3
513      *     {@link SearchResult}s, which contains document 1, 2 and 3.
514      *     <li>"term2" - Use it to be queryExpression in {@link #search} could get 2
515      *     {@link SearchResult}s, which contains document 2 and 3.
516      *     <li>"term3" - Use it to be queryExpression in {@link #search} could get 1
517      *     {@link SearchResult}, which contains document 3.
518      * </ul>
519      *
520      * <p>Search suggestions with the multiple term {@code suggestionQueryExpression} "org t", the
521      * suggested result will be "org term1" - The last token is completed by the suggested
522      * String.
523      *
524      * <p>Operators in {@link #search} are supported.
525      * <p><b>NOTE:</b> Exclusion and Grouped Terms in the last term is not supported.
526      * <p>example: "apple -f": This Api will throw an
527      * {@link androidx.appsearch.exceptions.AppSearchException} with
528      * {@link AppSearchResult#RESULT_INVALID_ARGUMENT}.
529      * <p>example: "apple (f)": This Api will return an empty results.
530      *
531      * <p>Invalid example: All these input {@code suggestionQueryExpression} don't have a valid
532      * last token, AppSearch will return an empty result list.
533      * <ul>
534      *     <li>""      - Empty {@code suggestionQueryExpression}.
535      *     <li>"(f)"   - Ending in a closed brackets.
536      *     <li>"f:"    - Ending in an operator.
537      *     <li>"f    " - Ending in trailing space.
538      * </ul>
539      *
540      * @param suggestionQueryExpression the non empty query string to search suggestions
541      * @param searchSuggestionSpec      spec for setting document filters
542      * @return The pending result of performing this operation which resolves to a List of
543      *         {@link SearchSuggestionResult} on success. The returned suggestion Strings are
544      *         ordered by the number of {@link SearchResult} you could get by using that suggestion
545      *         in {@link #search}.
546      *
547      * @see #search(String, SearchSpec)
548      */
searchSuggestionAsync( @onNull String suggestionQueryExpression, @NonNull SearchSuggestionSpec searchSuggestionSpec)549     @NonNull ListenableFuture<List<SearchSuggestionResult>> searchSuggestionAsync(
550             @NonNull String suggestionQueryExpression,
551             @NonNull SearchSuggestionSpec searchSuggestionSpec);
552 
553     /**
554      * Reports usage of a particular document by namespace and ID.
555      *
556      * <p>A usage report represents an event in which a user interacted with or viewed a document.
557      *
558      * <p>For each call to {@link #reportUsageAsync}, AppSearch updates usage count and usage
559      * recency * metrics for that particular document. These metrics are used for ordering
560      * {@link #search} results by the {@link SearchSpec#RANKING_STRATEGY_USAGE_COUNT} and
561      * {@link SearchSpec#RANKING_STRATEGY_USAGE_LAST_USED_TIMESTAMP} ranking strategies.
562      *
563      * <p>Reporting usage of a document is optional.
564      *
565      * @param request The usage reporting request.
566      * @return The pending result of performing this operation which resolves to {@code null} on
567      *     success.
568      */
reportUsageAsync(@onNull ReportUsageRequest request)569     @NonNull ListenableFuture<Void> reportUsageAsync(@NonNull ReportUsageRequest request);
570 
571     /**
572      * Removes {@link GenericDocument} objects by document IDs in a namespace from the
573      * {@link AppSearchSession} database.
574      *
575      * <p>Removed documents will no longer be surfaced by {@link #search} or
576      * {@link #getByDocumentIdAsync}
577      * calls.
578      *
579      * <p>Once the database crosses the document count or byte usage threshold, removed documents
580      * will be deleted from disk.
581      *
582      * @param request {@link RemoveByDocumentIdRequest} with IDs in a namespace to remove from the
583      *                index.
584      * @return a {@link ListenableFuture} which resolves to an {@link AppSearchBatchResult}.
585      * The keys of the {@link AppSearchBatchResult} represent the input IDs from the
586      * {@link RemoveByDocumentIdRequest} object. The values are either {@code null} on success,
587      * or a failed {@link AppSearchResult} otherwise. IDs that are not found will return a failed
588      * {@link AppSearchResult} with a result code of {@link AppSearchResult#RESULT_NOT_FOUND}.
589      */
removeAsync( @onNull RemoveByDocumentIdRequest request)590     @NonNull ListenableFuture<AppSearchBatchResult<String, Void>> removeAsync(
591             @NonNull RemoveByDocumentIdRequest request);
592 
593     /**
594      * Removes {@link GenericDocument}s from the index by Query. Documents will be removed if they
595      * match the {@code queryExpression} in given namespaces and schemaTypes which is set via
596      * {@link SearchSpec.Builder#addFilterNamespaces} and
597      * {@link SearchSpec.Builder#addFilterSchemas}.
598      *
599      * <p> An empty {@code queryExpression} matches all documents.
600      *
601      * <p> An empty set of namespaces or schemaTypes matches all namespaces or schemaTypes in
602      * the current database.
603      *
604      * @param queryExpression Query String to search.
605      * @param searchSpec      Spec containing schemaTypes, namespaces and query expression
606      *                        indicates how document will be removed. All specific about how to
607      *                        scoring, ordering, snippeting and resulting will be ignored.
608      * @return The pending result of performing this operation.
609      * @throws IllegalArgumentException if the {@link SearchSpec} contains a {@link JoinSpec}.
610      * {@link JoinSpec} lets you join docs that are not owned by the caller, so the semantics of
611      * failures from this method would be complex.
612      */
removeAsync(@onNull String queryExpression, @NonNull SearchSpec searchSpec)613     @NonNull ListenableFuture<Void> removeAsync(@NonNull String queryExpression,
614             @NonNull SearchSpec searchSpec);
615 
616     /**
617      * Gets the storage info for this {@link AppSearchSession} database.
618      *
619      * <p>This may take time proportional to the number of documents and may be inefficient to
620      * call repeatedly.
621      *
622      * @return a {@link ListenableFuture} which resolves to a {@link StorageInfo} object.
623      */
getStorageInfoAsync()624     @NonNull ListenableFuture<StorageInfo> getStorageInfoAsync();
625 
626     /**
627      * Flush all schema and document updates, additions, and deletes to disk if possible.
628      *
629      * <p>The request is not guaranteed to be handled and may be ignored by some implementations of
630      * AppSearchSession.
631      *
632      * @return The pending result of performing this operation.
633      * {@link androidx.appsearch.exceptions.AppSearchException} with
634      * {@link AppSearchResult#RESULT_INTERNAL_ERROR} will be set to the future if we hit error when
635      * save to disk.
636      */
requestFlushAsync()637     @NonNull ListenableFuture<Void> requestFlushAsync();
638 
639     /**
640      * Returns the {@link Features} to check for the availability of certain features
641      * for this session.
642      */
getFeatures()643     @NonNull Features getFeatures();
644 
645     /**
646      * Closes the {@link AppSearchSession} to persist all schema and document updates, additions,
647      * and deletes to disk.
648      */
649     @Override
close()650     void close();
651 }
652