blob: affdaa897591db5346fb70b3923b3da06d1e0ffd [file] [log] [blame]
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed 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 android.app.appsearch.cts.app;
import static android.app.appsearch.AppSearchResult.RESULT_INVALID_SCHEMA;
import static android.app.appsearch.AppSearchResult.RESULT_NOT_FOUND;
import static android.app.appsearch.testutil.AppSearchTestUtils.checkIsBatchResultSuccess;
import static android.app.appsearch.testutil.AppSearchTestUtils.convertSearchResultsToDocuments;
import static android.app.appsearch.testutil.AppSearchTestUtils.doGet;
import static android.app.appsearch.testutil.AppSearchTestUtils.retrieveAllSearchResults;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
import static org.junit.Assume.assumeFalse;
import static org.junit.Assume.assumeTrue;
import android.annotation.NonNull;
import android.app.appsearch.AppSearchBatchResult;
import android.app.appsearch.AppSearchResult;
import android.app.appsearch.AppSearchSchema;
import android.app.appsearch.AppSearchSchema.PropertyConfig;
import android.app.appsearch.AppSearchSchema.StringPropertyConfig;
import android.app.appsearch.AppSearchSessionShim;
import android.app.appsearch.Features;
import android.app.appsearch.GenericDocument;
import android.app.appsearch.GetByDocumentIdRequest;
import android.app.appsearch.GetSchemaResponse;
import android.app.appsearch.PackageIdentifier;
import android.app.appsearch.PutDocumentsRequest;
import android.app.appsearch.RemoveByDocumentIdRequest;
import android.app.appsearch.ReportUsageRequest;
import android.app.appsearch.SearchResult;
import android.app.appsearch.SearchResultsShim;
import android.app.appsearch.SearchSpec;
import android.app.appsearch.SetSchemaRequest;
import android.app.appsearch.StorageInfo;
import android.app.appsearch.exceptions.AppSearchException;
import android.app.appsearch.testutil.AppSearchEmail;
import android.content.Context;
import android.util.ArrayMap;
import androidx.test.core.app.ApplicationProvider;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
public abstract class AppSearchSessionCtsTestBase {
static final String DB_NAME_1 = "";
static final String DB_NAME_2 = "testDb2";
private final Context mContext = ApplicationProvider.getApplicationContext();
private AppSearchSessionShim mDb1;
private AppSearchSessionShim mDb2;
protected abstract ListenableFuture<AppSearchSessionShim> createSearchSessionAsync(
@NonNull String dbName);
protected abstract ListenableFuture<AppSearchSessionShim> createSearchSessionAsync(
@NonNull String dbName, @NonNull ExecutorService executor);
@Before
public void setUp() throws Exception {
mDb1 = createSearchSessionAsync(DB_NAME_1).get();
mDb2 = createSearchSessionAsync(DB_NAME_2).get();
// Cleanup whatever documents may still exist in these databases. This is needed in
// addition to tearDown in case a test exited without completing properly.
cleanup();
}
@After
public void tearDown() throws Exception {
// Cleanup whatever documents may still exist in these databases.
cleanup();
}
private void cleanup() throws Exception {
mDb1.setSchemaAsync(new SetSchemaRequest.Builder().setForceOverride(true).build()).get();
mDb2.setSchemaAsync(new SetSchemaRequest.Builder().setForceOverride(true).build()).get();
}
@Test
public void testSetSchema() throws Exception {
AppSearchSchema emailSchema =
new AppSearchSchema.Builder("Email")
.addProperty(
new StringPropertyConfig.Builder("subject")
.setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
.setIndexingType(
StringPropertyConfig.INDEXING_TYPE_PREFIXES)
.setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
.build())
.addProperty(
new StringPropertyConfig.Builder("body")
.setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
.setIndexingType(
StringPropertyConfig.INDEXING_TYPE_PREFIXES)
.setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
.build())
.build();
mDb1.setSchemaAsync(new SetSchemaRequest.Builder().addSchemas(emailSchema).build()).get();
}
@Test
public void testSetSchema_Failure() throws Exception {
mDb1.setSchemaAsync(
new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
.get();
AppSearchSchema emailSchema1 =
new AppSearchSchema.Builder(AppSearchEmail.SCHEMA_TYPE).build();
Throwable throwable =
assertThrows(
ExecutionException.class,
() ->
mDb1.setSchemaAsync(
new SetSchemaRequest.Builder()
.addSchemas(emailSchema1)
.build())
.get())
.getCause();
assertThat(throwable).isInstanceOf(AppSearchException.class);
AppSearchException exception = (AppSearchException) throwable;
assertThat(exception.getResultCode()).isEqualTo(RESULT_INVALID_SCHEMA);
assertThat(exception).hasMessageThat().contains("Schema is incompatible.");
assertThat(exception).hasMessageThat().contains("Incompatible types: {builtin:Email}");
throwable =
assertThrows(
ExecutionException.class,
() ->
mDb1.setSchemaAsync(new SetSchemaRequest.Builder().build())
.get())
.getCause();
assertThat(throwable).isInstanceOf(AppSearchException.class);
exception = (AppSearchException) throwable;
assertThat(exception.getResultCode()).isEqualTo(RESULT_INVALID_SCHEMA);
assertThat(exception).hasMessageThat().contains("Schema is incompatible.");
assertThat(exception).hasMessageThat().contains("Deleted types: {builtin:Email}");
}
@Test
public void testSetSchema_updateVersion() throws Exception {
AppSearchSchema schema =
new AppSearchSchema.Builder("Email")
.addProperty(
new StringPropertyConfig.Builder("subject")
.setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
.setIndexingType(
StringPropertyConfig.INDEXING_TYPE_PREFIXES)
.setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
.build())
.addProperty(
new StringPropertyConfig.Builder("body")
.setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
.setIndexingType(
StringPropertyConfig.INDEXING_TYPE_PREFIXES)
.setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
.build())
.build();
mDb1.setSchemaAsync(new SetSchemaRequest.Builder().addSchemas(schema).setVersion(1).build())
.get();
Set<AppSearchSchema> actualSchemaTypes = mDb1.getSchemaAsync().get().getSchemas();
assertThat(actualSchemaTypes).containsExactly(schema);
// increase version number
mDb1.setSchemaAsync(new SetSchemaRequest.Builder().addSchemas(schema).setVersion(2).build())
.get();
GetSchemaResponse getSchemaResponse = mDb1.getSchemaAsync().get();
assertThat(getSchemaResponse.getSchemas()).containsExactly(schema);
assertThat(getSchemaResponse.getVersion()).isEqualTo(2);
}
@Test
public void testSetSchema_checkVersion() throws Exception {
AppSearchSchema schema =
new AppSearchSchema.Builder("Email")
.addProperty(
new StringPropertyConfig.Builder("subject")
.setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
.setIndexingType(
StringPropertyConfig.INDEXING_TYPE_PREFIXES)
.setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
.build())
.addProperty(
new StringPropertyConfig.Builder("body")
.setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
.setIndexingType(
StringPropertyConfig.INDEXING_TYPE_PREFIXES)
.setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
.build())
.build();
// set different version number to different database.
mDb1.setSchemaAsync(
new SetSchemaRequest.Builder().addSchemas(schema).setVersion(135).build())
.get();
mDb2.setSchemaAsync(
new SetSchemaRequest.Builder().addSchemas(schema).setVersion(246).build())
.get();
// check the version has been set correctly.
GetSchemaResponse getSchemaResponse = mDb1.getSchemaAsync().get();
assertThat(getSchemaResponse.getSchemas()).containsExactly(schema);
assertThat(getSchemaResponse.getVersion()).isEqualTo(135);
getSchemaResponse = mDb2.getSchemaAsync().get();
assertThat(getSchemaResponse.getSchemas()).containsExactly(schema);
assertThat(getSchemaResponse.getVersion()).isEqualTo(246);
}
@Test
public void testGetSchema_allPropertyTypes() throws Exception {
AppSearchSchema inSchema =
new AppSearchSchema.Builder("Test")
.addProperty(
new StringPropertyConfig.Builder("string")
.setCardinality(PropertyConfig.CARDINALITY_REQUIRED)
.setIndexingType(
StringPropertyConfig.INDEXING_TYPE_EXACT_TERMS)
.setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
.build())
.addProperty(
new AppSearchSchema.LongPropertyConfig.Builder("long")
.setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
.build())
.addProperty(
new AppSearchSchema.DoublePropertyConfig.Builder("double")
.setCardinality(PropertyConfig.CARDINALITY_REPEATED)
.build())
.addProperty(
new AppSearchSchema.BooleanPropertyConfig.Builder("boolean")
.setCardinality(PropertyConfig.CARDINALITY_REQUIRED)
.build())
.addProperty(
new AppSearchSchema.BytesPropertyConfig.Builder("bytes")
.setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
.build())
.addProperty(
new AppSearchSchema.DocumentPropertyConfig.Builder(
"document", AppSearchEmail.SCHEMA_TYPE)
.setCardinality(PropertyConfig.CARDINALITY_REPEATED)
.setShouldIndexNestedProperties(true)
.build())
.build();
// Add it to AppSearch and then obtain it again
mDb1.setSchemaAsync(
new SetSchemaRequest.Builder()
.addSchemas(inSchema, AppSearchEmail.SCHEMA)
.build())
.get();
GetSchemaResponse response = mDb1.getSchemaAsync().get();
List<AppSearchSchema> schemas = new ArrayList<>(response.getSchemas());
assertThat(schemas).containsExactly(inSchema, AppSearchEmail.SCHEMA);
AppSearchSchema outSchema;
if (schemas.get(0).getSchemaType().equals("Test")) {
outSchema = schemas.get(0);
} else {
outSchema = schemas.get(1);
}
assertThat(outSchema.getSchemaType()).isEqualTo("Test");
assertThat(outSchema).isNotSameInstanceAs(inSchema);
List<PropertyConfig> properties = outSchema.getProperties();
assertThat(properties).hasSize(6);
assertThat(properties.get(0).getName()).isEqualTo("string");
assertThat(properties.get(0).getCardinality())
.isEqualTo(PropertyConfig.CARDINALITY_REQUIRED);
assertThat(((StringPropertyConfig) properties.get(0)).getIndexingType())
.isEqualTo(StringPropertyConfig.INDEXING_TYPE_EXACT_TERMS);
assertThat(((StringPropertyConfig) properties.get(0)).getTokenizerType())
.isEqualTo(StringPropertyConfig.TOKENIZER_TYPE_PLAIN);
assertThat(properties.get(1).getName()).isEqualTo("long");
assertThat(properties.get(1).getCardinality())
.isEqualTo(PropertyConfig.CARDINALITY_OPTIONAL);
assertThat(properties.get(1)).isInstanceOf(AppSearchSchema.LongPropertyConfig.class);
assertThat(properties.get(2).getName()).isEqualTo("double");
assertThat(properties.get(2).getCardinality())
.isEqualTo(PropertyConfig.CARDINALITY_REPEATED);
assertThat(properties.get(2)).isInstanceOf(AppSearchSchema.DoublePropertyConfig.class);
assertThat(properties.get(3).getName()).isEqualTo("boolean");
assertThat(properties.get(3).getCardinality())
.isEqualTo(PropertyConfig.CARDINALITY_REQUIRED);
assertThat(properties.get(3)).isInstanceOf(AppSearchSchema.BooleanPropertyConfig.class);
assertThat(properties.get(4).getName()).isEqualTo("bytes");
assertThat(properties.get(4).getCardinality())
.isEqualTo(PropertyConfig.CARDINALITY_OPTIONAL);
assertThat(properties.get(4)).isInstanceOf(AppSearchSchema.BytesPropertyConfig.class);
assertThat(properties.get(5).getName()).isEqualTo("document");
assertThat(properties.get(5).getCardinality())
.isEqualTo(PropertyConfig.CARDINALITY_REPEATED);
assertThat(((AppSearchSchema.DocumentPropertyConfig) properties.get(5)).getSchemaType())
.isEqualTo(AppSearchEmail.SCHEMA_TYPE);
assertThat(
((AppSearchSchema.DocumentPropertyConfig) properties.get(5))
.shouldIndexNestedProperties())
.isEqualTo(true);
}
@Test
public void testGetSchema_visibilitySetting() throws Exception {
assumeTrue(
mDb1.getFeatures().isFeatureSupported(Features.ADD_PERMISSIONS_AND_GET_VISIBILITY));
AppSearchSchema emailSchema =
new AppSearchSchema.Builder("Email1")
.addProperty(
new StringPropertyConfig.Builder("subject")
.setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
.setIndexingType(
StringPropertyConfig.INDEXING_TYPE_PREFIXES)
.setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
.build())
.addProperty(
new StringPropertyConfig.Builder("body")
.setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
.setIndexingType(
StringPropertyConfig.INDEXING_TYPE_PREFIXES)
.setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
.build())
.build();
byte[] shar256Cert1 = new byte[32];
Arrays.fill(shar256Cert1, (byte) 1);
byte[] shar256Cert2 = new byte[32];
Arrays.fill(shar256Cert2, (byte) 2);
PackageIdentifier packageIdentifier1 = new PackageIdentifier("pkgFoo", shar256Cert1);
PackageIdentifier packageIdentifier2 = new PackageIdentifier("pkgBar", shar256Cert2);
SetSchemaRequest request =
new SetSchemaRequest.Builder()
.addSchemas(emailSchema)
.setSchemaTypeDisplayedBySystem("Email1", /*displayed=*/ false)
.setSchemaTypeVisibilityForPackage(
"Email1", /*visible=*/ true, packageIdentifier1)
.setSchemaTypeVisibilityForPackage(
"Email1", /*visible=*/ true, packageIdentifier2)
.addRequiredPermissionsForSchemaTypeVisibility(
"Email1",
ImmutableSet.of(
SetSchemaRequest.READ_SMS, SetSchemaRequest.READ_CALENDAR))
.addRequiredPermissionsForSchemaTypeVisibility(
"Email1",
ImmutableSet.of(SetSchemaRequest.READ_HOME_APP_SEARCH_DATA))
.build();
mDb1.setSchemaAsync(request).get();
GetSchemaResponse getSchemaResponse = mDb1.getSchemaAsync().get();
Set<AppSearchSchema> actual = getSchemaResponse.getSchemas();
assertThat(actual).hasSize(1);
assertThat(actual).isEqualTo(request.getSchemas());
assertThat(getSchemaResponse.getSchemaTypesNotDisplayedBySystem())
.containsExactly("Email1");
assertThat(getSchemaResponse.getSchemaTypesVisibleToPackages())
.containsExactly("Email1", ImmutableSet.of(packageIdentifier1, packageIdentifier2));
assertThat(getSchemaResponse.getRequiredPermissionsForSchemaTypeVisibility())
.containsExactly(
"Email1",
ImmutableSet.of(
ImmutableSet.of(
SetSchemaRequest.READ_SMS, SetSchemaRequest.READ_CALENDAR),
ImmutableSet.of(SetSchemaRequest.READ_HOME_APP_SEARCH_DATA)));
}
@Test
public void testGetSchema_visibilitySetting_notSupported() throws Exception {
assumeFalse(
mDb1.getFeatures().isFeatureSupported(Features.ADD_PERMISSIONS_AND_GET_VISIBILITY));
AppSearchSchema emailSchema =
new AppSearchSchema.Builder("Email1")
.addProperty(
new StringPropertyConfig.Builder("subject")
.setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
.setIndexingType(
StringPropertyConfig.INDEXING_TYPE_PREFIXES)
.setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
.build())
.addProperty(
new StringPropertyConfig.Builder("body")
.setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
.setIndexingType(
StringPropertyConfig.INDEXING_TYPE_PREFIXES)
.setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
.build())
.build();
byte[] shar256Cert1 = new byte[32];
Arrays.fill(shar256Cert1, (byte) 1);
byte[] shar256Cert2 = new byte[32];
Arrays.fill(shar256Cert2, (byte) 2);
PackageIdentifier packageIdentifier1 = new PackageIdentifier("pkgFoo", shar256Cert1);
PackageIdentifier packageIdentifier2 = new PackageIdentifier("pkgBar", shar256Cert2);
SetSchemaRequest request =
new SetSchemaRequest.Builder()
.addSchemas(emailSchema)
.setSchemaTypeDisplayedBySystem("Email1", /*displayed=*/ false)
.setSchemaTypeVisibilityForPackage(
"Email1", /*visible=*/ true, packageIdentifier1)
.setSchemaTypeVisibilityForPackage(
"Email1", /*visible=*/ true, packageIdentifier2)
.build();
mDb1.setSchemaAsync(request).get();
GetSchemaResponse getSchemaResponse = mDb1.getSchemaAsync().get();
Set<AppSearchSchema> actual = getSchemaResponse.getSchemas();
assertThat(actual).hasSize(1);
assertThat(actual).isEqualTo(request.getSchemas());
assertThrows(
UnsupportedOperationException.class,
() -> getSchemaResponse.getSchemaTypesNotDisplayedBySystem());
assertThrows(
UnsupportedOperationException.class,
() -> getSchemaResponse.getSchemaTypesVisibleToPackages());
assertThrows(
UnsupportedOperationException.class,
() -> getSchemaResponse.getRequiredPermissionsForSchemaTypeVisibility());
}
@Test
public void testSetSchema_visibilitySettingPermission_notSupported() {
assumeFalse(
mDb1.getFeatures().isFeatureSupported(Features.ADD_PERMISSIONS_AND_GET_VISIBILITY));
AppSearchSchema emailSchema = new AppSearchSchema.Builder("Email1").build();
SetSchemaRequest request =
new SetSchemaRequest.Builder()
.addSchemas(emailSchema)
.setSchemaTypeDisplayedBySystem("Email1", /*displayed=*/ false)
.addRequiredPermissionsForSchemaTypeVisibility(
"Email1", ImmutableSet.of(SetSchemaRequest.READ_SMS))
.build();
assertThrows(UnsupportedOperationException.class, () -> mDb1.setSchemaAsync(request).get());
}
@Test
public void testGetNamespaces() throws Exception {
// Schema registration
mDb1.setSchemaAsync(
new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
.get();
assertThat(mDb1.getNamespacesAsync().get()).isEmpty();
// Index a document
checkIsBatchResultSuccess(
mDb1.putAsync(
new PutDocumentsRequest.Builder()
.addGenericDocuments(
new AppSearchEmail.Builder("namespace1", "id1").build())
.build()));
assertThat(mDb1.getNamespacesAsync().get()).containsExactly("namespace1");
// Index additional data
checkIsBatchResultSuccess(
mDb1.putAsync(
new PutDocumentsRequest.Builder()
.addGenericDocuments(
new AppSearchEmail.Builder("namespace2", "id1").build(),
new AppSearchEmail.Builder("namespace2", "id2").build(),
new AppSearchEmail.Builder("namespace3", "id1").build())
.build()));
assertThat(mDb1.getNamespacesAsync().get())
.containsExactly("namespace1", "namespace2", "namespace3");
// Remove namespace2/id2 -- namespace2 should still exist because of namespace2/id1
checkIsBatchResultSuccess(
mDb1.removeAsync(
new RemoveByDocumentIdRequest.Builder("namespace2").addIds("id2").build()));
assertThat(mDb1.getNamespacesAsync().get())
.containsExactly("namespace1", "namespace2", "namespace3");
// Remove namespace2/id1 -- namespace2 should now be gone
checkIsBatchResultSuccess(
mDb1.removeAsync(
new RemoveByDocumentIdRequest.Builder("namespace2").addIds("id1").build()));
assertThat(mDb1.getNamespacesAsync().get()).containsExactly("namespace1", "namespace3");
// Make sure the list of namespaces is preserved after restart
mDb1.close();
mDb1 = createSearchSessionAsync(DB_NAME_1).get();
assertThat(mDb1.getNamespacesAsync().get()).containsExactly("namespace1", "namespace3");
}
@Test
public void testGetNamespaces_dbIsolation() throws Exception {
// Schema registration
mDb1.setSchemaAsync(
new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
.get();
mDb2.setSchemaAsync(
new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
.get();
assertThat(mDb1.getNamespacesAsync().get()).isEmpty();
assertThat(mDb2.getNamespacesAsync().get()).isEmpty();
// Index documents
checkIsBatchResultSuccess(
mDb1.putAsync(
new PutDocumentsRequest.Builder()
.addGenericDocuments(
new AppSearchEmail.Builder("namespace1_db1", "id1").build())
.build()));
checkIsBatchResultSuccess(
mDb1.putAsync(
new PutDocumentsRequest.Builder()
.addGenericDocuments(
new AppSearchEmail.Builder("namespace2_db1", "id1").build())
.build()));
checkIsBatchResultSuccess(
mDb2.putAsync(
new PutDocumentsRequest.Builder()
.addGenericDocuments(
new AppSearchEmail.Builder("namespace_db2", "id1").build())
.build()));
assertThat(mDb1.getNamespacesAsync().get())
.containsExactly("namespace1_db1", "namespace2_db1");
assertThat(mDb2.getNamespacesAsync().get()).containsExactly("namespace_db2");
// Make sure the list of namespaces is preserved after restart
mDb1.close();
mDb1 = createSearchSessionAsync(DB_NAME_1).get();
assertThat(mDb1.getNamespacesAsync().get())
.containsExactly("namespace1_db1", "namespace2_db1");
assertThat(mDb2.getNamespacesAsync().get()).containsExactly("namespace_db2");
}
@Test
public void testGetSchema_emptyDB() throws Exception {
GetSchemaResponse getSchemaResponse = mDb1.getSchemaAsync().get();
assertThat(getSchemaResponse.getVersion()).isEqualTo(0);
}
@Test
public void testPutDocuments() throws Exception {
// Schema registration
mDb1.setSchemaAsync(
new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
.get();
// Index a document
AppSearchEmail email =
new AppSearchEmail.Builder("namespace", "id1")
.setFrom("from@example.com")
.setTo("to1@example.com", "to2@example.com")
.setSubject("testPut example")
.setBody("This is the body of the testPut email")
.build();
AppSearchBatchResult<String, Void> result =
checkIsBatchResultSuccess(
mDb1.putAsync(
new PutDocumentsRequest.Builder()
.addGenericDocuments(email)
.build()));
assertThat(result.getSuccesses()).containsExactly("id1", null);
assertThat(result.getFailures()).isEmpty();
}
@Test
public void testPutDocuments_emptyProperties() throws Exception {
// Schema registration. Due to b/204677124 is fixed in Android T. We have different
// behaviour when set empty array to bytes and documents between local and platform storage.
// This test only test String, long, boolean and double, for byte array and Document will be
// test in backend's specific test.
AppSearchSchema schema =
new AppSearchSchema.Builder("testSchema")
.addProperty(
new StringPropertyConfig.Builder("string")
.setCardinality(PropertyConfig.CARDINALITY_REPEATED)
.setIndexingType(
StringPropertyConfig.INDEXING_TYPE_EXACT_TERMS)
.setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
.build())
.addProperty(
new AppSearchSchema.LongPropertyConfig.Builder("long")
.setCardinality(PropertyConfig.CARDINALITY_REPEATED)
.build())
.addProperty(
new AppSearchSchema.DoublePropertyConfig.Builder("double")
.setCardinality(PropertyConfig.CARDINALITY_REPEATED)
.build())
.addProperty(
new AppSearchSchema.BooleanPropertyConfig.Builder("boolean")
.setCardinality(PropertyConfig.CARDINALITY_REPEATED)
.build())
.build();
mDb1.setSchemaAsync(
new SetSchemaRequest.Builder()
.addSchemas(schema, AppSearchEmail.SCHEMA)
.build())
.get();
// Index a document
GenericDocument document =
new GenericDocument.Builder<>("namespace", "id1", "testSchema")
.setPropertyBoolean("boolean")
.setPropertyString("string")
.setPropertyDouble("double")
.setPropertyLong("long")
.build();
AppSearchBatchResult<String, Void> result =
checkIsBatchResultSuccess(
mDb1.putAsync(
new PutDocumentsRequest.Builder()
.addGenericDocuments(document)
.build()));
assertThat(result.getSuccesses()).containsExactly("id1", null);
assertThat(result.getFailures()).isEmpty();
GetByDocumentIdRequest request =
new GetByDocumentIdRequest.Builder("namespace").addIds("id1").build();
List<GenericDocument> outDocuments = doGet(mDb1, request);
assertThat(outDocuments).hasSize(1);
GenericDocument outDocument = outDocuments.get(0);
assertThat(outDocument.getPropertyBooleanArray("boolean")).isEmpty();
assertThat(outDocument.getPropertyStringArray("string")).isEmpty();
assertThat(outDocument.getPropertyDoubleArray("double")).isEmpty();
assertThat(outDocument.getPropertyLongArray("long")).isEmpty();
}
@Test
public void testPutLargeDocumentBatch() throws Exception {
// Schema registration
AppSearchSchema schema =
new AppSearchSchema.Builder("Type")
.addProperty(
new StringPropertyConfig.Builder("body")
.setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
.setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
.setIndexingType(
StringPropertyConfig.INDEXING_TYPE_PREFIXES)
.build())
.build();
mDb1.setSchemaAsync(new SetSchemaRequest.Builder().addSchemas(schema).build()).get();
// Creates a large batch of Documents, since we have max document size in Framework which is
// 512KiB, we will create 1KiB * 4000 docs = 4MiB total size > 1MiB binder transaction limit
char[] chars = new char[1024]; // 1KiB
Arrays.fill(chars, ' ');
String body = String.valueOf(chars) + "the end.";
List<GenericDocument> inDocuments = new ArrayList<>();
GetByDocumentIdRequest.Builder getByDocumentIdRequestBuilder =
new GetByDocumentIdRequest.Builder("namespace");
for (int i = 0; i < 4000; i++) {
GenericDocument inDocument =
new GenericDocument.Builder<>("namespace", "id" + i, "Type")
.setPropertyString("body", body)
.build();
inDocuments.add(inDocument);
getByDocumentIdRequestBuilder.addIds("id" + i);
}
// Index documents.
AppSearchBatchResult<String, Void> result =
mDb1.putAsync(
new PutDocumentsRequest.Builder()
.addGenericDocuments(inDocuments)
.build())
.get();
assertThat(result.isSuccess()).isTrue();
// Query those documents and verify they are same with the input. This also verify
// AppSearchResult could handle large batch.
SearchResultsShim searchResults =
mDb1.search(
"end",
new SearchSpec.Builder()
.setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
.setResultCountPerPage(4000)
.build());
List<GenericDocument> outDocuments = convertSearchResultsToDocuments(searchResults);
// Create a map to assert the output is same to the input in O(n).
// containsExactlyElementsIn will create two iterators and the complexity is O(n^2).
Map<String, GenericDocument> outMap = new ArrayMap<>(outDocuments.size());
for (int i = 0; i < outDocuments.size(); i++) {
outMap.put(outDocuments.get(i).getId(), outDocuments.get(i));
}
for (int i = 0; i < inDocuments.size(); i++) {
GenericDocument inDocument = inDocuments.get(i);
assertThat(inDocument).isEqualTo(outMap.get(inDocument.getId()));
outMap.remove(inDocument.getId());
}
assertThat(outMap).isEmpty();
// Get by document ID and verify they are same with the input. This also verify
// AppSearchBatchResult could handle large batch.
AppSearchBatchResult<String, GenericDocument> batchResult =
mDb1.getByDocumentIdAsync(getByDocumentIdRequestBuilder.build()).get();
assertThat(batchResult.isSuccess()).isTrue();
for (int i = 0; i < inDocuments.size(); i++) {
GenericDocument inDocument = inDocuments.get(i);
assertThat(batchResult.getSuccesses().get(inDocument.getId())).isEqualTo(inDocument);
}
}
@Test
public void testUpdateSchema() throws Exception {
// Schema registration
AppSearchSchema oldEmailSchema =
new AppSearchSchema.Builder(AppSearchEmail.SCHEMA_TYPE)
.addProperty(
new StringPropertyConfig.Builder("subject")
.setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
.setIndexingType(
StringPropertyConfig.INDEXING_TYPE_PREFIXES)
.setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
.build())
.build();
AppSearchSchema newEmailSchema =
new AppSearchSchema.Builder(AppSearchEmail.SCHEMA_TYPE)
.addProperty(
new StringPropertyConfig.Builder("subject")
.setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
.setIndexingType(
StringPropertyConfig.INDEXING_TYPE_PREFIXES)
.setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
.build())
.addProperty(
new StringPropertyConfig.Builder("body")
.setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
.setIndexingType(
StringPropertyConfig.INDEXING_TYPE_PREFIXES)
.setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
.build())
.build();
AppSearchSchema giftSchema =
new AppSearchSchema.Builder("Gift")
.addProperty(
new AppSearchSchema.LongPropertyConfig.Builder("price")
.setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
.build())
.build();
mDb1.setSchemaAsync(new SetSchemaRequest.Builder().addSchemas(oldEmailSchema).build())
.get();
// Try to index a gift. This should fail as it's not in the schema.
GenericDocument gift =
new GenericDocument.Builder<>("namespace", "gift1", "Gift")
.setPropertyLong("price", 5)
.build();
AppSearchBatchResult<String, Void> result =
mDb1.putAsync(new PutDocumentsRequest.Builder().addGenericDocuments(gift).build())
.get();
assertThat(result.isSuccess()).isFalse();
assertThat(result.getFailures().get("gift1").getResultCode())
.isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
// Update the schema to include the gift and update email with a new field
mDb1.setSchemaAsync(
new SetSchemaRequest.Builder()
.addSchemas(newEmailSchema, giftSchema)
.build())
.get();
// Try to index the document again, which should now work
checkIsBatchResultSuccess(
mDb1.putAsync(new PutDocumentsRequest.Builder().addGenericDocuments(gift).build()));
// Indexing an email with a body should also work
AppSearchEmail email =
new AppSearchEmail.Builder("namespace", "email1")
.setSubject("testPut example")
.setBody("This is the body of the testPut email")
.build();
checkIsBatchResultSuccess(
mDb1.putAsync(
new PutDocumentsRequest.Builder().addGenericDocuments(email).build()));
}
@Test
public void testRemoveSchema() throws Exception {
// Schema registration
AppSearchSchema emailSchema =
new AppSearchSchema.Builder(AppSearchEmail.SCHEMA_TYPE)
.addProperty(
new StringPropertyConfig.Builder("subject")
.setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
.setIndexingType(
StringPropertyConfig.INDEXING_TYPE_PREFIXES)
.setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
.build())
.build();
mDb1.setSchemaAsync(new SetSchemaRequest.Builder().addSchemas(emailSchema).build()).get();
// Index an email and check it present.
AppSearchEmail email =
new AppSearchEmail.Builder("namespace", "email1")
.setSubject("testPut example")
.build();
checkIsBatchResultSuccess(
mDb1.putAsync(
new PutDocumentsRequest.Builder().addGenericDocuments(email).build()));
List<GenericDocument> outDocuments = doGet(mDb1, "namespace", "email1");
assertThat(outDocuments).hasSize(1);
AppSearchEmail outEmail = new AppSearchEmail(outDocuments.get(0));
assertThat(outEmail).isEqualTo(email);
// Try to remove the email schema. This should fail as it's an incompatible change.
Throwable failResult1 =
assertThrows(
ExecutionException.class,
() ->
mDb1.setSchemaAsync(new SetSchemaRequest.Builder().build())
.get())
.getCause();
assertThat(failResult1).isInstanceOf(AppSearchException.class);
assertThat(failResult1).hasMessageThat().contains("Schema is incompatible");
assertThat(failResult1).hasMessageThat().contains("Deleted types: {builtin:Email}");
// Try to remove the email schema again, which should now work as we set forceOverride to
// be true.
mDb1.setSchemaAsync(new SetSchemaRequest.Builder().setForceOverride(true).build()).get();
// Make sure the indexed email is gone.
AppSearchBatchResult<String, GenericDocument> getResult =
mDb1.getByDocumentIdAsync(
new GetByDocumentIdRequest.Builder("namespace")
.addIds("email1")
.build())
.get();
assertThat(getResult.isSuccess()).isFalse();
assertThat(getResult.getFailures().get("email1").getResultCode())
.isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
// Try to index an email again. This should fail as the schema has been removed.
AppSearchEmail email2 =
new AppSearchEmail.Builder("namespace", "email2")
.setSubject("testPut example")
.build();
AppSearchBatchResult<String, Void> failResult2 =
mDb1.putAsync(new PutDocumentsRequest.Builder().addGenericDocuments(email2).build())
.get();
assertThat(failResult2.isSuccess()).isFalse();
assertThat(failResult2.getFailures().get("email2").getErrorMessage())
.isEqualTo(
"Schema type config '"
+ mContext.getPackageName()
+ "$"
+ DB_NAME_1
+ "/builtin:Email' not found");
}
@Test
public void testRemoveSchema_twoDatabases() throws Exception {
// Schema registration in mDb1 and mDb2
AppSearchSchema emailSchema =
new AppSearchSchema.Builder(AppSearchEmail.SCHEMA_TYPE)
.addProperty(
new StringPropertyConfig.Builder("subject")
.setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
.setIndexingType(
StringPropertyConfig.INDEXING_TYPE_PREFIXES)
.setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
.build())
.build();
mDb1.setSchemaAsync(new SetSchemaRequest.Builder().addSchemas(emailSchema).build()).get();
mDb2.setSchemaAsync(new SetSchemaRequest.Builder().addSchemas(emailSchema).build()).get();
// Index an email and check it present in database1.
AppSearchEmail email1 =
new AppSearchEmail.Builder("namespace", "email1")
.setSubject("testPut example")
.build();
checkIsBatchResultSuccess(
mDb1.putAsync(
new PutDocumentsRequest.Builder().addGenericDocuments(email1).build()));
List<GenericDocument> outDocuments = doGet(mDb1, "namespace", "email1");
assertThat(outDocuments).hasSize(1);
AppSearchEmail outEmail = new AppSearchEmail(outDocuments.get(0));
assertThat(outEmail).isEqualTo(email1);
// Index an email and check it present in database2.
AppSearchEmail email2 =
new AppSearchEmail.Builder("namespace", "email2")
.setSubject("testPut example")
.build();
checkIsBatchResultSuccess(
mDb2.putAsync(
new PutDocumentsRequest.Builder().addGenericDocuments(email2).build()));
outDocuments = doGet(mDb2, "namespace", "email2");
assertThat(outDocuments).hasSize(1);
outEmail = new AppSearchEmail(outDocuments.get(0));
assertThat(outEmail).isEqualTo(email2);
// Try to remove the email schema in database1. This should fail as it's an incompatible
// change.
Throwable failResult1 =
assertThrows(
ExecutionException.class,
() ->
mDb1.setSchemaAsync(new SetSchemaRequest.Builder().build())
.get())
.getCause();
assertThat(failResult1).isInstanceOf(AppSearchException.class);
assertThat(failResult1).hasMessageThat().contains("Schema is incompatible");
assertThat(failResult1).hasMessageThat().contains("Deleted types: {builtin:Email}");
// Try to remove the email schema again, which should now work as we set forceOverride to
// be true.
mDb1.setSchemaAsync(new SetSchemaRequest.Builder().setForceOverride(true).build()).get();
// Make sure the indexed email is gone in database 1.
AppSearchBatchResult<String, GenericDocument> getResult =
mDb1.getByDocumentIdAsync(
new GetByDocumentIdRequest.Builder("namespace")
.addIds("email1")
.build())
.get();
assertThat(getResult.isSuccess()).isFalse();
assertThat(getResult.getFailures().get("email1").getResultCode())
.isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
// Try to index an email again. This should fail as the schema has been removed.
AppSearchEmail email3 =
new AppSearchEmail.Builder("namespace", "email3")
.setSubject("testPut example")
.build();
AppSearchBatchResult<String, Void> failResult2 =
mDb1.putAsync(new PutDocumentsRequest.Builder().addGenericDocuments(email3).build())
.get();
assertThat(failResult2.isSuccess()).isFalse();
assertThat(failResult2.getFailures().get("email3").getErrorMessage())
.isEqualTo(
"Schema type config '"
+ mContext.getPackageName()
+ "$"
+ DB_NAME_1
+ "/builtin:Email' not found");
// Make sure email in database 2 still present.
outDocuments = doGet(mDb2, "namespace", "email2");
assertThat(outDocuments).hasSize(1);
outEmail = new AppSearchEmail(outDocuments.get(0));
assertThat(outEmail).isEqualTo(email2);
// Make sure email could still be indexed in database 2.
checkIsBatchResultSuccess(
mDb2.putAsync(
new PutDocumentsRequest.Builder().addGenericDocuments(email2).build()));
}
@Test
public void testGetDocuments() throws Exception {
// Schema registration
mDb1.setSchemaAsync(
new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
.get();
// Index a document
AppSearchEmail inEmail =
new AppSearchEmail.Builder("namespace", "id1")
.setFrom("from@example.com")
.setTo("to1@example.com", "to2@example.com")
.setSubject("testPut example")
.setBody("This is the body of the testPut email")
.build();
checkIsBatchResultSuccess(
mDb1.putAsync(
new PutDocumentsRequest.Builder().addGenericDocuments(inEmail).build()));
// Get the document
List<GenericDocument> outDocuments = doGet(mDb1, "namespace", "id1");
assertThat(outDocuments).hasSize(1);
AppSearchEmail outEmail = new AppSearchEmail(outDocuments.get(0));
assertThat(outEmail).isEqualTo(inEmail);
// Can't get the document in the other instance.
AppSearchBatchResult<String, GenericDocument> failResult =
mDb2.getByDocumentIdAsync(
new GetByDocumentIdRequest.Builder("namespace")
.addIds("id1")
.build())
.get();
assertThat(failResult.isSuccess()).isFalse();
assertThat(failResult.getFailures().get("id1").getResultCode())
.isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
}
@Test
public void testGetDocuments_projection() throws Exception {
// Schema registration
mDb1.setSchemaAsync(
new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
.get();
// Index two documents
AppSearchEmail email1 =
new AppSearchEmail.Builder("namespace", "id1")
.setCreationTimestampMillis(1000)
.setFrom("from@example.com")
.setTo("to1@example.com", "to2@example.com")
.setSubject("testPut example")
.setBody("This is the body of the testPut email")
.build();
AppSearchEmail email2 =
new AppSearchEmail.Builder("namespace", "id2")
.setCreationTimestampMillis(1000)
.setFrom("from@example.com")
.setTo("to1@example.com", "to2@example.com")
.setSubject("testPut example")
.setBody("This is the body of the testPut email")
.build();
checkIsBatchResultSuccess(
mDb1.putAsync(
new PutDocumentsRequest.Builder()
.addGenericDocuments(email1, email2)
.build()));
// Get with type property paths {"Email", ["subject", "to"]}
GetByDocumentIdRequest request =
new GetByDocumentIdRequest.Builder("namespace")
.addIds("id1", "id2")
.addProjection(
AppSearchEmail.SCHEMA_TYPE, ImmutableList.of("subject", "to"))
.build();
List<GenericDocument> outDocuments = doGet(mDb1, request);
// The two email documents should have been returned with only the "subject" and "to"
// properties.
AppSearchEmail expected1 =
new AppSearchEmail.Builder("namespace", "id2")
.setCreationTimestampMillis(1000)
.setTo("to1@example.com", "to2@example.com")
.setSubject("testPut example")
.build();
AppSearchEmail expected2 =
new AppSearchEmail.Builder("namespace", "id1")
.setCreationTimestampMillis(1000)
.setTo("to1@example.com", "to2@example.com")
.setSubject("testPut example")
.build();
assertThat(outDocuments).containsExactly(expected1, expected2);
}
@Test
public void testGetDocuments_projectionEmpty() throws Exception {
// Schema registration
mDb1.setSchemaAsync(
new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
.get();
// Index two documents
AppSearchEmail email1 =
new AppSearchEmail.Builder("namespace", "id1")
.setCreationTimestampMillis(1000)
.setFrom("from@example.com")
.setTo("to1@example.com", "to2@example.com")
.setSubject("testPut example")
.setBody("This is the body of the testPut email")
.build();
AppSearchEmail email2 =
new AppSearchEmail.Builder("namespace", "id2")
.setCreationTimestampMillis(1000)
.setFrom("from@example.com")
.setTo("to1@example.com", "to2@example.com")
.setSubject("testPut example")
.setBody("This is the body of the testPut email")
.build();
checkIsBatchResultSuccess(
mDb1.putAsync(
new PutDocumentsRequest.Builder()
.addGenericDocuments(email1, email2)
.build()));
// Get with type property paths {"Email", ["subject", "to"]}
GetByDocumentIdRequest request =
new GetByDocumentIdRequest.Builder("namespace")
.addIds("id1", "id2")
.addProjection(AppSearchEmail.SCHEMA_TYPE, Collections.emptyList())
.build();
List<GenericDocument> outDocuments = doGet(mDb1, request);
// The two email documents should have been returned without any properties.
AppSearchEmail expected1 =
new AppSearchEmail.Builder("namespace", "id2")
.setCreationTimestampMillis(1000)
.build();
AppSearchEmail expected2 =
new AppSearchEmail.Builder("namespace", "id1")
.setCreationTimestampMillis(1000)
.build();
assertThat(outDocuments).containsExactly(expected1, expected2);
}
@Test
public void testGetDocuments_projectionNonExistentType() throws Exception {
// Schema registration
mDb1.setSchemaAsync(
new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
.get();
// Index two documents
AppSearchEmail email1 =
new AppSearchEmail.Builder("namespace", "id1")
.setCreationTimestampMillis(1000)
.setFrom("from@example.com")
.setTo("to1@example.com", "to2@example.com")
.setSubject("testPut example")
.setBody("This is the body of the testPut email")
.build();
AppSearchEmail email2 =
new AppSearchEmail.Builder("namespace", "id2")
.setCreationTimestampMillis(1000)
.setFrom("from@example.com")
.setTo("to1@example.com", "to2@example.com")
.setSubject("testPut example")
.setBody("This is the body of the testPut email")
.build();
checkIsBatchResultSuccess(
mDb1.putAsync(
new PutDocumentsRequest.Builder()
.addGenericDocuments(email1, email2)
.build()));
// Get with type property paths {"Email", ["subject", "to"]}
GetByDocumentIdRequest request =
new GetByDocumentIdRequest.Builder("namespace")
.addIds("id1", "id2")
.addProjection("NonExistentType", Collections.emptyList())
.addProjection(
AppSearchEmail.SCHEMA_TYPE, ImmutableList.of("subject", "to"))
.build();
List<GenericDocument> outDocuments = doGet(mDb1, request);
// The two email documents should have been returned with only the "subject" and "to"
// properties.
AppSearchEmail expected1 =
new AppSearchEmail.Builder("namespace", "id2")
.setCreationTimestampMillis(1000)
.setTo("to1@example.com", "to2@example.com")
.setSubject("testPut example")
.build();
AppSearchEmail expected2 =
new AppSearchEmail.Builder("namespace", "id1")
.setCreationTimestampMillis(1000)
.setTo("to1@example.com", "to2@example.com")
.setSubject("testPut example")
.build();
assertThat(outDocuments).containsExactly(expected1, expected2);
}
@Test
public void testGetDocuments_wildcardProjection() throws Exception {
// Schema registration
mDb1.setSchemaAsync(
new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
.get();
// Index two documents
AppSearchEmail email1 =
new AppSearchEmail.Builder("namespace", "id1")
.setCreationTimestampMillis(1000)
.setFrom("from@example.com")
.setTo("to1@example.com", "to2@example.com")
.setSubject("testPut example")
.setBody("This is the body of the testPut email")
.build();
AppSearchEmail email2 =
new AppSearchEmail.Builder("namespace", "id2")
.setCreationTimestampMillis(1000)
.setFrom("from@example.com")
.setTo("to1@example.com", "to2@example.com")
.setSubject("testPut example")
.setBody("This is the body of the testPut email")
.build();
checkIsBatchResultSuccess(
mDb1.putAsync(
new PutDocumentsRequest.Builder()
.addGenericDocuments(email1, email2)
.build()));
// Get with type property paths {"Email", ["subject", "to"]}
GetByDocumentIdRequest request =
new GetByDocumentIdRequest.Builder("namespace")
.addIds("id1", "id2")
.addProjection(
GetByDocumentIdRequest.PROJECTION_SCHEMA_TYPE_WILDCARD,
ImmutableList.of("subject", "to"))
.build();
List<GenericDocument> outDocuments = doGet(mDb1, request);
// The two email documents should have been returned with only the "subject" and "to"
// properties.
AppSearchEmail expected1 =
new AppSearchEmail.Builder("namespace", "id2")
.setCreationTimestampMillis(1000)
.setTo("to1@example.com", "to2@example.com")
.setSubject("testPut example")
.build();
AppSearchEmail expected2 =
new AppSearchEmail.Builder("namespace", "id1")
.setCreationTimestampMillis(1000)
.setTo("to1@example.com", "to2@example.com")
.setSubject("testPut example")
.build();
assertThat(outDocuments).containsExactly(expected1, expected2);
}
@Test
public void testGetDocuments_wildcardProjectionEmpty() throws Exception {
// Schema registration
mDb1.setSchemaAsync(
new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
.get();
// Index two documents
AppSearchEmail email1 =
new AppSearchEmail.Builder("namespace", "id1")
.setCreationTimestampMillis(1000)
.setFrom("from@example.com")
.setTo("to1@example.com", "to2@example.com")
.setSubject("testPut example")
.setBody("This is the body of the testPut email")
.build();
AppSearchEmail email2 =
new AppSearchEmail.Builder("namespace", "id2")
.setCreationTimestampMillis(1000)
.setFrom("from@example.com")
.setTo("to1@example.com", "to2@example.com")
.setSubject("testPut example")
.setBody("This is the body of the testPut email")
.build();
checkIsBatchResultSuccess(
mDb1.putAsync(
new PutDocumentsRequest.Builder()
.addGenericDocuments(email1, email2)
.build()));
// Get with type property paths {"Email", ["subject", "to"]}
GetByDocumentIdRequest request =
new GetByDocumentIdRequest.Builder("namespace")
.addIds("id1", "id2")
.addProjection(
GetByDocumentIdRequest.PROJECTION_SCHEMA_TYPE_WILDCARD,
Collections.emptyList())
.build();
List<GenericDocument> outDocuments = doGet(mDb1, request);
// The two email documents should have been returned without any properties.
AppSearchEmail expected1 =
new AppSearchEmail.Builder("namespace", "id2")
.setCreationTimestampMillis(1000)
.build();
AppSearchEmail expected2 =
new AppSearchEmail.Builder("namespace", "id1")
.setCreationTimestampMillis(1000)
.build();
assertThat(outDocuments).containsExactly(expected1, expected2);
}
@Test
public void testGetDocuments_wildcardProjectionNonExistentType() throws Exception {
// Schema registration
mDb1.setSchemaAsync(
new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
.get();
// Index two documents
AppSearchEmail email1 =
new AppSearchEmail.Builder("namespace", "id1")
.setCreationTimestampMillis(1000)
.setFrom("from@example.com")
.setTo("to1@example.com", "to2@example.com")
.setSubject("testPut example")
.setBody("This is the body of the testPut email")
.build();
AppSearchEmail email2 =
new AppSearchEmail.Builder("namespace", "id2")
.setCreationTimestampMillis(1000)
.setFrom("from@example.com")
.setTo("to1@example.com", "to2@example.com")
.setSubject("testPut example")
.setBody("This is the body of the testPut email")
.build();
checkIsBatchResultSuccess(
mDb1.putAsync(
new PutDocumentsRequest.Builder()
.addGenericDocuments(email1, email2)
.build()));
// Get with type property paths {"Email", ["subject", "to"]}
GetByDocumentIdRequest request =
new GetByDocumentIdRequest.Builder("namespace")
.addIds("id1", "id2")
.addProjection("NonExistentType", Collections.emptyList())
.addProjection(
GetByDocumentIdRequest.PROJECTION_SCHEMA_TYPE_WILDCARD,
ImmutableList.of("subject", "to"))
.build();
List<GenericDocument> outDocuments = doGet(mDb1, request);
// The two email documents should have been returned with only the "subject" and "to"
// properties.
AppSearchEmail expected1 =
new AppSearchEmail.Builder("namespace", "id2")
.setCreationTimestampMillis(1000)
.setTo("to1@example.com", "to2@example.com")
.setSubject("testPut example")
.build();
AppSearchEmail expected2 =
new AppSearchEmail.Builder("namespace", "id1")
.setCreationTimestampMillis(1000)
.setTo("to1@example.com", "to2@example.com")
.setSubject("testPut example")
.build();
assertThat(outDocuments).containsExactly(expected1, expected2);
}
@Test
public void testQuery() throws Exception {
// Schema registration
mDb1.setSchemaAsync(
new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
.get();
// Index a document
AppSearchEmail inEmail =
new AppSearchEmail.Builder("namespace", "id1")
.setFrom("from@example.com")
.setTo("to1@example.com", "to2@example.com")
.setSubject("testPut example")
.setBody("This is the body of the testPut email")
.build();
checkIsBatchResultSuccess(
mDb1.putAsync(
new PutDocumentsRequest.Builder().addGenericDocuments(inEmail).build()));
// Query for the document
SearchResultsShim searchResults =
mDb1.search(
"body",
new SearchSpec.Builder()
.setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
.build());
List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
assertThat(documents).hasSize(1);
assertThat(documents.get(0)).isEqualTo(inEmail);
// Multi-term query
searchResults =
mDb1.search(
"body email",
new SearchSpec.Builder()
.setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
.build());
documents = convertSearchResultsToDocuments(searchResults);
assertThat(documents).hasSize(1);
assertThat(documents.get(0)).isEqualTo(inEmail);
}
@Test
public void testQuery_getNextPage() throws Exception {
// Schema registration
mDb1.setSchemaAsync(
new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
.get();
Set<AppSearchEmail> emailSet = new HashSet<>();
PutDocumentsRequest.Builder putDocumentsRequestBuilder = new PutDocumentsRequest.Builder();
// Index 31 documents
for (int i = 0; i < 31; i++) {
AppSearchEmail inEmail =
new AppSearchEmail.Builder("namespace", "id" + i)
.setFrom("from@example.com")
.setTo("to1@example.com", "to2@example.com")
.setSubject("testPut example")
.setBody("This is the body of the testPut email")
.build();
emailSet.add(inEmail);
putDocumentsRequestBuilder.addGenericDocuments(inEmail);
}
checkIsBatchResultSuccess(mDb1.putAsync(putDocumentsRequestBuilder.build()));
// Set number of results per page is 7.
SearchResultsShim searchResults =
mDb1.search(
"body",
new SearchSpec.Builder()
.setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
.setResultCountPerPage(7)
.build());
List<GenericDocument> documents = new ArrayList<>();
int pageNumber = 0;
List<SearchResult> results;
// keep loading next page until it's empty.
do {
results = searchResults.getNextPageAsync().get();
++pageNumber;
for (SearchResult result : results) {
documents.add(result.getGenericDocument());
}
} while (results.size() > 0);
// check all document presents
assertThat(documents).containsExactlyElementsIn(emailSet);
assertThat(pageNumber).isEqualTo(6); // 5 (upper(31/7)) + 1 (final empty page)
}
@Test
public void testQuery_relevanceScoring() throws Exception {
// Schema registration
mDb1.setSchemaAsync(
new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
.get();
// Index two documents
AppSearchEmail email1 =
new AppSearchEmail.Builder("namespace", "id1")
.setCreationTimestampMillis(1000)
.setFrom("from@example.com")
.setTo("to1@example.com", "to2@example.com")
.setSubject("Mary had a little lamb")
.setBody("A little lamb, little lamb")
.build();
AppSearchEmail email2 =
new AppSearchEmail.Builder("namespace", "id2")
.setCreationTimestampMillis(1000)
.setFrom("from@example.com")
.setTo("to1@example.com", "to2@example.com")
.setSubject("I'm a little teapot")
.setBody("short and stout. Here is my handle, here is my spout.")
.build();
checkIsBatchResultSuccess(
mDb1.putAsync(
new PutDocumentsRequest.Builder()
.addGenericDocuments(email1, email2)
.build()));
// Query for "little". It should match both emails.
SearchResultsShim searchResults =
mDb1.search(
"little",
new SearchSpec.Builder()
.setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
.setRankingStrategy(SearchSpec.RANKING_STRATEGY_RELEVANCE_SCORE)
.build());
List<SearchResult> results = retrieveAllSearchResults(searchResults);
// The email1 should be ranked higher because 'little' appears three times in email1 and
// only once in email2.
assertThat(results).hasSize(2);
assertThat(results.get(0).getGenericDocument()).isEqualTo(email1);
assertThat(results.get(0).getRankingSignal())
.isGreaterThan(results.get(1).getRankingSignal());
assertThat(results.get(1).getGenericDocument()).isEqualTo(email2);
assertThat(results.get(1).getRankingSignal()).isGreaterThan(0);
// Query for "little OR stout". It should match both emails.
searchResults =
mDb1.search(
"little OR stout",
new SearchSpec.Builder()
.setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
.setRankingStrategy(SearchSpec.RANKING_STRATEGY_RELEVANCE_SCORE)
.build());
results = retrieveAllSearchResults(searchResults);
// The email2 should be ranked higher because 'little' appears once and "stout", which is a
// rarer term, appears once. email1 only has the three 'little' appearances.
assertThat(results).hasSize(2);
assertThat(results.get(0).getGenericDocument()).isEqualTo(email2);
assertThat(results.get(0).getRankingSignal())
.isGreaterThan(results.get(1).getRankingSignal());
assertThat(results.get(1).getGenericDocument()).isEqualTo(email1);
assertThat(results.get(1).getRankingSignal()).isGreaterThan(0);
}
@Test
public void testQuery_typeFilter() throws Exception {
// Schema registration
AppSearchSchema genericSchema =
new AppSearchSchema.Builder("Generic")
.addProperty(
new StringPropertyConfig.Builder("foo")
.setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
.setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
.setIndexingType(
StringPropertyConfig.INDEXING_TYPE_PREFIXES)
.build())
.build();
mDb1.setSchemaAsync(
new SetSchemaRequest.Builder()
.addSchemas(AppSearchEmail.SCHEMA)
.addSchemas(genericSchema)
.build())
.get();
// Index a document
AppSearchEmail inEmail =
new AppSearchEmail.Builder("namespace", "id1")
.setFrom("from@example.com")
.setTo("to1@example.com", "to2@example.com")
.setSubject("testPut example")
.setBody("This is the body of the testPut email")
.build();
GenericDocument inDoc =
new GenericDocument.Builder<>("namespace", "id2", "Generic")
.setPropertyString("foo", "body")
.build();
checkIsBatchResultSuccess(
mDb1.putAsync(
new PutDocumentsRequest.Builder()
.addGenericDocuments(inEmail, inDoc)
.build()));
// Query for the documents
SearchResultsShim searchResults =
mDb1.search(
"body",
new SearchSpec.Builder()
.setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
.build());
List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
assertThat(documents).hasSize(2);
assertThat(documents).containsExactly(inEmail, inDoc);
// Query only for Document
searchResults =
mDb1.search(
"body",
new SearchSpec.Builder()
.addFilterSchemas(
"Generic",
"Generic") // duplicate type in filter won't matter.
.setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
.build());
documents = convertSearchResultsToDocuments(searchResults);
assertThat(documents).hasSize(1);
assertThat(documents).containsExactly(inDoc);
// Query only for non-exist type
searchResults =
mDb1.search(
"body",
new SearchSpec.Builder()
.addFilterSchemas("nonExistType")
.setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
.build());
documents = convertSearchResultsToDocuments(searchResults);
assertThat(documents).isEmpty();
}
@Test
public void testQuery_packageFilter() throws Exception {
// Schema registration
mDb1.setSchemaAsync(
new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
.get();
// Index documents
AppSearchEmail email =
new AppSearchEmail.Builder("namespace", "id1")
.setFrom("from@example.com")
.setTo("to1@example.com", "to2@example.com")
.setSubject("foo")
.setBody("This is the body of the testPut email")
.build();
checkIsBatchResultSuccess(
mDb1.putAsync(
new PutDocumentsRequest.Builder().addGenericDocuments(email).build()));
// Query for the document within our package
SearchResultsShim searchResults =
mDb1.search(
"foo",
new SearchSpec.Builder()
.setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
.addFilterPackageNames(
ApplicationProvider.getApplicationContext()
.getPackageName())
.build());
List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
assertThat(documents).containsExactly(email);
// Query for the document in some other package, which won't exist
searchResults =
mDb1.search(
"foo",
new SearchSpec.Builder()
.setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
.addFilterPackageNames("some.other.package")
.build());
List<SearchResult> results = searchResults.getNextPageAsync().get();
assertThat(results).isEmpty();
}
@Test
public void testQuery_namespaceFilter() throws Exception {
// Schema registration
mDb1.setSchemaAsync(
new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
.get();
// Index two documents
AppSearchEmail expectedEmail =
new AppSearchEmail.Builder("expectedNamespace", "id1")
.setFrom("from@example.com")
.setTo("to1@example.com", "to2@example.com")
.setSubject("testPut example")
.setBody("This is the body of the testPut email")
.build();
AppSearchEmail unexpectedEmail =
new AppSearchEmail.Builder("unexpectedNamespace", "id1")
.setFrom("from@example.com")
.setTo("to1@example.com", "to2@example.com")
.setSubject("testPut example")
.setBody("This is the body of the testPut email")
.build();
checkIsBatchResultSuccess(
mDb1.putAsync(
new PutDocumentsRequest.Builder()
.addGenericDocuments(expectedEmail, unexpectedEmail)
.build()));
// Query for all namespaces
SearchResultsShim searchResults =
mDb1.search(
"body",
new SearchSpec.Builder()
.setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
.build());
List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
assertThat(documents).hasSize(2);
assertThat(documents).containsExactly(expectedEmail, unexpectedEmail);
// Query only for expectedNamespace
searchResults =
mDb1.search(
"body",
new SearchSpec.Builder()
.addFilterNamespaces("expectedNamespace")
.setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
.build());
documents = convertSearchResultsToDocuments(searchResults);
assertThat(documents).hasSize(1);
assertThat(documents).containsExactly(expectedEmail);
// Query only for non-exist namespace
searchResults =
mDb1.search(
"body",
new SearchSpec.Builder()
.addFilterNamespaces("nonExistNamespace")
.setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
.build());
documents = convertSearchResultsToDocuments(searchResults);
assertThat(documents).isEmpty();
}
@Test
public void testQuery_getPackageName() throws Exception {
// Schema registration
mDb1.setSchemaAsync(
new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
.get();
// Index a document
AppSearchEmail inEmail =
new AppSearchEmail.Builder("namespace", "id1")
.setFrom("from@example.com")
.setTo("to1@example.com", "to2@example.com")
.setSubject("testPut example")
.setBody("This is the body of the testPut email")
.build();
checkIsBatchResultSuccess(
mDb1.putAsync(
new PutDocumentsRequest.Builder().addGenericDocuments(inEmail).build()));
// Query for the document
SearchResultsShim searchResults =
mDb1.search(
"body",
new SearchSpec.Builder()
.setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
.build());
List<SearchResult> results;
List<GenericDocument> documents = new ArrayList<>();
// keep loading next page until it's empty.
do {
results = searchResults.getNextPageAsync().get();
for (SearchResult result : results) {
assertThat(result.getGenericDocument()).isEqualTo(inEmail);
assertThat(result.getPackageName())
.isEqualTo(ApplicationProvider.getApplicationContext().getPackageName());
documents.add(result.getGenericDocument());
}
} while (results.size() > 0);
assertThat(documents).hasSize(1);
}
@Test
public void testQuery_getDatabaseName() throws Exception {
// Schema registration
mDb1.setSchemaAsync(
new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
.get();
// Index a document
AppSearchEmail inEmail =
new AppSearchEmail.Builder("namespace", "id1")
.setFrom("from@example.com")
.setTo("to1@example.com", "to2@example.com")
.setSubject("testPut example")
.setBody("This is the body of the testPut email")
.build();
checkIsBatchResultSuccess(
mDb1.putAsync(
new PutDocumentsRequest.Builder().addGenericDocuments(inEmail).build()));
// Query for the document
SearchResultsShim searchResults =
mDb1.search(
"body",
new SearchSpec.Builder()
.setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
.build());
List<SearchResult> results;
List<GenericDocument> documents = new ArrayList<>();
// keep loading next page until it's empty.
do {
results = searchResults.getNextPageAsync().get();
for (SearchResult result : results) {
assertThat(result.getGenericDocument()).isEqualTo(inEmail);
assertThat(result.getDatabaseName()).isEqualTo(DB_NAME_1);
documents.add(result.getGenericDocument());
}
} while (results.size() > 0);
assertThat(documents).hasSize(1);
// Schema registration for another database
mDb2.setSchemaAsync(
new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
.get();
checkIsBatchResultSuccess(
mDb2.putAsync(
new PutDocumentsRequest.Builder().addGenericDocuments(inEmail).build()));
// Query for the document
searchResults =
mDb2.search(
"body",
new SearchSpec.Builder()
.setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
.build());
documents = new ArrayList<>();
// keep loading next page until it's empty.
do {
results = searchResults.getNextPageAsync().get();
for (SearchResult result : results) {
assertThat(result.getGenericDocument()).isEqualTo(inEmail);
assertThat(result.getDatabaseName()).isEqualTo(DB_NAME_2);
documents.add(result.getGenericDocument());
}
} while (results.size() > 0);
assertThat(documents).hasSize(1);
}
@Test
public void testQuery_projection() throws Exception {
// Schema registration
mDb1.setSchemaAsync(
new SetSchemaRequest.Builder()
.addSchemas(AppSearchEmail.SCHEMA)
.addSchemas(
new AppSearchSchema.Builder("Note")
.addProperty(
new StringPropertyConfig.Builder("title")
.setCardinality(
PropertyConfig
.CARDINALITY_REQUIRED)
.setIndexingType(
StringPropertyConfig
.INDEXING_TYPE_EXACT_TERMS)
.setTokenizerType(
StringPropertyConfig
.TOKENIZER_TYPE_PLAIN)
.build())
.addProperty(
new StringPropertyConfig.Builder("body")
.setCardinality(
PropertyConfig
.CARDINALITY_REQUIRED)
.setIndexingType(
StringPropertyConfig
.INDEXING_TYPE_EXACT_TERMS)
.setTokenizerType(
StringPropertyConfig
.TOKENIZER_TYPE_PLAIN)
.build())
.build())
.build())
.get();
// Index two documents
AppSearchEmail email =
new AppSearchEmail.Builder("namespace", "id1")
.setCreationTimestampMillis(1000)
.setFrom("from@example.com")
.setTo("to1@example.com", "to2@example.com")
.setSubject("testPut example")
.setBody("This is the body of the testPut email")
.build();
GenericDocument note =
new GenericDocument.Builder<>("namespace", "id2", "Note")
.setCreationTimestampMillis(1000)
.setPropertyString("title", "Note title")
.setPropertyString("body", "Note body")
.build();
checkIsBatchResultSuccess(
mDb1.putAsync(
new PutDocumentsRequest.Builder()
.addGenericDocuments(email, note)
.build()));
// Query with type property paths {"Email", ["body", "to"]}
SearchResultsShim searchResults =
mDb1.search(
"body",
new SearchSpec.Builder()
.setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
.addProjection(
AppSearchEmail.SCHEMA_TYPE, ImmutableList.of("body", "to"))
.build());
List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
// The email document should have been returned with only the "body" and "to"
// properties. The note document should have been returned with all of its properties.
AppSearchEmail expectedEmail =
new AppSearchEmail.Builder("namespace", "id1")
.setCreationTimestampMillis(1000)
.setTo("to1@example.com", "to2@example.com")
.setBody("This is the body of the testPut email")
.build();
GenericDocument expectedNote =
new GenericDocument.Builder<>("namespace", "id2", "Note")
.setCreationTimestampMillis(1000)
.setPropertyString("title", "Note title")
.setPropertyString("body", "Note body")
.build();
assertThat(documents).containsExactly(expectedNote, expectedEmail);
}
@Test
public void testQuery_projectionEmpty() throws Exception {
// Schema registration
mDb1.setSchemaAsync(
new SetSchemaRequest.Builder()
.addSchemas(AppSearchEmail.SCHEMA)
.addSchemas(
new AppSearchSchema.Builder("Note")
.addProperty(
new StringPropertyConfig.Builder("title")
.setCardinality(
PropertyConfig
.CARDINALITY_REQUIRED)
.setIndexingType(
StringPropertyConfig
.INDEXING_TYPE_EXACT_TERMS)
.setTokenizerType(
StringPropertyConfig
.TOKENIZER_TYPE_PLAIN)
.build())
.addProperty(
new StringPropertyConfig.Builder("body")
.setCardinality(
PropertyConfig
.CARDINALITY_REQUIRED)
.setIndexingType(
StringPropertyConfig
.INDEXING_TYPE_EXACT_TERMS)
.setTokenizerType(
StringPropertyConfig
.TOKENIZER_TYPE_PLAIN)
.build())
.build())
.build())
.get();
// Index two documents
AppSearchEmail email =
new AppSearchEmail.Builder("namespace", "id1")
.setCreationTimestampMillis(1000)
.setFrom("from@example.com")
.setTo("to1@example.com", "to2@example.com")
.setSubject("testPut example")
.setBody("This is the body of the testPut email")
.build();
GenericDocument note =
new GenericDocument.Builder<>("namespace", "id2", "Note")
.setCreationTimestampMillis(1000)
.setPropertyString("title", "Note title")
.setPropertyString("body", "Note body")
.build();
checkIsBatchResultSuccess(
mDb1.putAsync(
new PutDocumentsRequest.Builder()
.addGenericDocuments(email, note)
.build()));
// Query with type property paths {"Email", []}
SearchResultsShim searchResults =
mDb1.search(
"body",
new SearchSpec.Builder()
.setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
.addProjection(AppSearchEmail.SCHEMA_TYPE, Collections.emptyList())
.build());
List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
// The email document should have been returned without any properties. The note document
// should have been returned with all of its properties.
AppSearchEmail expectedEmail =
new AppSearchEmail.Builder("namespace", "id1")
.setCreationTimestampMillis(1000)
.build();
GenericDocument expectedNote =
new GenericDocument.Builder<>("namespace", "id2", "Note")
.setCreationTimestampMillis(1000)
.setPropertyString("title", "Note title")
.setPropertyString("body", "Note body")
.build();
assertThat(documents).containsExactly(expectedNote, expectedEmail);
}
@Test
public void testQuery_projectionNonExistentType() throws Exception {
// Schema registration
mDb1.setSchemaAsync(
new SetSchemaRequest.Builder()
.addSchemas(AppSearchEmail.SCHEMA)
.addSchemas(
new AppSearchSchema.Builder("Note")
.addProperty(
new StringPropertyConfig.Builder("title")
.setCardinality(
PropertyConfig
.CARDINALITY_REQUIRED)
.setIndexingType(
StringPropertyConfig
.INDEXING_TYPE_EXACT_TERMS)
.setTokenizerType(
StringPropertyConfig
.TOKENIZER_TYPE_PLAIN)
.build())
.addProperty(
new StringPropertyConfig.Builder("body")
.setCardinality(
PropertyConfig
.CARDINALITY_REQUIRED)
.setIndexingType(
StringPropertyConfig
.INDEXING_TYPE_EXACT_TERMS)
.setTokenizerType(
StringPropertyConfig
.TOKENIZER_TYPE_PLAIN)
.build())
.build())
.build())
.get();
// Index two documents
AppSearchEmail email =
new AppSearchEmail.Builder("namespace", "id1")
.setCreationTimestampMillis(1000)
.setFrom("from@example.com")
.setTo("to1@example.com", "to2@example.com")
.setSubject("testPut example")
.setBody("This is the body of the testPut email")
.build();
GenericDocument note =
new GenericDocument.Builder<>("namespace", "id2", "Note")
.setCreationTimestampMillis(1000)
.setPropertyString("title", "Note title")
.setPropertyString("body", "Note body")
.build();
checkIsBatchResultSuccess(
mDb1.putAsync(
new PutDocumentsRequest.Builder()
.addGenericDocuments(email, note)
.build()));
// Query with type property paths {"NonExistentType", []}, {"Email", ["body", "to"]}
SearchResultsShim searchResults =
mDb1.search(
"body",
new SearchSpec.Builder()
.setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
.addProjection("NonExistentType", Collections.emptyList())
.addProjection(
AppSearchEmail.SCHEMA_TYPE, ImmutableList.of("body", "to"))
.build());
List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
// The email document should have been returned with only the "body" and "to" properties.
// The note document should have been returned with all of its properties.
AppSearchEmail expectedEmail =
new AppSearchEmail.Builder("namespace", "id1")
.setCreationTimestampMillis(1000)
.setTo("to1@example.com", "to2@example.com")
.setBody("This is the body of the testPut email")
.build();
GenericDocument expectedNote =
new GenericDocument.Builder<>("namespace", "id2", "Note")
.setCreationTimestampMillis(1000)
.setPropertyString("title", "Note title")
.setPropertyString("body", "Note body")
.build();
assertThat(documents).containsExactly(expectedNote, expectedEmail);
}
@Test
public void testQuery_wildcardProjection() throws Exception {
// Schema registration
mDb1.setSchemaAsync(
new SetSchemaRequest.Builder()
.addSchemas(AppSearchEmail.SCHEMA)
.addSchemas(
new AppSearchSchema.Builder("Note")
.addProperty(
new StringPropertyConfig.Builder("title")
.setCardinality(
PropertyConfig
.CARDINALITY_REQUIRED)
.setIndexingType(
StringPropertyConfig
.INDEXING_TYPE_EXACT_TERMS)
.setTokenizerType(
StringPropertyConfig
.TOKENIZER_TYPE_PLAIN)
.build())
.addProperty(
new StringPropertyConfig.Builder("body")
.setCardinality(
PropertyConfig
.CARDINALITY_REQUIRED)
.setIndexingType(
StringPropertyConfig
.INDEXING_TYPE_EXACT_TERMS)
.setTokenizerType(
StringPropertyConfig
.TOKENIZER_TYPE_PLAIN)
.build())
.build())
.build())
.get();
// Index two documents
AppSearchEmail email =
new AppSearchEmail.Builder("namespace", "id1")
.setCreationTimestampMillis(1000)
.setFrom("from@example.com")
.setTo("to1@example.com", "to2@example.com")
.setSubject("testPut example")
.setBody("This is the body of the testPut email")
.build();
GenericDocument note =
new GenericDocument.Builder<>("namespace", "id2", "Note")
.setCreationTimestampMillis(1000)
.setPropertyString("title", "Note title")
.setPropertyString("body", "Note body")
.build();
checkIsBatchResultSuccess(
mDb1.putAsync(
new PutDocumentsRequest.Builder()
.addGenericDocuments(email, note)
.build()));
// Query with type property paths {"*", ["body", "to"]}
SearchResultsShim searchResults =
mDb1.search(
"body",
new SearchSpec.Builder()
.setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
.addProjection(
SearchSpec.PROJECTION_SCHEMA_TYPE_WILDCARD,
ImmutableList.of("body", "to"))
.build());
List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
// The email document should have been returned with only the "body" and "to"
// properties. The note document should have been returned with only the "body" property.
AppSearchEmail expectedEmail =
new AppSearchEmail.Builder("namespace", "id1")
.setCreationTimestampMillis(1000)
.setTo("to1@example.com", "to2@example.com")
.setBody("This is the body of the testPut email")
.build();
GenericDocument expectedNote =
new GenericDocument.Builder<>("namespace", "id2", "Note")
.setCreationTimestampMillis(1000)
.setPropertyString("body", "Note body")
.build();
assertThat(documents).containsExactly(expectedNote, expectedEmail);
}
@Test
public void testQuery_wildcardProjectionEmpty() throws Exception {
// Schema registration
mDb1.setSchemaAsync(
new SetSchemaRequest.Builder()
.addSchemas(AppSearchEmail.SCHEMA)
.addSchemas(
new AppSearchSchema.Builder("Note")
.addProperty(
new StringPropertyConfig.Builder("title")
.setCardinality(
PropertyConfig
.CARDINALITY_REQUIRED)
.setIndexingType(
StringPropertyConfig
.INDEXING_TYPE_EXACT_TERMS)
.setTokenizerType(
StringPropertyConfig
.TOKENIZER_TYPE_PLAIN)
.build())
.addProperty(
new StringPropertyConfig.Builder("body")
.setCardinality(
PropertyConfig
.CARDINALITY_REQUIRED)
.setIndexingType(
StringPropertyConfig
.INDEXING_TYPE_EXACT_TERMS)
.setTokenizerType(
StringPropertyConfig
.TOKENIZER_TYPE_PLAIN)
.build())
.build())
.build())
.get();
// Index two documents
AppSearchEmail email =
new AppSearchEmail.Builder("namespace", "id1")
.setCreationTimestampMillis(1000)
.setFrom("from@example.com")
.setTo("to1@example.com", "to2@example.com")
.setSubject("testPut example")
.setBody("This is the body of the testPut email")
.build();
GenericDocument note =
new GenericDocument.Builder<>("namespace", "id2", "Note")
.setCreationTimestampMillis(1000)
.setPropertyString("title", "Note title")
.setPropertyString("body", "Note body")
.build();
checkIsBatchResultSuccess(
mDb1.putAsync(
new PutDocumentsRequest.Builder()
.addGenericDocuments(email, note)
.build()));
// Query with type property paths {"*", []}
SearchResultsShim searchResults =
mDb1.search(
"body",
new SearchSpec.Builder()
.setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
.addProjection(
SearchSpec.PROJECTION_SCHEMA_TYPE_WILDCARD,
Collections.emptyList())
.build());
List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
// The email and note documents should have been returned without any properties.
AppSearchEmail expectedEmail =
new AppSearchEmail.Builder("namespace", "id1")
.setCreationTimestampMillis(1000)
.build();
GenericDocument expectedNote =
new GenericDocument.Builder<>("namespace", "id2", "Note")
.setCreationTimestampMillis(1000)
.build();
assertThat(documents).containsExactly(expectedNote, expectedEmail);
}
@Test
public void testQuery_wildcardProjectionNonExistentType() throws Exception {
// Schema registration
mDb1.setSchemaAsync(
new SetSchemaRequest.Builder()
.addSchemas(AppSearchEmail.SCHEMA)
.addSchemas(
new AppSearchSchema.Builder("Note")
.addProperty(
new StringPropertyConfig.Builder("title")
.setCardinality(
PropertyConfig
.CARDINALITY_REQUIRED)
.setIndexingType(
StringPropertyConfig
.INDEXING_TYPE_EXACT_TERMS)
.setTokenizerType(
StringPropertyConfig
.TOKENIZER_TYPE_PLAIN)
.build())
.addProperty(
new StringPropertyConfig.Builder("body")
.setCardinality(
PropertyConfig
.CARDINALITY_REQUIRED)
.setIndexingType(
StringPropertyConfig
.INDEXING_TYPE_EXACT_TERMS)
.setTokenizerType(
StringPropertyConfig
.TOKENIZER_TYPE_PLAIN)
.build())
.build())
.build())
.get();
// Index two documents
AppSearchEmail email =
new AppSearchEmail.Builder("namespace", "id1")
.setCreationTimestampMillis(1000)
.setFrom("from@example.com")
.setTo("to1@example.com", "to2@example.com")
.setSubject("testPut example")
.setBody("This is the body of the testPut email")
.build();
GenericDocument note =
new GenericDocument.Builder<>("namespace", "id2", "Note")
.setCreationTimestampMillis(1000)
.setPropertyString("title", "Note title")
.setPropertyString("body", "Note body")
.build();
checkIsBatchResultSuccess(
mDb1.putAsync(
new PutDocumentsRequest.Builder()
.addGenericDocuments(email, note)
.build()));
// Query with type property paths {"NonExistentType", []}, {"*", ["body", "to"]}
SearchResultsShim searchResults =
mDb1.search(
"body",
new SearchSpec.Builder()
.setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
.addProjection("NonExistentType", Collections.emptyList())
.addProjection(
SearchSpec.PROJECTION_SCHEMA_TYPE_WILDCARD,
ImmutableList.of("body", "to"))
.build());
List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
// The email document should have been returned with only the "body" and "to"
// properties. The note document should have been returned with only the "body" property.
AppSearchEmail expectedEmail =
new AppSearchEmail.Builder("namespace", "id1")
.setCreationTimestampMillis(1000)
.setTo("to1@example.com", "to2@example.com")
.setBody("This is the body of the testPut email")
.build();
GenericDocument expectedNote =
new GenericDocument.Builder<>("namespace", "id2", "Note")
.setCreationTimestampMillis(1000)
.setPropertyString("body", "Note body")
.build();
assertThat(documents).containsExactly(expectedNote, expectedEmail);
}
@Test
public void testQuery_twoInstances() throws Exception {
// Schema registration
mDb1.setSchemaAsync(
new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
.get();
mDb2.setSchemaAsync(
new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
.get();
// Index a document to instance 1.
AppSearchEmail inEmail1 =
new AppSearchEmail.Builder("namespace", "id1")
.setFrom("from@example.com")
.setTo("to1@example.com", "to2@example.com")
.setSubject("testPut example")
.setBody("This is the body of the testPut email")
.build();
checkIsBatchResultSuccess(
mDb1.putAsync(
new PutDocumentsRequest.Builder().addGenericDocuments(inEmail1).build()));
// Index a document to instance 2.
AppSearchEmail inEmail2 =
new AppSearchEmail.Builder("namespace", "id2")
.setFrom("from@example.com")
.setTo("to1@example.com", "to2@example.com")
.setSubject("testPut example")
.setBody("This is the body of the testPut email")
.build();
checkIsBatchResultSuccess(
mDb2.putAsync(
new PutDocumentsRequest.Builder().addGenericDocuments(inEmail2).build()));
// Query for instance 1.
SearchResultsShim searchResults =
mDb1.search(
"body",
new SearchSpec.Builder()
.setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
.build());
List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
assertThat(documents).hasSize(1);
assertThat(documents).containsExactly(inEmail1);
// Query for instance 2.
searchResults =
mDb2.search(
"body",
new SearchSpec.Builder()
.setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
.build());
documents = convertSearchResultsToDocuments(searchResults);
assertThat(documents).hasSize(1);
assertThat(documents).containsExactly(inEmail2);
}
@Test
public void testSnippet() throws Exception {
// Schema registration
AppSearchSchema genericSchema =
new AppSearchSchema.Builder("Generic")
.addProperty(
new StringPropertyConfig.Builder("subject")
.setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
.setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
.setIndexingType(
StringPropertyConfig.INDEXING_TYPE_PREFIXES)
.build())
.build();
mDb1.setSchemaAsync(new SetSchemaRequest.Builder().addSchemas(genericSchema).build()).get();
// Index a document
GenericDocument document =
new GenericDocument.Builder<>("namespace", "id", "Generic")
.setPropertyString(
"subject",
"A commonly used fake word is foo. "
+ "Another nonsense word that’s used a lot is bar")
.build();
checkIsBatchResultSuccess(
mDb1.putAsync(
new PutDocumentsRequest.Builder().addGenericDocuments(document).build()));
// Query for the document
SearchResultsShim searchResults =
mDb1.search(
"fo",
new SearchSpec.Builder()
.addFilterSchemas("Generic")
.setSnippetCount(1)
.setSnippetCountPerProperty(1)
.setMaxSnippetSize(10)
.setTermMatch(SearchSpec.TERM_MATCH_PREFIX)
.build());
List<SearchResult> results = searchResults.getNextPageAsync().get();
assertThat(results).hasSize(1);
List<SearchResult.MatchInfo> matchInfos = results.get(0).getMatchInfos();
assertThat(matchInfos).isNotNull();
assertThat(matchInfos).hasSize(1);
SearchResult.MatchInfo matchInfo = matchInfos.get(0);
assertThat(matchInfo.getFullText())
.isEqualTo(
"A commonly used fake word is foo. "
+ "Another nonsense word that’s used a lot is bar");
assertThat(matchInfo.getExactMatchRange())
.isEqualTo(new SearchResult.MatchRange(/*lower=*/ 29, /*upper=*/ 32));
assertThat(matchInfo.getExactMatch()).isEqualTo("foo");
assertThat(matchInfo.getSnippetRange())
.isEqualTo(new SearchResult.MatchRange(/*lower=*/ 26, /*upper=*/ 33));
assertThat(matchInfo.getSnippet()).isEqualTo("is foo.");
if (!mDb1.getFeatures().isFeatureSupported(Features.SEARCH_RESULT_MATCH_INFO_SUBMATCH)) {
assertThrows(UnsupportedOperationException.class, matchInfo::getSubmatchRange);
assertThrows(UnsupportedOperationException.class, matchInfo::getSubmatch);
} else {
assertThat(matchInfo.getSubmatchRange())
.isEqualTo(new SearchResult.MatchRange(/*lower=*/ 29, /*upper=*/ 31));
assertThat(matchInfo.getSubmatch()).isEqualTo("fo");
}
}
@Test
public void testSetSnippetCount() throws Exception {
// Schema registration
AppSearchSchema genericSchema =
new AppSearchSchema.Builder("Generic")
.addProperty(
new StringPropertyConfig.Builder("subject")
.setCardinality(PropertyConfig.CARDINALITY_REPEATED)
.setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
.setIndexingType(
StringPropertyConfig.INDEXING_TYPE_PREFIXES)
.build())
.build();
mDb1.setSchemaAsync(new SetSchemaRequest.Builder().addSchemas(genericSchema).build()).get();
// Index documents
checkIsBatchResultSuccess(
mDb1.putAsync(
new PutDocumentsRequest.Builder()
.addGenericDocuments(
new GenericDocument.Builder<>("namespace", "id1", "Generic")
.setPropertyString(
"subject",
"I like cats",
"I like dogs",
"I like birds",
"I like fish")
.setScore(10)
.build(),
new GenericDocument.Builder<>("namespace", "id2", "Generic")
.setPropertyString(
"subject",
"I like red",
"I like green",
"I like blue",
"I like yellow")
.setScore(20)
.build(),
new GenericDocument.Builder<>("namespace", "id3", "Generic")
.setPropertyString(
"subject",
"I like cupcakes",
"I like donuts",
"I like eclairs",
"I like froyo")
.setScore(5)
.build())
.build()));
// Query for the document
SearchResultsShim searchResults =
mDb1.search(
"like",
new SearchSpec.Builder()
.addFilterSchemas("Generic")
.setSnippetCount(2)
.setSnippetCountPerProperty(3)
.setMaxSnippetSize(11)
.setRankingStrategy(SearchSpec.RANKING_STRATEGY_DOCUMENT_SCORE)
.setTermMatch(SearchSpec.TERM_MATCH_PREFIX)
.build());
// Check result 1
List<SearchResult> results = searchResults.getNextPageAsync().get();
assertThat(results).hasSize(3);
assertThat(results.get(0).getGenericDocument().getId()).isEqualTo("id2");
List<SearchResult.MatchInfo> matchInfos = results.get(0).getMatchInfos();
assertThat(matchInfos).hasSize(3);
assertThat(matchInfos.get(0).getSnippet()).isEqualTo("I like red");
assertThat(matchInfos.get(1).getSnippet()).isEqualTo("I like");
assertThat(matchInfos.get(2).getSnippet()).isEqualTo("I like blue");
// Check result 2
assertThat(results.get(1).getGenericDocument().getId()).isEqualTo("id1");
matchInfos = results.get(1).getMatchInfos();
assertThat(matchInfos).hasSize(3);
assertThat(matchInfos.get(0).getSnippet()).isEqualTo("I like cats");
assertThat(matchInfos.get(1).getSnippet()).isEqualTo("I like dogs");
assertThat(matchInfos.get(2).getSnippet()).isEqualTo("I like");
// Check result 2
assertThat(results.get(2).getGenericDocument().getId()).isEqualTo("id3");
matchInfos = results.get(2).getMatchInfos();
assertThat(matchInfos).isEmpty();
}
@Test
public void testCJKSnippet() throws Exception {
// Schema registration
AppSearchSchema genericSchema =
new AppSearchSchema.Builder("Generic")
.addProperty(
new StringPropertyConfig.Builder("subject")
.setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
.setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
.setIndexingType(
StringPropertyConfig.INDEXING_TYPE_PREFIXES)
.build())
.build();
mDb1.setSchemaAsync(new SetSchemaRequest.Builder().addSchemas(genericSchema).build()).get();
String japanese =
"差し出されたのが今日ランドセルでした普通の子であれば満面の笑みで俺を言うでしょうしかし私は赤いランド"
+ "セルを見て笑うことができませんでしたどうしたのと心配そうな仕事ガラスながら渋い顔する私書いたこと言"
+ "うんじゃないのカードとなる声を聞きたい私は目から涙をこぼしながらおじいちゃんの近くにかけおり頭をポ"
+ "ンポンと叩きピンクが良かったんだもん";
// Index a document
GenericDocument document =
new GenericDocument.Builder<>("namespace", "id", "Generic")
.setPropertyString("subject", japanese)
.build();
checkIsBatchResultSuccess(
mDb1.putAsync(
new PutDocumentsRequest.Builder().addGenericDocuments(document).build()));
// Query for the document
SearchResultsShim searchResults =
mDb1.search(
"は",
new SearchSpec.Builder()
.addFilterSchemas("Generic")
.setSnippetCount(1)
.setSnippetCountPerProperty(1)
.setTermMatch(SearchSpec.TERM_MATCH_PREFIX)
.build());
List<SearchResult> results = searchResults.getNextPageAsync().get();
assertThat(results).hasSize(1);
List<SearchResult.MatchInfo> matchInfos = results.get(0).getMatchInfos();
assertThat(matchInfos).isNotNull();
assertThat(matchInfos).hasSize(1);
SearchResult.MatchInfo matchInfo = matchInfos.get(0);
assertThat(matchInfo.getFullText()).isEqualTo(japanese);
assertThat(matchInfo.getExactMatchRange())
.isEqualTo(new SearchResult.MatchRange(/*lower=*/ 44, /*upper=*/ 45));
assertThat(matchInfo.getExactMatch()).isEqualTo("は");
if (!mDb1.getFeatures().isFeatureSupported(Features.SEARCH_RESULT_MATCH_INFO_SUBMATCH)) {
assertThrows(UnsupportedOperationException.class, matchInfo::getSubmatchRange);
assertThrows(UnsupportedOperationException.class, matchInfo::getSubmatch);
} else {
assertThat(matchInfo.getSubmatchRange())
.isEqualTo(new SearchResult.MatchRange(/*lower=*/ 44, /*upper=*/ 45));
assertThat(matchInfo.getSubmatch()).isEqualTo("は");
}
}
@Test
public void testRemove() throws Exception {
// Schema registration
mDb1.setSchemaAsync(
new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
.get();
// Index documents
AppSearchEmail email1 =
new AppSearchEmail.Builder("namespace", "id1")
.setFrom("from@example.com")
.setTo("to1@example.com", "to2@example.com")
.setSubject("testPut example")
.setBody("This is the body of the testPut email")
.build();
AppSearchEmail email2 =
new AppSearchEmail.Builder("namespace", "id2")
.setFrom("from@example.com")
.setTo("to1@example.com", "to2@example.com")
.setSubject("testPut example 2")
.setBody("This is the body of the testPut second email")
.build();
checkIsBatchResultSuccess(
mDb1.putAsync(
new PutDocumentsRequest.Builder()
.addGenericDocuments(email1, email2)
.build()));
// Check the presence of the documents
assertThat(doGet(mDb1, "namespace", "id1")).hasSize(1);
assertThat(doGet(mDb1, "namespace", "id2")).hasSize(1);
// Delete the document
checkIsBatchResultSuccess(
mDb1.removeAsync(
new RemoveByDocumentIdRequest.Builder("namespace").addIds("id1").build()));
// Make sure it's really gone
AppSearchBatchResult<String, GenericDocument> getResult =
mDb1.getByDocumentIdAsync(
new GetByDocumentIdRequest.Builder("namespace")
.addIds("id1", "id2")
.build())
.get();
assertThat(getResult.isSuccess()).isFalse();
assertThat(getResult.getFailures().get("id1").getResultCode())
.isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
assertThat(getResult.getSuccesses().get("id2")).isEqualTo(email2);
// Test if we delete a nonexistent id.
AppSearchBatchResult<String, Void> deleteResult =
mDb1.removeAsync(
new RemoveByDocumentIdRequest.Builder("namespace")
.addIds("id1")
.build())
.get();
assertThat(deleteResult.getFailures().get("id1").getResultCode())
.isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
}
@Test
public void testRemove_multipleIds() throws Exception {
// Schema registration
mDb1.setSchemaAsync(
new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
.get();
// Index documents
AppSearchEmail email1 =
new AppSearchEmail.Builder("namespace", "id1")
.setFrom("from@example.com")
.setTo("to1@example.com", "to2@example.com")
.setSubject("testPut example")
.setBody("This is the body of the testPut email")
.build();
AppSearchEmail email2 =
new AppSearchEmail.Builder("namespace", "id2")
.setFrom("from@example.com")
.setTo("to1@example.com", "to2@example.com")
.setSubject("testPut example 2")
.setBody("This is the body of the testPut second email")
.build();
checkIsBatchResultSuccess(
mDb1.putAsync(
new PutDocumentsRequest.Builder()
.addGenericDocuments(email1, email2)
.build()));
// Check the presence of the documents
assertThat(doGet(mDb1, "namespace", "id1")).hasSize(1);
assertThat(doGet(mDb1, "namespace", "id2")).hasSize(1);
// Delete the document
checkIsBatchResultSuccess(
mDb1.removeAsync(
new RemoveByDocumentIdRequest.Builder("namespace")
.addIds("id1", "id2")
.build()));
// Make sure it's really gone
AppSearchBatchResult<String, GenericDocument> getResult =
mDb1.getByDocumentIdAsync(
new GetByDocumentIdRequest.Builder("namespace")
.addIds("id1", "id2")
.build())
.get();
assertThat(getResult.isSuccess()).isFalse();
assertThat(getResult.getFailures().get("id1").getResultCode())
.isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
assertThat(getResult.getFailures().get("id2").getResultCode())
.isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
}
@Test
public void testRemoveByQuery() throws Exception {
// Schema registration
mDb1.setSchemaAsync(
new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
.get();
// Index documents
AppSearchEmail email1 =
new AppSearchEmail.Builder("namespace", "id1")
.setFrom("from@example.com")
.setTo("to1@example.com", "to2@example.com")
.setSubject("foo")
.setBody("This is the body of the testPut email")
.build();
AppSearchEmail email2 =
new AppSearchEmail.Builder("namespace", "id2")
.setFrom("from@example.com")
.setTo("to1@example.com", "to2@example.com")
.setSubject("bar")
.setBody("This is the body of the testPut second email")
.build();
checkIsBatchResultSuccess(
mDb1.putAsync(
new PutDocumentsRequest.Builder()
.addGenericDocuments(email1, email2)
.build()));
// Check the presence of the documents
assertThat(doGet(mDb1, "namespace", "id1")).hasSize(1);
assertThat(doGet(mDb1, "namespace", "id2")).hasSize(1);
// Delete the email 1 by query "foo"
mDb1.removeAsync(
"foo",
new SearchSpec.Builder().setTermMatch(SearchSpec.TERM_MATCH_PREFIX).build())
.get();
AppSearchBatchResult<String, GenericDocument> getResult =
mDb1.getByDocumentIdAsync(
new GetByDocumentIdRequest.Builder("namespace")
.addIds("id1", "id2")
.build())
.get();
assertThat(getResult.isSuccess()).isFalse();
assertThat(getResult.getFailures().get("id1").getResultCode())
.isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
assertThat(getResult.getSuccesses().get("id2")).isEqualTo(email2);
// Delete the email 2 by query "bar"
mDb1.removeAsync(
"bar",
new SearchSpec.Builder().setTermMatch(SearchSpec.TERM_MATCH_PREFIX).build())
.get();
getResult =
mDb1.getByDocumentIdAsync(
new GetByDocumentIdRequest.Builder("namespace")
.addIds("id2")
.build())
.get();
assertThat(getResult.isSuccess()).isFalse();
assertThat(getResult.getFailures().get("id2").getResultCode())
.isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
}
@Test
public void testRemoveByQuery_nonExistNamespace() throws Exception {
// Schema registration
mDb1.setSchemaAsync(
new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
.get();
// Index documents
AppSearchEmail email1 =
new AppSearchEmail.Builder("namespace1", "id1")
.setFrom("from@example.com")
.setTo("to1@example.com", "to2@example.com")
.setSubject("foo")
.setBody("This is the body of the testPut email")
.build();
AppSearchEmail email2 =
new AppSearchEmail.Builder("namespace2", "id2")
.setFrom("from@example.com")
.setTo("to1@example.com", "to2@example.com")
.setSubject("bar")
.setBody("This is the body of the testPut second email")
.build();
checkIsBatchResultSuccess(
mDb1.putAsync(
new PutDocumentsRequest.Builder()
.addGenericDocuments(email1, email2)
.build()));
// Check the presence of the documents
assertThat(doGet(mDb1, "namespace1", "id1")).hasSize(1);
assertThat(doGet(mDb1, "namespace2", "id2")).hasSize(1);
// Delete the email by nonExist namespace.
mDb1.removeAsync(
"",
new SearchSpec.Builder()
.setTermMatch(SearchSpec.TERM_MATCH_PREFIX)
.addFilterNamespaces("nonExistNamespace")
.build())
.get();
// None of these emails will be deleted.
assertThat(doGet(mDb1, "namespace1", "id1")).hasSize(1);
assertThat(doGet(mDb1, "namespace2", "id2")).hasSize(1);
}
@Test
public void testRemoveByQuery_packageFilter() throws Exception {
// Schema registration
mDb1.setSchemaAsync(
new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
.get();
// Index documents
AppSearchEmail email =
new AppSearchEmail.Builder("namespace", "id1")
.setFrom("from@example.com")
.setTo("to1@example.com", "to2@example.com")
.setSubject("foo")
.setBody("This is the body of the testPut email")
.build();
checkIsBatchResultSuccess(
mDb1.putAsync(
new PutDocumentsRequest.Builder().addGenericDocuments(email).build()));
// Check the presence of the documents
assertThat(doGet(mDb1, "namespace", "id1")).hasSize(1);
// Try to delete email with query "foo", but restricted to a different package name.
// Won't work and email will still exist.
mDb1.removeAsync(
"foo",
new SearchSpec.Builder()
.setTermMatch(SearchSpec.TERM_MATCH_PREFIX)
.addFilterPackageNames("some.other.package")
.build())
.get();
assertThat(doGet(mDb1, "namespace", "id1")).hasSize(1);
// Delete the email by query "foo", restricted to the correct package this time.
mDb1.removeAsync(
"foo",
new SearchSpec.Builder()
.setTermMatch(SearchSpec.TERM_MATCH_PREFIX)
.addFilterPackageNames(
ApplicationProvider.getApplicationContext()
.getPackageName())
.build())
.get();
AppSearchBatchResult<String, GenericDocument> getResult =
mDb1.getByDocumentIdAsync(
new GetByDocumentIdRequest.Builder("namespace")
.addIds("id1", "id2")
.build())
.get();
assertThat(getResult.isSuccess()).isFalse();
assertThat(getResult.getFailures().get("id1").getResultCode())
.isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
}
@Test
public void testRemove_twoInstances() throws Exception {
// Schema registration
mDb1.setSchemaAsync(
new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
.get();
// Index documents
AppSearchEmail email1 =
new AppSearchEmail.Builder("namespace", "id1")
.setFrom("from@example.com")
.setTo("to1@example.com", "to2@example.com")
.setSubject("testPut example")
.setBody("This is the body of the testPut email")
.build();
checkIsBatchResultSuccess(
mDb1.putAsync(
new PutDocumentsRequest.Builder().addGenericDocuments(email1).build()));
// Check the presence of the documents
assertThat(doGet(mDb1, "namespace", "id1")).hasSize(1);
// Can't delete in the other instance.
AppSearchBatchResult<String, Void> deleteResult =
mDb2.removeAsync(
new RemoveByDocumentIdRequest.Builder("namespace")
.addIds("id1")
.build())
.get();
assertThat(deleteResult.getFailures().get("id1").getResultCode())
.isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
assertThat(doGet(mDb1, "namespace", "id1")).hasSize(1);
// Delete the document
checkIsBatchResultSuccess(
mDb1.removeAsync(
new RemoveByDocumentIdRequest.Builder("namespace").addIds("id1").build()));
// Make sure it's really gone
AppSearchBatchResult<String, GenericDocument> getResult =
mDb1.getByDocumentIdAsync(
new GetByDocumentIdRequest.Builder("namespace")
.addIds("id1")
.build())
.get();
assertThat(getResult.isSuccess()).isFalse();
assertThat(getResult.getFailures().get("id1").getResultCode())
.isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
// Test if we delete a nonexistent id.
deleteResult =
mDb1.removeAsync(
new RemoveByDocumentIdRequest.Builder("namespace")
.addIds("id1")
.build())
.get();
assertThat(deleteResult.getFailures().get("id1").getResultCode())
.isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
}
@Test
public void testRemoveByTypes() throws Exception {
// Schema registration
AppSearchSchema genericSchema = new AppSearchSchema.Builder("Generic").build();
mDb1.setSchemaAsync(
new SetSchemaRequest.Builder()
.addSchemas(AppSearchEmail.SCHEMA)
.addSchemas(genericSchema)
.build())
.get();
// Index documents
AppSearchEmail email1 =
new AppSearchEmail.Builder("namespace", "id1")
.setFrom("from@example.com")
.setTo("to1@example.com", "to2@example.com")
.setSubject("testPut example")
.setBody("This is the body of the testPut email")
.build();
AppSearchEmail email2 =
new AppSearchEmail.Builder("namespace", "id2")
.setFrom("from@example.com")
.setTo("to1@example.com", "to2@example.com")
.setSubject("testPut example 2")
.setBody("This is the body of the testPut second email")
.build();
GenericDocument document1 =
new GenericDocument.Builder<>("namespace", "id3", "Generic").build();
checkIsBatchResultSuccess(
mDb1.putAsync(
new PutDocumentsRequest.Builder()
.addGenericDocuments(email1, email2, document1)
.build()));
// Check the presence of the documents
assertThat(doGet(mDb1, "namespace", "id1", "id2", "id3")).hasSize(3);
// Delete the email type
mDb1.removeAsync(
"",
new SearchSpec.Builder()
.setTermMatch(SearchSpec.TERM_MATCH_PREFIX)
.addFilterSchemas(AppSearchEmail.SCHEMA_TYPE)
.build())
.get();
// Make sure it's really gone
AppSearchBatchResult<String, GenericDocument> getResult =
mDb1.getByDocumentIdAsync(
new GetByDocumentIdRequest.Builder("namespace")
.addIds("id1", "id2", "id3")
.build())
.get();
assertThat(getResult.isSuccess()).isFalse();
assertThat(getResult.getFailures().get("id1").getResultCode())
.isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
assertThat(getResult.getFailures().get("id2").getResultCode())
.isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
assertThat(getResult.getSuccesses().get("id3")).isEqualTo(document1);
}
@Test
public void testRemoveByTypes_twoInstances() throws Exception {
// Schema registration
mDb1.setSchemaAsync(
new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
.get();
mDb2.setSchemaAsync(
new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
.get();
// Index documents
AppSearchEmail email1 =
new AppSearchEmail.Builder("namespace", "id1")
.setFrom("from@example.com")
.setTo("to1@example.com", "to2@example.com")
.setSubject("testPut example")
.setBody("This is the body of the testPut email")
.build();
AppSearchEmail email2 =
new AppSearchEmail.Builder("namespace", "id2")
.setFrom("from@example.com")
.setTo("to1@example.com", "to2@example.com")
.setSubject("testPut example 2")
.setBody("This is the body of the testPut second email")
.build();
checkIsBatchResultSuccess(
mDb1.putAsync(
new PutDocumentsRequest.Builder().addGenericDocuments(email1).build()));
checkIsBatchResultSuccess(
mDb2.putAsync(
new PutDocumentsRequest.Builder().addGenericDocuments(email2).build()));
// Check the presence of the documents
assertThat(doGet(mDb1, "namespace", "id1")).hasSize(1);
assertThat(doGet(mDb2, "namespace", "id2")).hasSize(1);
// Delete the email type in instance 1
mDb1.removeAsync(
"",
new SearchSpec.Builder()
.setTermMatch(SearchSpec.TERM_MATCH_PREFIX)
.addFilterSchemas(AppSearchEmail.SCHEMA_TYPE)
.build())
.get();
// Make sure it's really gone in instance 1
AppSearchBatchResult<String, GenericDocument> getResult =
mDb1.getByDocumentIdAsync(
new GetByDocumentIdRequest.Builder("namespace")
.addIds("id1")
.build())
.get();
assertThat(getResult.isSuccess()).isFalse();
assertThat(getResult.getFailures().get("id1").getResultCode())
.isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
// Make sure it's still in instance 2.
getResult =
mDb2.getByDocumentIdAsync(
new GetByDocumentIdRequest.Builder("namespace")
.addIds("id2")
.build())
.get();
assertThat(getResult.isSuccess()).isTrue();
assertThat(getResult.getSuccesses().get("id2")).isEqualTo(email2);
}
@Test
public void testRemoveByNamespace() throws Exception {
// Schema registration
AppSearchSchema genericSchema =
new AppSearchSchema.Builder("Generic")
.addProperty(
new StringPropertyConfig.Builder("foo")
.setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
.setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
.setIndexingType(
StringPropertyConfig.INDEXING_TYPE_PREFIXES)
.build())
.build();
mDb1.setSchemaAsync(
new SetSchemaRequest.Builder()
.addSchemas(AppSearchEmail.SCHEMA)
.addSchemas(genericSchema)
.build())
.get();
// Index documents
AppSearchEmail email1 =
new AppSearchEmail.Builder("email", "id1")
.setFrom("from@example.com")
.setTo("to1@example.com", "to2@example.com")
.setSubject("testPut example")
.setBody("This is the body of the testPut email")
.build();
AppSearchEmail email2 =
new AppSearchEmail.Builder("email", "id2")
.setFrom("from@example.com")
.setTo("to1@example.com", "to2@example.com")
.setSubject("testPut example 2")
.setBody("This is the body of the testPut second email")
.build();
GenericDocument document1 =
new GenericDocument.Builder<>("document", "id3", "Generic")
.setPropertyString("foo", "bar")
.build();
checkIsBatchResultSuccess(
mDb1.putAsync(
new PutDocumentsRequest.Builder()
.addGenericDocuments(email1, email2, document1)
.build()));
// Check the presence of the documents
assertThat(doGet(mDb1, /*namespace=*/ "email", "id1", "id2")).hasSize(2);
assertThat(doGet(mDb1, /*namespace=*/ "document", "id3")).hasSize(1);
// Delete the email namespace
mDb1.removeAsync(
"",
new SearchSpec.Builder()
.setTermMatch(SearchSpec.TERM_MATCH_PREFIX)
.addFilterNamespaces("email")
.build())
.get();
// Make sure it's really gone
AppSearchBatchResult<String, GenericDocument> getResult =
mDb1.getByDocumentIdAsync(
new GetByDocumentIdRequest.Builder("email")
.addIds("id1", "id2")
.build())
.get();
assertThat(getResult.isSuccess()).isFalse();
assertThat(getResult.getFailures().get("id1").getResultCode())
.isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
assertThat(getResult.getFailures().get("id2").getResultCode())
.isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
getResult =
mDb1.getByDocumentIdAsync(
new GetByDocumentIdRequest.Builder("document")
.addIds("id3")
.build())
.get();
assertThat(getResult.isSuccess()).isTrue();
assertThat(getResult.getSuccesses().get("id3")).isEqualTo(document1);
}
@Test
public void testRemoveByNamespaces_twoInstances() throws Exception {
// Schema registration
mDb1.setSchemaAsync(
new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
.get();
mDb2.setSchemaAsync(
new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
.get();
// Index documents
AppSearchEmail email1 =
new AppSearchEmail.Builder("email", "id1")
.setFrom("from@example.com")
.setTo("to1@example.com", "to2@example.com")
.setSubject("testPut example")
.setBody("This is the body of the testPut email")
.build();
AppSearchEmail email2 =
new AppSearchEmail.Builder("email", "id2")
.setFrom("from@example.com")
.setTo("to1@example.com", "to2@example.com")
.setSubject("testPut example 2")
.setBody("This is the body of the testPut second email")
.build();
checkIsBatchResultSuccess(
mDb1.putAsync(
new PutDocumentsRequest.Builder().addGenericDocuments(email1).build()));
checkIsBatchResultSuccess(
mDb2.putAsync(
new PutDocumentsRequest.Builder().addGenericDocuments(email2).build()));
// Check the presence of the documents
assertThat(doGet(mDb1, /*namespace=*/ "email", "id1")).hasSize(1);
assertThat(doGet(mDb2, /*namespace=*/ "email", "id2")).hasSize(1);
// Delete the email namespace in instance 1
mDb1.removeAsync(
"",
new SearchSpec.Builder()
.setTermMatch(SearchSpec.TERM_MATCH_PREFIX)
.addFilterNamespaces("email")
.build())
.get();
// Make sure it's really gone in instance 1
AppSearchBatchResult<String, GenericDocument> getResult =
mDb1.getByDocumentIdAsync(
new GetByDocumentIdRequest.Builder("email").addIds("id1").build())
.get();
assertThat(getResult.isSuccess()).isFalse();
assertThat(getResult.getFailures().get("id1").getResultCode())
.isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
// Make sure it's still in instance 2.
getResult =
mDb2.getByDocumentIdAsync(
new GetByDocumentIdRequest.Builder("email").addIds("id2").build())
.get();
assertThat(getResult.isSuccess()).isTrue();
assertThat(getResult.getSuccesses().get("id2")).isEqualTo(email2);
}
@Test
public void testRemoveAll_twoInstances() throws Exception {
// Schema registration
mDb1.setSchemaAsync(
new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
.get();
mDb2.setSchemaAsync(
new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
.get();
// Index documents
AppSearchEmail email1 =
new AppSearchEmail.Builder("namespace", "id1")
.setFrom("from@example.com")
.setTo("to1@example.com", "to2@example.com")
.setSubject("testPut example")
.setBody("This is the body of the testPut email")
.build();
AppSearchEmail email2 =
new AppSearchEmail.Builder("namespace", "id2")
.setFrom("from@example.com")
.setTo("to1@example.com", "to2@example.com")
.setSubject("testPut example 2")
.setBody("This is the body of the testPut second email")
.build();
checkIsBatchResultSuccess(
mDb1.putAsync(
new PutDocumentsRequest.Builder().addGenericDocuments(email1).build()));
checkIsBatchResultSuccess(
mDb2.putAsync(
new PutDocumentsRequest.Builder().addGenericDocuments(email2).build()));
// Check the presence of the documents
assertThat(doGet(mDb1, "namespace", "id1")).hasSize(1);
assertThat(doGet(mDb2, "namespace", "id2")).hasSize(1);
// Delete the all document in instance 1
mDb1.removeAsync(
"",
new SearchSpec.Builder().setTermMatch(SearchSpec.TERM_MATCH_PREFIX).build())
.get();
// Make sure it's really gone in instance 1
AppSearchBatchResult<String, GenericDocument> getResult =
mDb1.getByDocumentIdAsync(
new GetByDocumentIdRequest.Builder("namespace")
.addIds("id1")
.build())
.get();
assertThat(getResult.isSuccess()).isFalse();
assertThat(getResult.getFailures().get("id1").getResultCode())
.isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
// Make sure it's still in instance 2.
getResult =
mDb2.getByDocumentIdAsync(
new GetByDocumentIdRequest.Builder("namespace")
.addIds("id2")
.build())
.get();
assertThat(getResult.isSuccess()).isTrue();
assertThat(getResult.getSuccesses().get("id2")).isEqualTo(email2);
}
@Test
public void testRemoveAll_termMatchType() throws Exception {
// Schema registration
mDb1.setSchemaAsync(
new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
.get();
mDb2.setSchemaAsync(
new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
.get();
// Index documents
AppSearchEmail email1 =
new AppSearchEmail.Builder("namespace", "id1")
.setFrom("from@example.com")
.setTo("to1@example.com", "to2@example.com")
.setSubject("testPut example")
.setBody("This is the body of the testPut email")
.build();
AppSearchEmail email2 =
new AppSearchEmail.Builder("namespace", "id2")
.setFrom("from@example.com")
.setTo("to1@example.com", "to2@example.com")
.setSubject("testPut example 2")
.setBody("This is the body of the testPut second email")
.build();
AppSearchEmail email3 =
new AppSearchEmail.Builder("namespace", "id3")
.setFrom("from@example.com")
.setTo("to1@example.com", "to2@example.com")
.setSubject("testPut example 3")
.setBody("This is the body of the testPut second email")
.build();
AppSearchEmail email4 =
new AppSearchEmail.Builder("namespace", "id4")
.setFrom("from@example.com")
.setTo("to1@example.com", "to2@example.com")
.setSubject("testPut example 4")
.setBody("This is the body of the testPut second email")
.build();
checkIsBatchResultSuccess(
mDb1.putAsync(
new PutDocumentsRequest.Builder()
.addGenericDocuments(email1, email2)
.build()));
checkIsBatchResultSuccess(
mDb2.putAsync(
new PutDocumentsRequest.Builder()
.addGenericDocuments(email3, email4)
.build()));
// Check the presence of the documents
SearchResultsShim searchResults =
mDb1.search(
"",
new SearchSpec.Builder()
.setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
.build());
List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
assertThat(documents).hasSize(2);
searchResults =
mDb2.search(
"",
new SearchSpec.Builder()
.setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
.build());
documents = convertSearchResultsToDocuments(searchResults);
assertThat(documents).hasSize(2);
// Delete the all document in instance 1 with TERM_MATCH_PREFIX
mDb1.removeAsync(
"",
new SearchSpec.Builder().setTermMatch(SearchSpec.TERM_MATCH_PREFIX).build())
.get();
searchResults =
mDb1.search(
"",
new SearchSpec.Builder()
.setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
.build());
documents = convertSearchResultsToDocuments(searchResults);
assertThat(documents).isEmpty();
// Delete the all document in instance 2 with TERM_MATCH_EXACT_ONLY
mDb2.removeAsync(
"",
new SearchSpec.Builder()
.setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
.build())
.get();
searchResults =
mDb2.search(
"",
new SearchSpec.Builder()
.setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
.build());
documents = convertSearchResultsToDocuments(searchResults);
assertThat(documents).isEmpty();
}
@Test
public void testRemoveAllAfterEmpty() throws Exception {
// Schema registration
mDb1.setSchemaAsync(
new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
.get();
// Index documents
AppSearchEmail email1 =
new AppSearchEmail.Builder("namespace", "id1")
.setFrom("from@example.com")
.setTo("to1@example.com", "to2@example.com")
.setSubject("testPut example")
.setBody("This is the body of the testPut email")
.build();
checkIsBatchResultSuccess(
mDb1.putAsync(
new PutDocumentsRequest.Builder().addGenericDocuments(email1).build()));
// Check the presence of the documents
assertThat(doGet(mDb1, "namespace", "id1")).hasSize(1);
// Remove the document
checkIsBatchResultSuccess(
mDb1.removeAsync(
new RemoveByDocumentIdRequest.Builder("namespace").addIds("id1").build()));
// Make sure it's really gone
AppSearchBatchResult<String, GenericDocument> getResult =
mDb1.getByDocumentIdAsync(
new GetByDocumentIdRequest.Builder("namespace")
.addIds("id1")
.build())
.get();
assertThat(getResult.isSuccess()).isFalse();
assertThat(getResult.getFailures().get("id1").getResultCode())
.isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
// Delete the all documents
mDb1.removeAsync(
"",
new SearchSpec.Builder().setTermMatch(SearchSpec.TERM_MATCH_PREFIX).build())
.get();
// Make sure it's still gone
getResult =
mDb1.getByDocumentIdAsync(
new GetByDocumentIdRequest.Builder("namespace")
.addIds("id1")
.build())
.get();
assertThat(getResult.isSuccess()).isFalse();
assertThat(getResult.getFailures().get("id1").getResultCode())
.isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
}
@Test
public void testCloseAndReopen() throws Exception {
// Schema registration
mDb1.setSchemaAsync(
new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
.get();
// Index a document
AppSearchEmail inEmail =
new AppSearchEmail.Builder("namespace", "id1")
.setFrom("from@example.com")
.setTo("to1@example.com", "to2@example.com")
.setSubject("testPut example")
.setBody("This is the body of the testPut email")
.build();
checkIsBatchResultSuccess(
mDb1.putAsync(
new PutDocumentsRequest.Builder().addGenericDocuments(inEmail).build()));
// close and re-open the appSearchSession
mDb1.close();
mDb1 = createSearchSessionAsync(DB_NAME_1).get();
// Query for the document
SearchResultsShim searchResults =
mDb1.search(
"body",
new SearchSpec.Builder()
.setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
.build());
List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
assertThat(documents).containsExactly(inEmail);
}
@Test
public void testCallAfterClose() throws Exception {
// Create a same-thread database by inject an executor which could help us maintain the
// execution order of those async tasks.
Context context = ApplicationProvider.getApplicationContext();
AppSearchSessionShim sameThreadDb =
createSearchSessionAsync("sameThreadDb", MoreExecutors.newDirectExecutorService())
.get();
try {
// Schema registration -- just mutate something
sameThreadDb
.setSchemaAsync(
new SetSchemaRequest.Builder()
.addSchemas(AppSearchEmail.SCHEMA)
.build())
.get();
// Close the database. No further call will be allowed.
sameThreadDb.close();
// Try to query the closed database
// We are using the same-thread db here to make sure it has been closed.
IllegalStateException e =
assertThrows(
IllegalStateException.class,
() ->
sameThreadDb.search(
"query",
new SearchSpec.Builder()
.setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
.build()));
assertThat(e).hasMessageThat().contains("SearchSession has already been closed");
} finally {
// To clean the data that has been added in the test, need to re-open the session and
// set an empty schema.
AppSearchSessionShim reopen =
createSearchSessionAsync(
"sameThreadDb", MoreExecutors.newDirectExecutorService())
.get();
reopen.setSchemaAsync(new SetSchemaRequest.Builder().setForceOverride(true).build())
.get();
}
}
@Test
public void testReportUsage() throws Exception {
mDb1.setSchemaAsync(
new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
.get();
// Index two documents.
AppSearchEmail email1 = new AppSearchEmail.Builder("namespace", "id1").build();
AppSearchEmail email2 = new AppSearchEmail.Builder("namespace", "id2").build();
checkIsBatchResultSuccess(
mDb1.putAsync(
new PutDocumentsRequest.Builder()
.addGenericDocuments(email1, email2)
.build()));
// Email 1 has more usages, but email 2 has more recent usages.
mDb1.reportUsageAsync(
new ReportUsageRequest.Builder("namespace", "id1")
.setUsageTimestampMillis(1000)
.build())
.get();
mDb1.reportUsageAsync(
new ReportUsageRequest.Builder("namespace", "id1")
.setUsageTimestampMillis(2000)
.build())
.get();
mDb1.reportUsageAsync(
new ReportUsageRequest.Builder("namespace", "id1")
.setUsageTimestampMillis(3000)
.build())
.get();
mDb1.reportUsageAsync(
new ReportUsageRequest.Builder("namespace", "id2")
.setUsageTimestampMillis(10000)
.build())
.get();
mDb1.reportUsageAsync(
new ReportUsageRequest.Builder("namespace", "id2")
.setUsageTimestampMillis(20000)
.build())
.get();
// Query by number of usages
List<SearchResult> results =
retrieveAllSearchResults(
mDb1.search(
"",
new SearchSpec.Builder()
.setRankingStrategy(SearchSpec.RANKING_STRATEGY_USAGE_COUNT)
.setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
.build()));
// Email 1 has three usages and email 2 has two usages.
assertThat(results).hasSize(2);
assertThat(results.get(0).getGenericDocument()).isEqualTo(email1);
assertThat(results.get(1).getGenericDocument()).isEqualTo(email2);
assertThat(results.get(0).getRankingSignal()).isEqualTo(3);
assertThat(results.get(1).getRankingSignal()).isEqualTo(2);
// Query by most recent usage.
results =
retrieveAllSearchResults(
mDb1.search(
"",
new SearchSpec.Builder()
.setRankingStrategy(
SearchSpec
.RANKING_STRATEGY_USAGE_LAST_USED_TIMESTAMP)
.setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
.build()));
assertThat(results).hasSize(2);
assertThat(results.get(0).getGenericDocument()).isEqualTo(email2);
assertThat(results.get(1).getGenericDocument()).isEqualTo(email1);
assertThat(results.get(0).getRankingSignal()).isEqualTo(20000);
assertThat(results.get(1).getRankingSignal()).isEqualTo(3000);
}
@Test
public void testReportUsage_invalidNamespace() throws Exception {
mDb1.setSchemaAsync(
new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
.get();
AppSearchEmail email1 = new AppSearchEmail.Builder("namespace", "id1").build();
checkIsBatchResultSuccess(
mDb1.putAsync(
new PutDocumentsRequest.Builder().addGenericDocuments(email1).build()));
// Use the correct namespace; it works
mDb1.reportUsageAsync(new ReportUsageRequest.Builder("namespace", "id1").build()).get();
// Use an incorrect namespace; it fails
ExecutionException e =
assertThrows(
ExecutionException.class,
() ->
mDb1.reportUsageAsync(
new ReportUsageRequest.Builder("namespace2", "id1")
.build())
.get());
assertThat(e).hasCauseThat().isInstanceOf(AppSearchException.class);
AppSearchException cause = (AppSearchException) e.getCause();
assertThat(cause.getResultCode()).isEqualTo(RESULT_NOT_FOUND);
}
@Test
public void testGetStorageInfo() throws Exception {
StorageInfo storageInfo = mDb1.getStorageInfoAsync().get();
assertThat(storageInfo.getSizeBytes()).isEqualTo(0);
mDb1.setSchemaAsync(
new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
.get();
// Still no storage space attributed with just a schema
storageInfo = mDb1.getStorageInfoAsync().get();
assertThat(storageInfo.getSizeBytes()).isEqualTo(0);
// Index two documents.
AppSearchEmail email1 = new AppSearchEmail.Builder("namespace1", "id1").build();
AppSearchEmail email2 = new AppSearchEmail.Builder("namespace1", "id2").build();
AppSearchEmail email3 = new AppSearchEmail.Builder("namespace2", "id1").build();
checkIsBatchResultSuccess(
mDb1.putAsync(
new PutDocumentsRequest.Builder()
.addGenericDocuments(email1, email2, email3)
.build()));
// Non-zero size now
storageInfo = mDb1.getStorageInfoAsync().get();
assertThat(storageInfo.getSizeBytes()).isGreaterThan(0);
assertThat(storageInfo.getAliveDocumentsCount()).isEqualTo(3);
assertThat(storageInfo.getAliveNamespacesCount()).isEqualTo(2);
}
@Test
public void testFlush() throws Exception {
// Schema registration
mDb1.setSchemaAsync(
new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
.get();
// Index a document
AppSearchEmail email =
new AppSearchEmail.Builder("namespace", "id1")
.setFrom("from@example.com")
.setTo("to1@example.com", "to2@example.com")
.setSubject("testPut example")
.setBody("This is the body of the testPut email")
.build();
AppSearchBatchResult<String, Void> result =
checkIsBatchResultSuccess(
mDb1.putAsync(
new PutDocumentsRequest.Builder()
.addGenericDocuments(email)
.build()));
assertThat(result.getSuccesses()).containsExactly("id1", null);
assertThat(result.getFailures()).isEmpty();
// The future returned from requestFlush will be set as a void or an Exception on error.
mDb1.requestFlushAsync().get();
}
@Test
public void testQuery_ResultGroupingLimits() throws Exception {
// Schema registration
mDb1.setSchemaAsync(
new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
.get();
// Index four documents.
AppSearchEmail inEmail1 =
new AppSearchEmail.Builder("namespace1", "id1")
.setFrom("from@example.com")
.setTo("to1@example.com", "to2@example.com")
.setSubject("testPut example")
.setBody("This is the body of the testPut email")
.build();
checkIsBatchResultSuccess(
mDb1.putAsync(
new PutDocumentsRequest.Builder().addGenericDocuments(inEmail1).build()));
AppSearchEmail inEmail2 =
new AppSearchEmail.Builder("namespace1", "id2")
.setFrom("from@example.com")
.setTo("to1@example.com", "to2@example.com")
.setSubject("testPut example")
.setBody("This is the body of the testPut email")
.build();
checkIsBatchResultSuccess(
mDb1.putAsync(
new PutDocumentsRequest.Builder().addGenericDocuments(inEmail2).build()));
AppSearchEmail inEmail3 =
new AppSearchEmail.Builder("namespace2", "id3")
.setFrom("from@example.com")
.setTo("to1@example.com", "to2@example.com")
.setSubject("testPut example")
.setBody("This is the body of the testPut email")
.build();
checkIsBatchResultSuccess(
mDb1.putAsync(
new PutDocumentsRequest.Builder().addGenericDocuments(inEmail3).build()));
AppSearchEmail inEmail4 =
new AppSearchEmail.Builder("namespace2", "id4")
.setFrom("from@example.com")
.setTo("to1@example.com", "to2@example.com")
.setSubject("testPut example")
.setBody("This is the body of the testPut email")
.build();
checkIsBatchResultSuccess(
mDb1.putAsync(
new PutDocumentsRequest.Builder().addGenericDocuments(inEmail4).build()));
// Query with per package result grouping. Only the last document 'email4' should be
// returned.
SearchResultsShim searchResults =
mDb1.search(
"body",
new SearchSpec.Builder()
.setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
.setResultGrouping(
SearchSpec.GROUPING_TYPE_PER_PACKAGE, /*resultLimit=*/ 1)
.build());
List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
assertThat(documents).containsExactly(inEmail4);
// Query with per namespace result grouping. Only the last document in each namespace should
// be returned ('email4' and 'email2').
searchResults =
mDb1.search(
"body",
new SearchSpec.Builder()
.setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
.setResultGrouping(
SearchSpec.GROUPING_TYPE_PER_NAMESPACE, /*resultLimit=*/ 1)
.build());
documents = convertSearchResultsToDocuments(searchResults);
assertThat(documents).containsExactly(inEmail4, inEmail2);
// Query with per package and per namespace result grouping. Only the last document in each
// namespace should be returned ('email4' and 'email2').
searchResults =
mDb1.search(
"body",
new SearchSpec.Builder()
.setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
.setResultGrouping(
SearchSpec.GROUPING_TYPE_PER_NAMESPACE
| SearchSpec.GROUPING_TYPE_PER_PACKAGE,
/*resultLimit=*/ 1)
.build());
documents = convertSearchResultsToDocuments(searchResults);
assertThat(documents).containsExactly(inEmail4, inEmail2);
}
@Test
public void testIndexNestedDocuments() throws Exception {
// Schema registration
mDb1.setSchemaAsync(
new SetSchemaRequest.Builder()
.addSchemas(AppSearchEmail.SCHEMA)
.addSchemas(
new AppSearchSchema.Builder("YesNestedIndex")
.addProperty(
new AppSearchSchema.DocumentPropertyConfig
.Builder(
"prop",
AppSearchEmail.SCHEMA_TYPE)
.setShouldIndexNestedProperties(
true)
.build())
.build())
.addSchemas(
new AppSearchSchema.Builder("NoNestedIndex")
.addProperty(
new AppSearchSchema.DocumentPropertyConfig
.Builder(
"prop",
AppSearchEmail.SCHEMA_TYPE)
.setShouldIndexNestedProperties(
false)
.build())
.build())
.build())
.get();
// Index the documents.
AppSearchEmail email =
new AppSearchEmail.Builder("", "").setSubject("This is the body").build();
GenericDocument yesNestedIndex =
new GenericDocument.Builder<>("namespace", "yesNestedIndex", "YesNestedIndex")
.setPropertyDocument("prop", email)
.build();
GenericDocument noNestedIndex =
new GenericDocument.Builder<>("namespace", "noNestedIndex", "NoNestedIndex")
.setPropertyDocument("prop", email)
.build();
checkIsBatchResultSuccess(
mDb1.putAsync(
new PutDocumentsRequest.Builder()
.addGenericDocuments(yesNestedIndex, noNestedIndex)
.build()));
// Query.
SearchResultsShim searchResults =
mDb1.search(
"body",
new SearchSpec.Builder()
.setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
.setSnippetCount(10)
.setSnippetCountPerProperty(10)
.build());
List<SearchResult> page = searchResults.getNextPageAsync().get();
assertThat(page).hasSize(1);
assertThat(page.get(0).getGenericDocument()).isEqualTo(yesNestedIndex);
List<SearchResult.MatchInfo> matches = page.get(0).getMatchInfos();
assertThat(matches).hasSize(1);
assertThat(matches.get(0).getPropertyPath()).isEqualTo("prop.subject");
assertThat(matches.get(0).getFullText()).isEqualTo("This is the body");
assertThat(matches.get(0).getExactMatch()).isEqualTo("body");
}
@Test
public void testCJKTQuery() throws Exception {
// Schema registration
mDb1.setSchemaAsync(
new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
.get();
// Index a document to instance 1.
AppSearchEmail inEmail1 =
new AppSearchEmail.Builder("namespace", "uri1").setBody("他是個男孩 is a boy").build();
checkIsBatchResultSuccess(
mDb1.putAsync(
new PutDocumentsRequest.Builder().addGenericDocuments(inEmail1).build()));
// Query for "他" (He)
SearchResultsShim searchResults =
mDb1.search(
"他",
new SearchSpec.Builder()
.setTermMatch(SearchSpec.TERM_MATCH_PREFIX)
.build());
List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
assertThat(documents).containsExactly(inEmail1);
// Query for "男孩" (boy)
searchResults =
mDb1.search(
"男孩",
new SearchSpec.Builder()
.setTermMatch(SearchSpec.TERM_MATCH_PREFIX)
.build());
documents = convertSearchResultsToDocuments(searchResults);
assertThat(documents).containsExactly(inEmail1);
// Query for "boy"
searchResults =
mDb1.search(
"boy",
new SearchSpec.Builder()
.setTermMatch(SearchSpec.TERM_MATCH_PREFIX)
.build());
documents = convertSearchResultsToDocuments(searchResults);
assertThat(documents).containsExactly(inEmail1);
}
@Test
public void testSetSchemaWithIncompatibleNestedSchema() throws Exception {
// 1. Set the original schema. This should succeed without any problems.
AppSearchSchema originalNestedSchema =
new AppSearchSchema.Builder("TypeA")
.addProperty(
new StringPropertyConfig.Builder("prop1")
.setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
.build())
.build();
SetSchemaRequest originalRequest =
new SetSchemaRequest.Builder().addSchemas(originalNestedSchema).build();
mDb1.setSchemaAsync(originalRequest).get();
// 2. Set a new schema with a new type that refers to "TypeA" and an incompatible change to
// "TypeA". This should fail.
AppSearchSchema newNestedSchema =
new AppSearchSchema.Builder("TypeA")
.addProperty(
new StringPropertyConfig.Builder("prop1")
.setCardinality(PropertyConfig.CARDINALITY_REQUIRED)
.build())
.build();
AppSearchSchema newSchema =
new AppSearchSchema.Builder("TypeB")
.addProperty(
new AppSearchSchema.DocumentPropertyConfig.Builder("prop2", "TypeA")
.build())
.build();
final SetSchemaRequest newRequest =
new SetSchemaRequest.Builder().addSchemas(newNestedSchema, newSchema).build();
Throwable throwable =
assertThrows(ExecutionException.class, () -> mDb1.setSchemaAsync(newRequest).get())
.getCause();
assertThat(throwable).isInstanceOf(AppSearchException.class);
AppSearchException exception = (AppSearchException) throwable;
assertThat(exception.getResultCode()).isEqualTo(RESULT_INVALID_SCHEMA);
assertThat(exception).hasMessageThat().contains("Schema is incompatible.");
assertThat(exception).hasMessageThat().contains("Incompatible types: {TypeA}");
// 3. Now set that same set of schemas but with forceOverride=true. This should succeed.
SetSchemaRequest newRequestForced =
new SetSchemaRequest.Builder()
.addSchemas(newNestedSchema, newSchema)
.setForceOverride(true)
.build();
mDb1.setSchemaAsync(newRequestForced).get();
}
@Test
public void testEmojiSnippet() throws Exception {
// Schema registration
mDb1.setSchemaAsync(
new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
.get();
// String: "Luca Brasi sleeps with the 🐟🐟🐟."
// ^ ^ ^ ^ ^ ^ ^ ^ ^
// UTF8 idx: 0 5 11 18 23 27 3135 39
// UTF16 idx: 0 5 11 18 23 27 2931 33
// Breaks into segments: "Luca", "Brasi", "sleeps", "with", "the", "🐟", "🐟"
// and "🐟".
// Index a document to instance 1.
String sicilianMessage = "Luca Brasi sleeps with the 🐟🐟🐟.";
AppSearchEmail inEmail1 =
new AppSearchEmail.Builder("namespace", "uri1").setBody(sicilianMessage).build();
checkIsBatchResultSuccess(
mDb1.putAsync(
new PutDocumentsRequest.Builder().addGenericDocuments(inEmail1).build()));
AppSearchEmail inEmail2 =
new AppSearchEmail.Builder("namespace", "uri2")
.setBody("Some other content.")
.build();
checkIsBatchResultSuccess(
mDb1.putAsync(
new PutDocumentsRequest.Builder().addGenericDocuments(inEmail2).build()));
// Query for "🐟"
SearchResultsShim searchResults =
mDb1.search(
"🐟",
new SearchSpec.Builder()
.setTermMatch(SearchSpec.TERM_MATCH_PREFIX)
.setSnippetCount(1)
.setSnippetCountPerProperty(1)
.build());
List<SearchResult> page = searchResults.getNextPageAsync().get();
assertThat(page).hasSize(1);
assertThat(page.get(0).getGenericDocument()).isEqualTo(inEmail1);
List<SearchResult.MatchInfo> matches = page.get(0).getMatchInfos();
assertThat(matches).hasSize(1);
assertThat(matches.get(0).getPropertyPath()).isEqualTo("body");
assertThat(matches.get(0).getFullText()).isEqualTo(sicilianMessage);
assertThat(matches.get(0).getExactMatch()).isEqualTo("🐟");
if (mDb1.getFeatures().isFeatureSupported(Features.SEARCH_RESULT_MATCH_INFO_SUBMATCH)) {
assertThat(matches.get(0).getSubmatch()).isEqualTo("🐟");
}
}
}