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 17 package android.app.appsearch; 18 19 import android.annotation.NonNull; 20 import android.annotation.SuppressLint; 21 22 import com.google.common.util.concurrent.ListenableFuture; 23 24 import java.io.Closeable; 25 import java.util.List; 26 import java.util.Set; 27 28 /** 29 * Provides a connection to a single AppSearch database. 30 * 31 * <p>An {@link AppSearchSessionShim} instance provides access to database operations such as 32 * setting a schema, adding documents, and searching. 33 * 34 * <p>Instances of this interface are usually obtained from a storage implementation, e.g. {@code 35 * AppSearchManager.createSearchSessionAsync()} or {@code 36 * PlatformStorage.createSearchSessionAsync()}. 37 * 38 * <p>All implementations of this interface must be thread safe. 39 * 40 * @see GlobalSearchSessionShim 41 */ 42 public interface AppSearchSessionShim extends Closeable { 43 44 /** 45 * Sets the schema that represents the organizational structure of data within the AppSearch 46 * database. 47 * 48 * <p>Upon creating an {@link AppSearchSessionShim}, {@link #setSchema} should be called. If the 49 * schema needs to be updated, or it has not been previously set, then the provided schema will 50 * be saved and persisted to disk. Otherwise, {@link #setSchema} is handled efficiently as a 51 * no-op call. 52 * 53 * @param request the schema to set or update the AppSearch database to. 54 * @return a {@link ListenableFuture} which resolves to a {@link SetSchemaResponse} object. 55 */ 56 @NonNull setSchemaAsync(@onNull SetSchemaRequest request)57 ListenableFuture<SetSchemaResponse> setSchemaAsync(@NonNull SetSchemaRequest request); 58 59 /** 60 * Retrieves the schema most recently successfully provided to {@link #setSchema}. 61 * 62 * @return The pending {@link GetSchemaResponse} of performing this operation. 63 */ 64 // This call hits disk; async API prevents us from treating these calls as properties. 65 @SuppressLint("KotlinPropertyAccess") 66 @NonNull getSchemaAsync()67 ListenableFuture<GetSchemaResponse> getSchemaAsync(); 68 69 /** 70 * Retrieves the set of all namespaces in the current database with at least one document. 71 * 72 * @return The pending result of performing this operation. 73 */ 74 @NonNull getNamespacesAsync()75 ListenableFuture<Set<String>> getNamespacesAsync(); 76 77 /** 78 * Indexes documents into the {@link AppSearchSessionShim} database. 79 * 80 * <p>Each {@link GenericDocument} object must have a {@code schemaType} field set to an {@link 81 * AppSearchSchema} type that has been previously registered by calling the {@link #setSchema} 82 * method. 83 * 84 * @param request containing documents to be indexed. 85 * @return a {@link ListenableFuture} which resolves to an {@link AppSearchBatchResult}. The 86 * keys of the returned {@link AppSearchBatchResult} are the IDs of the input documents. The 87 * values are either {@code null} if the corresponding document was successfully indexed, or 88 * a failed {@link AppSearchResult} otherwise. 89 */ 90 @NonNull putAsync( @onNull PutDocumentsRequest request)91 ListenableFuture<AppSearchBatchResult<String, Void>> putAsync( 92 @NonNull PutDocumentsRequest request); 93 94 /** 95 * Gets {@link GenericDocument} objects by document IDs in a namespace from the {@link 96 * AppSearchSessionShim} 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}. The 100 * keys of the {@link AppSearchBatchResult} represent the input document IDs from the {@link 101 * GetByDocumentIdRequest} object. The values are either the corresponding {@link 102 * GenericDocument} object for the ID on success, or an {@link AppSearchResult} object on 103 * failure. For example, if an ID is not found, the value for that ID will be set to an 104 * {@link AppSearchResult} object with result code: {@link 105 * AppSearchResult#RESULT_NOT_FOUND}. 106 */ 107 @NonNull getByDocumentIdAsync( @onNull GetByDocumentIdRequest request)108 ListenableFuture<AppSearchBatchResult<String, GenericDocument>> getByDocumentIdAsync( 109 @NonNull GetByDocumentIdRequest request); 110 111 /** 112 * Retrieves documents from the open {@link AppSearchSessionShim} that match a given query 113 * string and type of search provided. 114 * 115 * <p>Query strings can be empty, contain one term with no operators, or contain multiple terms 116 * and operators. 117 * 118 * <p>For query strings that are empty, all documents that match the {@link SearchSpec} will be 119 * returned. 120 * 121 * <p>For query strings with a single term and no operators, documents that match the provided 122 * query string and {@link SearchSpec} will be returned. 123 * 124 * <p>The following operators are supported: 125 * 126 * <ul> 127 * <li>AND (implicit) 128 * <p>AND is an operator that matches documents that contain <i>all</i> provided terms. 129 * <p><b>NOTE:</b> A space between terms is treated as an "AND" operator. Explicitly 130 * including "AND" in a query string will treat "AND" as a term, returning documents that 131 * also contain "AND". 132 * <p>Example: "apple AND banana" matches documents that contain the terms "apple", "and", 133 * "banana". 134 * <p>Example: "apple banana" matches documents that contain both "apple" and "banana". 135 * <p>Example: "apple banana cherry" matches documents that contain "apple", "banana", and 136 * "cherry". 137 * <li>OR 138 * <p>OR is an operator that matches documents that contain <i>any</i> provided term. 139 * <p>Example: "apple OR banana" matches documents that contain either "apple" or 140 * "banana". 141 * <p>Example: "apple OR banana OR cherry" matches documents that contain any of "apple", 142 * "banana", or "cherry". 143 * <li>Exclusion (-) 144 * <p>Exclusion (-) is an operator that matches documents that <i>do not</i> contain the 145 * provided term. 146 * <p>Example: "-apple" matches documents that do not contain "apple". 147 * <li>Grouped Terms 148 * <p>For queries that require multiple operators and terms, terms can be grouped into 149 * subqueries. Subqueries are contained within an open "(" and close ")" parenthesis. 150 * <p>Example: "(donut OR bagel) (coffee OR tea)" matches documents that contain either 151 * "donut" or "bagel" and either "coffee" or "tea". 152 * <li>Property Restricts 153 * <p>For queries that require a term to match a specific {@link AppSearchSchema} property 154 * of a document, a ":" must be included between the property name and the term. 155 * <p>Example: "subject:important" matches documents that contain the term "important" in 156 * the "subject" property. 157 * </ul> 158 * 159 * <p>The above description covers the query operators that are supported on all versions of 160 * AppSearch. Additional operators and their required features are described below. 161 * 162 * <p>{@link Features#LIST_FILTER_QUERY_LANGUAGE}: This feature covers the expansion of the 163 * query language to conform to the definition of the list filters language (https://aip 164 * .dev/160). This includes: 165 * 166 * <ul> 167 * <li>addition of explicit 'AND' and 'NOT' operators 168 * <li>property restricts are allowed with groupings (ex. "prop:(a OR b)") 169 * <li>addition of custom functions to control matching 170 * </ul> 171 * 172 * <p>The newly added custom functions covered by this feature are: 173 * 174 * <ul> 175 * <li>createList(String...) 176 * <li>search(String, {@code List<String>}) 177 * <li>propertyDefined(String) 178 * </ul> 179 * 180 * <p>createList takes a variable number of strings and returns a list of strings. It is for use 181 * with search. 182 * 183 * <p>search takes a query string that will be parsed according to the supported query language 184 * and an optional list of strings that specify the properties to be restricted to. This exists 185 * as a convenience for multiple property restricts. So, for example, the query `(subject:foo OR 186 * body:foo) (subject:bar OR body:bar)` could be rewritten as `search("foo bar", 187 * createList("subject", "bar"))`. 188 * 189 * <p>propertyDefined takes a string specifying the property of interest and matches all 190 * documents of any type that defines the specified property (ex. 191 * `propertyDefined("sender.name")`). Note that propertyDefined will match so long as the 192 * document's type defines the specified property. Unlike the "hasProperty" function below, this 193 * function does NOT require that the document actually hold any values for this property. 194 * 195 * <p>{@link Features#NUMERIC_SEARCH}: This feature covers numeric search expressions. In the 196 * query language, the values of properties that have {@link 197 * AppSearchSchema.LongPropertyConfig#INDEXING_TYPE_RANGE} set can be matched with a numeric 198 * search expression (the property, a supported comparator and an integer value). Supported 199 * comparators are <, <=, ==, >= and >. 200 * 201 * <p>Ex. `price < 10` will match all documents that has a numeric value in its price property 202 * that is less than 10. 203 * 204 * <p>{@link Features#VERBATIM_SEARCH}: This feature covers the verbatim string operator 205 * (quotation marks). 206 * 207 * <p>Ex. `"foo/bar" OR baz` will ensure that 'foo/bar' is treated as a single 'verbatim' token. 208 * 209 * <p>{@link Features#LIST_FILTER_HAS_PROPERTY_FUNCTION}: This feature covers the "hasProperty" 210 * function in query expressions, which takes a string specifying the property of interest and 211 * matches all documents that hold values for this property. Not to be confused with the 212 * "propertyDefined" function, which checks whether a document's schema has defined the 213 * property, instead of whether a document itself has this property. 214 * 215 * <p>Ex. `foo hasProperty("sender.name")` will return all documents that have the term "foo" 216 * AND have values in the property "sender.name". Consider two documents, documentA and 217 * documentB, of the same schema with an optional property "sender.name". If documentA sets 218 * "foo" in this property but documentB does not, then `hasProperty("sender.name")` will only 219 * match documentA. However, `propertyDefined("sender.name")` will match both documentA and 220 * documentB, regardless of whether a value is actually set. 221 * 222 * <p>{@link Features#SCHEMA_EMBEDDING_PROPERTY_CONFIG}: This feature covers the 223 * "semanticSearch" and "getSearchSpecEmbedding" functions in query expressions, which are used 224 * for semantic search. 225 * 226 * <p>Usage: semanticSearch(getSearchSpecEmbedding({embedding_index}), {low}, {high}, {metric}) 227 * 228 * <ul> 229 * <li>semanticSearch matches all documents that have at least one embedding vector with a 230 * matching model signature (see {@link EmbeddingVector#getModelSignature()}) and a 231 * similarity score within the range specified based on the provided metric. 232 * <li>getSearchSpecEmbedding({embedding_index}) retrieves the embedding search passed in 233 * {@link SearchSpec.Builder#addSearchEmbeddings} based on the index specified, which 234 * starts from 0. 235 * <li>"low" and "high" are floating point numbers that specify the similarity score range. If 236 * omitted, they default to negative and positive infinity, respectively. 237 * <li>"metric" is a string value that specifies how embedding similarities should be 238 * calculated. If omitted, it defaults to the metric specified in {@link 239 * SearchSpec.Builder#setDefaultEmbeddingSearchMetricType(int)}. Possible values: 240 * <ul> 241 * <li>"COSINE" 242 * <li>"DOT_PRODUCT" 243 * <li>"EUCLIDEAN" 244 * </ul> 245 * </ul> 246 * 247 * <p>Examples: 248 * 249 * <ul> 250 * <li>Basic: semanticSearch(getSearchSpecEmbedding(0), 0.5, 1, "COSINE") 251 * <li>With a property restriction: property1:semanticSearch(getSearchSpecEmbedding(0), 0.5, 252 * 1) 253 * <li>Hybrid: foo OR semanticSearch(getSearchSpecEmbedding(0), 0.5, 1) 254 * <li>Complex: (foo OR semanticSearch(getSearchSpecEmbedding(0), 0.5, 1)) AND bar 255 * </ul> 256 * 257 * <p>{@link Features#LIST_FILTER_TOKENIZE_FUNCTION}: This feature covers the "tokenize" 258 * function in query expressions, which takes a string and treats the entire string as plain 259 * text. This string is then segmented, normalized and stripped of punctuation-only segments. 260 * The remaining tokens are then AND'd together. This function is useful for callers who wish to 261 * provide user input, but want to ensure that that user input does not invoke any query 262 * operators. 263 * 264 * <p>Ex. `foo OR tokenize("bar OR baz.")`. The string "bar OR baz." will be segmented into 265 * "bar", "OR", "baz", ".". Punctuation is removed and the segments are normalized to "bar", 266 * "or", "baz". This query will be equivalent to `foo OR (bar AND or AND baz)`. 267 * 268 * <p>Ex. `tokenize("\"bar\" OR \\baz")`. Quotation marks and escape characters must be escaped. 269 * This query will be segmented into "\"", "bar", "\"", "OR", "\", "baz". Once stripped of 270 * punctuation and normalized, this will be equivalent to the query `bar AND or AND baz`. 271 * 272 * <p>The availability of each of these features can be checked by calling {@link 273 * Features#isFeatureSupported} with the desired feature. 274 * 275 * <p>Additional search specifications, such as filtering by {@link AppSearchSchema} type or 276 * adding projection, can be set by calling the corresponding {@link SearchSpec.Builder} setter. 277 * 278 * <p>This method is lightweight. The heavy work will be done in {@link 279 * SearchResultsShim#getNextPage}. 280 * 281 * @param queryExpression query string to search. 282 * @param searchSpec spec for setting document filters, adding projection, setting term match 283 * type, etc. 284 * @return a {@link SearchResultsShim} object for retrieved matched documents. 285 */ 286 // TODO(b/326656531): Refine the javadoc to provide guidance on the best practice of 287 // embedding searches and how to select an appropriate metric. 288 @NonNull search(@onNull String queryExpression, @NonNull SearchSpec searchSpec)289 SearchResultsShim search(@NonNull String queryExpression, @NonNull SearchSpec searchSpec); 290 291 /** 292 * Retrieves suggested Strings that could be used as {@code queryExpression} in {@link 293 * #search(String, SearchSpec)} API. 294 * 295 * <p>The {@code suggestionQueryExpression} can contain one term with no operators, or contain 296 * multiple terms and operators. Operators will be considered as a normal term. Please see the 297 * operator examples below. The {@code suggestionQueryExpression} must end with a valid term, 298 * the suggestions are generated based on the last term. If the input {@code 299 * suggestionQueryExpression} doesn't have a valid token, AppSearch will return an empty result 300 * list. Please see the invalid examples below. 301 * 302 * <p>Example: if there are following documents with content stored in AppSearch. 303 * 304 * <ul> 305 * <li>document1: "term1" 306 * <li>document2: "term1 term2" 307 * <li>document3: "term1 term2 term3" 308 * <li>document4: "org" 309 * </ul> 310 * 311 * <p>Search suggestions with the single term {@code suggestionQueryExpression} "t", the 312 * suggested results are: 313 * 314 * <ul> 315 * <li>"term1" - Use it to be queryExpression in {@link #search} could get 3 {@link 316 * SearchResult}s, which contains document 1, 2 and 3. 317 * <li>"term2" - Use it to be queryExpression in {@link #search} could get 2 {@link 318 * SearchResult}s, which contains document 2 and 3. 319 * <li>"term3" - Use it to be queryExpression in {@link #search} could get 1 {@link 320 * SearchResult}, which contains document 3. 321 * </ul> 322 * 323 * <p>Search suggestions with the multiple term {@code suggestionQueryExpression} "org t", the 324 * suggested result will be "org term1" - The last token is completed by the suggested String. 325 * 326 * <p>Operators in {@link #search} are supported. 327 * 328 * <p><b>NOTE:</b> Exclusion and Grouped Terms in the last term is not supported. 329 * 330 * <p>example: "apple -f": This Api will throw an {@link 331 * android.app.appsearch.exceptions.AppSearchException} with {@link 332 * AppSearchResult#RESULT_INVALID_ARGUMENT}. 333 * 334 * <p>example: "apple (f)": This Api will return an empty results. 335 * 336 * <p>Invalid example: All these input {@code suggestionQueryExpression} don't have a valid last 337 * token, AppSearch will return an empty result list. 338 * 339 * <ul> 340 * <li>"" - Empty {@code suggestionQueryExpression}. 341 * <li>"(f)" - Ending in a closed brackets. 342 * <li>"f:" - Ending in an operator. 343 * <li>"f " - Ending in trailing space. 344 * </ul> 345 * 346 * @param suggestionQueryExpression the non empty query string to search suggestions 347 * @param searchSuggestionSpec spec for setting document filters 348 * @return The pending result of performing this operation which resolves to a List of {@link 349 * SearchSuggestionResult} on success. The returned suggestion Strings are ordered by the 350 * number of {@link SearchResult} you could get by using that suggestion in {@link #search}. 351 * @see #search(String, SearchSpec) 352 */ 353 @NonNull searchSuggestionAsync( @onNull String suggestionQueryExpression, @NonNull SearchSuggestionSpec searchSuggestionSpec)354 ListenableFuture<List<SearchSuggestionResult>> searchSuggestionAsync( 355 @NonNull String suggestionQueryExpression, 356 @NonNull SearchSuggestionSpec searchSuggestionSpec); 357 358 /** 359 * Reports usage of a particular document by namespace and ID. 360 * 361 * <p>A usage report represents an event in which a user interacted with or viewed a document. 362 * 363 * <p>For each call to {@link #reportUsage}, AppSearch updates usage count and usage recency * 364 * metrics for that particular document. These metrics are used for ordering {@link #search} 365 * results by the {@link SearchSpec#RANKING_STRATEGY_USAGE_COUNT} and {@link 366 * SearchSpec#RANKING_STRATEGY_USAGE_LAST_USED_TIMESTAMP} ranking strategies. 367 * 368 * <p>Reporting usage of a document is optional. 369 * 370 * @param request The usage reporting request. 371 * @return The pending result of performing this operation which resolves to {@code null} on 372 * success. 373 */ 374 @NonNull reportUsageAsync(@onNull ReportUsageRequest request)375 ListenableFuture<Void> reportUsageAsync(@NonNull ReportUsageRequest request); 376 377 /** 378 * Removes {@link GenericDocument} objects by document IDs in a namespace from the {@link 379 * AppSearchSessionShim} database. 380 * 381 * <p>Removed documents will no longer be surfaced by {@link #search} or {@link 382 * #getByDocumentId} calls. 383 * 384 * <p>Once the database crosses the document count or byte usage threshold, removed documents 385 * will be deleted from disk. 386 * 387 * @param request {@link RemoveByDocumentIdRequest} with IDs in a namespace to remove from the 388 * index. 389 * @return a {@link ListenableFuture} which resolves to an {@link AppSearchBatchResult}. The 390 * keys of the {@link AppSearchBatchResult} represent the input IDs from the {@link 391 * RemoveByDocumentIdRequest} object. The values are either {@code null} on success, or a 392 * failed {@link AppSearchResult} otherwise. IDs that are not found will return a failed 393 * {@link AppSearchResult} with a result code of {@link AppSearchResult#RESULT_NOT_FOUND}. 394 */ 395 @NonNull removeAsync( @onNull RemoveByDocumentIdRequest request)396 ListenableFuture<AppSearchBatchResult<String, Void>> removeAsync( 397 @NonNull RemoveByDocumentIdRequest request); 398 399 /** 400 * Removes {@link GenericDocument}s from the index by Query. Documents will be removed if they 401 * match the {@code queryExpression} in given namespaces and schemaTypes which is set via {@link 402 * SearchSpec.Builder#addFilterNamespaces} and {@link SearchSpec.Builder#addFilterSchemas}. 403 * 404 * <p>An empty {@code queryExpression} matches all documents. 405 * 406 * <p>An empty set of namespaces or schemaTypes matches all namespaces or schemaTypes in the 407 * current database. 408 * 409 * @param queryExpression Query String to search. 410 * @param searchSpec Spec containing schemaTypes, namespaces and query expression indicates how 411 * document will be removed. All specific about how to scoring, ordering, snippeting and 412 * resulting will be ignored. 413 * @return The pending result of performing this operation. 414 * @throws IllegalArgumentException if the {@link SearchSpec} contains a {@link JoinSpec}. 415 * {@link JoinSpec} lets you join docs that are not owned by the caller, so the semantics of 416 * failures from this method would be complex. 417 */ 418 @NonNull removeAsync( @onNull String queryExpression, @NonNull SearchSpec searchSpec)419 ListenableFuture<Void> removeAsync( 420 @NonNull String queryExpression, @NonNull SearchSpec searchSpec); 421 422 /** 423 * Gets the storage info for this {@link AppSearchSessionShim} database. 424 * 425 * <p>This may take time proportional to the number of documents and may be inefficient to call 426 * repeatedly. 427 * 428 * @return a {@link ListenableFuture} which resolves to a {@link StorageInfo} object. 429 */ 430 @NonNull getStorageInfoAsync()431 ListenableFuture<StorageInfo> getStorageInfoAsync(); 432 433 /** 434 * Flush all schema and document updates, additions, and deletes to disk if possible. 435 * 436 * <p>The request is not guaranteed to be handled and may be ignored by some implementations of 437 * AppSearchSessionShim. 438 * 439 * @return The pending result of performing this operation. {@link 440 * android.app.appsearch.exceptions.AppSearchException} with {@link 441 * AppSearchResult#RESULT_INTERNAL_ERROR} will be set to the future if we hit error when 442 * save to disk. 443 */ 444 @NonNull requestFlushAsync()445 ListenableFuture<Void> requestFlushAsync(); 446 447 /** 448 * Returns the {@link Features} to check for the availability of certain features for this 449 * session. 450 */ 451 @NonNull getFeatures()452 Features getFeatures(); 453 454 /** 455 * Closes the {@link AppSearchSessionShim} to persist all schema and document updates, 456 * additions, and deletes to disk. 457 */ 458 @Override close()459 void close(); 460 } 461