blob: f40a5ad7bcb6b20b730c9a7d7458a7f6e42b3356 [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 com.android.server.appsearch.external.localstorage;
import static com.android.server.appsearch.external.localstorage.util.PrefixUtil.addPrefixToDocument;
import static com.android.server.appsearch.external.localstorage.util.PrefixUtil.createPrefix;
import static com.android.server.appsearch.external.localstorage.util.PrefixUtil.removePrefixesFromDocument;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
import android.app.appsearch.AppSearchResult;
import android.app.appsearch.AppSearchSchema;
import android.app.appsearch.GenericDocument;
import android.app.appsearch.SearchResult;
import android.app.appsearch.SearchResultPage;
import android.app.appsearch.SearchSpec;
import android.app.appsearch.SetSchemaResponse;
import android.app.appsearch.StorageInfo;
import android.app.appsearch.exceptions.AppSearchException;
import android.content.Context;
import android.os.Process;
import android.util.ArrayMap;
import android.util.ArraySet;
import androidx.test.core.app.ApplicationProvider;
import com.android.server.appsearch.external.localstorage.converter.GenericDocumentToProtoConverter;
import com.android.server.appsearch.external.localstorage.stats.InitializeStats;
import com.android.server.appsearch.external.localstorage.stats.OptimizeStats;
import com.android.server.appsearch.external.localstorage.util.PrefixUtil;
import com.android.server.appsearch.icing.proto.DocumentProto;
import com.android.server.appsearch.icing.proto.GetOptimizeInfoResultProto;
import com.android.server.appsearch.icing.proto.PersistType;
import com.android.server.appsearch.icing.proto.PropertyConfigProto;
import com.android.server.appsearch.icing.proto.PropertyProto;
import com.android.server.appsearch.icing.proto.PutResultProto;
import com.android.server.appsearch.icing.proto.SchemaProto;
import com.android.server.appsearch.icing.proto.SchemaTypeConfigProto;
import com.android.server.appsearch.icing.proto.SearchResultProto;
import com.android.server.appsearch.icing.proto.SearchSpecProto;
import com.android.server.appsearch.icing.proto.StatusProto;
import com.android.server.appsearch.icing.proto.StorageInfoProto;
import com.android.server.appsearch.icing.proto.StringIndexingConfig;
import com.android.server.appsearch.icing.proto.TermMatchType;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class AppSearchImplTest {
@Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
private AppSearchImpl mAppSearchImpl;
/**
* Always trigger optimize in this class. OptimizeStrategy will be tested in its own test class.
*/
private static final OptimizeStrategy ALWAYS_OPTIMIZE = optimizeInfo -> true;
@Before
public void setUp() throws Exception {
mAppSearchImpl =
AppSearchImpl.create(
mTemporaryFolder.newFolder(),
new UnlimitedLimitConfig(),
/*initStatsBuilder=*/ null,
ALWAYS_OPTIMIZE);
}
/**
* Ensure that we can rewrite an incoming schema type by adding the database as a prefix. While
* also keeping any other existing schema types that may already be part of Icing's persisted
* schema.
*/
@Test
public void testRewriteSchema_addType() throws Exception {
SchemaProto.Builder existingSchemaBuilder =
SchemaProto.newBuilder()
.addTypes(
SchemaTypeConfigProto.newBuilder()
.setSchemaType("package$existingDatabase/Foo")
.build());
// Create a copy so we can modify it.
List<SchemaTypeConfigProto> existingTypes =
new ArrayList<>(existingSchemaBuilder.getTypesList());
SchemaTypeConfigProto schemaTypeConfigProto1 =
SchemaTypeConfigProto.newBuilder().setSchemaType("Foo").build();
SchemaTypeConfigProto schemaTypeConfigProto2 =
SchemaTypeConfigProto.newBuilder()
.setSchemaType("TestType")
.addProperties(
PropertyConfigProto.newBuilder()
.setPropertyName("subject")
.setDataType(PropertyConfigProto.DataType.Code.STRING)
.setCardinality(
PropertyConfigProto.Cardinality.Code.OPTIONAL)
.setStringIndexingConfig(
StringIndexingConfig.newBuilder()
.setTokenizerType(
StringIndexingConfig.TokenizerType
.Code.PLAIN)
.setTermMatchType(TermMatchType.Code.PREFIX)
.build())
.build())
.addProperties(
PropertyConfigProto.newBuilder()
.setPropertyName("link")
.setDataType(PropertyConfigProto.DataType.Code.DOCUMENT)
.setCardinality(
PropertyConfigProto.Cardinality.Code.OPTIONAL)
.setSchemaType("RefType")
.build())
.build();
SchemaTypeConfigProto schemaTypeConfigProto3 =
SchemaTypeConfigProto.newBuilder().setSchemaType("RefType").build();
SchemaProto newSchema =
SchemaProto.newBuilder()
.addTypes(schemaTypeConfigProto1)
.addTypes(schemaTypeConfigProto2)
.addTypes(schemaTypeConfigProto3)
.build();
AppSearchImpl.RewrittenSchemaResults rewrittenSchemaResults =
AppSearchImpl.rewriteSchema(
createPrefix("package", "newDatabase"), existingSchemaBuilder, newSchema);
// We rewrote all the new types that were added. And nothing was removed.
assertThat(rewrittenSchemaResults.mRewrittenPrefixedTypes.keySet())
.containsExactly(
"package$newDatabase/Foo",
"package$newDatabase/TestType",
"package$newDatabase/RefType");
assertThat(
rewrittenSchemaResults
.mRewrittenPrefixedTypes
.get("package$newDatabase/Foo")
.getSchemaType())
.isEqualTo("package$newDatabase/Foo");
assertThat(
rewrittenSchemaResults
.mRewrittenPrefixedTypes
.get("package$newDatabase/TestType")
.getSchemaType())
.isEqualTo("package$newDatabase/TestType");
assertThat(
rewrittenSchemaResults
.mRewrittenPrefixedTypes
.get("package$newDatabase/RefType")
.getSchemaType())
.isEqualTo("package$newDatabase/RefType");
assertThat(rewrittenSchemaResults.mDeletedPrefixedTypes).isEmpty();
SchemaProto expectedSchema =
SchemaProto.newBuilder()
.addTypes(
SchemaTypeConfigProto.newBuilder()
.setSchemaType("package$newDatabase/Foo")
.build())
.addTypes(
SchemaTypeConfigProto.newBuilder()
.setSchemaType("package$newDatabase/TestType")
.addProperties(
PropertyConfigProto.newBuilder()
.setPropertyName("subject")
.setDataType(
PropertyConfigProto.DataType.Code
.STRING)
.setCardinality(
PropertyConfigProto.Cardinality.Code
.OPTIONAL)
.setStringIndexingConfig(
StringIndexingConfig.newBuilder()
.setTokenizerType(
StringIndexingConfig
.TokenizerType
.Code.PLAIN)
.setTermMatchType(
TermMatchType.Code
.PREFIX)
.build())
.build())
.addProperties(
PropertyConfigProto.newBuilder()
.setPropertyName("link")
.setDataType(
PropertyConfigProto.DataType.Code
.DOCUMENT)
.setCardinality(
PropertyConfigProto.Cardinality.Code
.OPTIONAL)
.setSchemaType(
"package$newDatabase/RefType")
.build())
.build())
.addTypes(
SchemaTypeConfigProto.newBuilder()
.setSchemaType("package$newDatabase/RefType")
.build())
.build();
existingTypes.addAll(expectedSchema.getTypesList());
assertThat(existingSchemaBuilder.getTypesList()).containsExactlyElementsIn(existingTypes);
}
/**
* Ensure that we track all types that were rewritten in the input schema. Even if they were not
* technically "added" to the existing schema.
*/
@Test
public void testRewriteSchema_rewriteType() throws Exception {
SchemaProto.Builder existingSchemaBuilder =
SchemaProto.newBuilder()
.addTypes(
SchemaTypeConfigProto.newBuilder()
.setSchemaType("package$existingDatabase/Foo")
.build());
SchemaProto newSchema =
SchemaProto.newBuilder()
.addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("Foo").build())
.build();
AppSearchImpl.RewrittenSchemaResults rewrittenSchemaResults =
AppSearchImpl.rewriteSchema(
createPrefix("package", "existingDatabase"),
existingSchemaBuilder,
newSchema);
// Nothing was removed, but the method did rewrite the type name.
assertThat(rewrittenSchemaResults.mRewrittenPrefixedTypes.keySet())
.containsExactly("package$existingDatabase/Foo");
assertThat(rewrittenSchemaResults.mDeletedPrefixedTypes).isEmpty();
// Same schema since nothing was added.
SchemaProto expectedSchema = existingSchemaBuilder.build();
assertThat(existingSchemaBuilder.getTypesList())
.containsExactlyElementsIn(expectedSchema.getTypesList());
}
/**
* Ensure that we track which types from the existing schema are deleted when a new schema is
* set.
*/
@Test
public void testRewriteSchema_deleteType() throws Exception {
SchemaProto.Builder existingSchemaBuilder =
SchemaProto.newBuilder()
.addTypes(
SchemaTypeConfigProto.newBuilder()
.setSchemaType("package$existingDatabase/Foo")
.build());
SchemaProto newSchema =
SchemaProto.newBuilder()
.addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("Bar").build())
.build();
AppSearchImpl.RewrittenSchemaResults rewrittenSchemaResults =
AppSearchImpl.rewriteSchema(
createPrefix("package", "existingDatabase"),
existingSchemaBuilder,
newSchema);
// Bar type was rewritten, but Foo ended up being deleted since it wasn't included in the
// new schema.
assertThat(rewrittenSchemaResults.mRewrittenPrefixedTypes)
.containsKey("package$existingDatabase/Bar");
assertThat(rewrittenSchemaResults.mRewrittenPrefixedTypes.keySet().size()).isEqualTo(1);
assertThat(rewrittenSchemaResults.mDeletedPrefixedTypes)
.containsExactly("package$existingDatabase/Foo");
// Same schema since nothing was added.
SchemaProto expectedSchema =
SchemaProto.newBuilder()
.addTypes(
SchemaTypeConfigProto.newBuilder()
.setSchemaType("package$existingDatabase/Bar")
.build())
.build();
assertThat(existingSchemaBuilder.getTypesList())
.containsExactlyElementsIn(expectedSchema.getTypesList());
}
@Test
public void testAddDocumentTypePrefix() {
DocumentProto insideDocument =
DocumentProto.newBuilder()
.setUri("inside-id")
.setSchema("type")
.setNamespace("namespace")
.build();
DocumentProto documentProto =
DocumentProto.newBuilder()
.setUri("id")
.setSchema("type")
.setNamespace("namespace")
.addProperties(PropertyProto.newBuilder().addDocumentValues(insideDocument))
.build();
DocumentProto expectedInsideDocument =
DocumentProto.newBuilder()
.setUri("inside-id")
.setSchema("package$databaseName/type")
.setNamespace("package$databaseName/namespace")
.build();
DocumentProto expectedDocumentProto =
DocumentProto.newBuilder()
.setUri("id")
.setSchema("package$databaseName/type")
.setNamespace("package$databaseName/namespace")
.addProperties(
PropertyProto.newBuilder()
.addDocumentValues(expectedInsideDocument))
.build();
DocumentProto.Builder actualDocument = documentProto.toBuilder();
addPrefixToDocument(actualDocument, createPrefix("package", "databaseName"));
assertThat(actualDocument.build()).isEqualTo(expectedDocumentProto);
}
@Test
public void testRemoveDocumentTypePrefixes() throws Exception {
DocumentProto insideDocument =
DocumentProto.newBuilder()
.setUri("inside-id")
.setSchema("package$databaseName/type")
.setNamespace("package$databaseName/namespace")
.build();
DocumentProto documentProto =
DocumentProto.newBuilder()
.setUri("id")
.setSchema("package$databaseName/type")
.setNamespace("package$databaseName/namespace")
.addProperties(PropertyProto.newBuilder().addDocumentValues(insideDocument))
.build();
DocumentProto expectedInsideDocument =
DocumentProto.newBuilder()
.setUri("inside-id")
.setSchema("type")
.setNamespace("namespace")
.build();
DocumentProto expectedDocumentProto =
DocumentProto.newBuilder()
.setUri("id")
.setSchema("type")
.setNamespace("namespace")
.addProperties(
PropertyProto.newBuilder()
.addDocumentValues(expectedInsideDocument))
.build();
DocumentProto.Builder actualDocument = documentProto.toBuilder();
assertThat(removePrefixesFromDocument(actualDocument)).isEqualTo("package$databaseName/");
assertThat(actualDocument.build()).isEqualTo(expectedDocumentProto);
}
@Test
public void testRemoveDatabasesFromDocumentThrowsException() {
// Set two different database names in the document, which should never happen
DocumentProto documentProto =
DocumentProto.newBuilder()
.setUri("id")
.setSchema("prefix1/type")
.setNamespace("prefix2/namespace")
.build();
DocumentProto.Builder actualDocument = documentProto.toBuilder();
AppSearchException e =
assertThrows(
AppSearchException.class, () -> removePrefixesFromDocument(actualDocument));
assertThat(e).hasMessageThat().contains("Found unexpected multiple prefix names");
}
@Test
public void testNestedRemoveDatabasesFromDocumentThrowsException() {
// Set two different database names in the outer and inner document, which should never
// happen.
DocumentProto insideDocument =
DocumentProto.newBuilder()
.setUri("inside-id")
.setSchema("prefix1/type")
.setNamespace("prefix1/namespace")
.build();
DocumentProto documentProto =
DocumentProto.newBuilder()
.setUri("id")
.setSchema("prefix2/type")
.setNamespace("prefix2/namespace")
.addProperties(PropertyProto.newBuilder().addDocumentValues(insideDocument))
.build();
DocumentProto.Builder actualDocument = documentProto.toBuilder();
AppSearchException e =
assertThrows(
AppSearchException.class, () -> removePrefixesFromDocument(actualDocument));
assertThat(e).hasMessageThat().contains("Found unexpected multiple prefix names");
}
@Test
public void testTriggerCheckOptimizeByMutationSize() throws Exception {
// Insert schema
List<AppSearchSchema> schemas =
Collections.singletonList(new AppSearchSchema.Builder("type").build());
mAppSearchImpl.setSchema(
"package",
"database",
schemas,
/*visibilityStore=*/ null,
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
/*version=*/ 0);
// Insert a document and then remove it to generate garbage.
GenericDocument document = new GenericDocument.Builder<>("namespace", "id", "type").build();
mAppSearchImpl.putDocument("package", "database", document, /*logger=*/ null);
mAppSearchImpl.remove(
"package", "database", "namespace", "id", /*removeStatsBuilder=*/ null);
// Verify there is garbage documents.
GetOptimizeInfoResultProto optimizeInfo = mAppSearchImpl.getOptimizeInfoResultLocked();
assertThat(optimizeInfo.getOptimizableDocs()).isEqualTo(1);
// Increase mutation counter and stop before reach the threshold
mAppSearchImpl.checkForOptimize(
AppSearchImpl.CHECK_OPTIMIZE_INTERVAL - 1, /*builder=*/ null);
// Verify the optimize() isn't triggered.
optimizeInfo = mAppSearchImpl.getOptimizeInfoResultLocked();
assertThat(optimizeInfo.getOptimizableDocs()).isEqualTo(1);
// Increase the counter and reach the threshold, optimize() should be triggered.
OptimizeStats.Builder builder = new OptimizeStats.Builder();
mAppSearchImpl.checkForOptimize(/*mutateBatchSize=*/ 1, builder);
// Verify optimize() is triggered.
optimizeInfo = mAppSearchImpl.getOptimizeInfoResultLocked();
assertThat(optimizeInfo.getOptimizableDocs()).isEqualTo(0);
assertThat(optimizeInfo.getEstimatedOptimizableBytes()).isEqualTo(0);
// Verify the stats have been set.
OptimizeStats oStats = builder.build();
assertThat(oStats.getOriginalDocumentCount()).isEqualTo(1);
assertThat(oStats.getDeletedDocumentCount()).isEqualTo(1);
}
@Test
public void testReset() throws Exception {
// Setup the index
Context context = ApplicationProvider.getApplicationContext();
File appsearchDir = mTemporaryFolder.newFolder();
AppSearchImpl appSearchImpl =
AppSearchImpl.create(
appsearchDir,
new UnlimitedLimitConfig(),
/*initStatsBuilder=*/ null,
ALWAYS_OPTIMIZE);
// Insert schema
List<AppSearchSchema> schemas =
ImmutableList.of(
new AppSearchSchema.Builder("Type1").build(),
new AppSearchSchema.Builder("Type2").build());
appSearchImpl.setSchema(
context.getPackageName(),
"database1",
schemas,
/*visibilityStore=*/ null,
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
/*version=*/ 0);
// Insert a valid doc
GenericDocument validDoc =
new GenericDocument.Builder<>("namespace1", "id1", "Type1").build();
appSearchImpl.putDocument(
context.getPackageName(), "database1", validDoc, /*logger=*/ null);
// Query it via global query. We use the same code again later so this is to make sure we
// have our global query configured right.
SearchResultPage results =
appSearchImpl.globalQuery(
/*queryExpression=*/ "",
new SearchSpec.Builder().addFilterSchemas("Type1").build(),
context.getPackageName(),
/*visibilityStore=*/ null,
Process.INVALID_UID,
/*callerHasSystemAccess=*/ false,
/*logger=*/ null);
assertThat(results.getResults()).hasSize(1);
assertThat(results.getResults().get(0).getGenericDocument()).isEqualTo(validDoc);
// Create a doc with a malformed namespace
DocumentProto invalidDoc =
DocumentProto.newBuilder()
.setNamespace("invalidNamespace")
.setUri("id2")
.setSchema(context.getPackageName() + "$database1/Type1")
.build();
AppSearchException e =
assertThrows(
AppSearchException.class,
() -> PrefixUtil.getPrefix(invalidDoc.getNamespace()));
assertThat(e)
.hasMessageThat()
.isEqualTo(
"The prefixed value \"invalidNamespace\" doesn't contain a valid database"
+ " name");
// Insert the invalid doc with an invalid namespace right into icing
PutResultProto putResultProto = appSearchImpl.mIcingSearchEngineLocked.put(invalidDoc);
assertThat(putResultProto.getStatus().getCode()).isEqualTo(StatusProto.Code.OK);
// Initialize AppSearchImpl. This should cause a reset.
InitializeStats.Builder initStatsBuilder = new InitializeStats.Builder();
appSearchImpl.close();
appSearchImpl =
AppSearchImpl.create(
appsearchDir,
new UnlimitedLimitConfig(),
initStatsBuilder,
ALWAYS_OPTIMIZE);
// Check recovery state
InitializeStats initStats = initStatsBuilder.build();
assertThat(initStats).isNotNull();
assertThat(initStats.getStatusCode()).isEqualTo(AppSearchResult.RESULT_INTERNAL_ERROR);
assertThat(initStats.hasDeSync()).isFalse();
assertThat(initStats.getDocumentStoreRecoveryCause())
.isEqualTo(InitializeStats.RECOVERY_CAUSE_NONE);
assertThat(initStats.getIndexRestorationCause())
.isEqualTo(InitializeStats.RECOVERY_CAUSE_NONE);
assertThat(initStats.getSchemaStoreRecoveryCause())
.isEqualTo(InitializeStats.RECOVERY_CAUSE_NONE);
assertThat(initStats.getDocumentStoreDataStatus())
.isEqualTo(InitializeStats.DOCUMENT_STORE_DATA_STATUS_NO_DATA_LOSS);
assertThat(initStats.hasReset()).isTrue();
assertThat(initStats.getResetStatusCode()).isEqualTo(AppSearchResult.RESULT_OK);
// Make sure all our data is gone
assertThat(appSearchImpl.getSchema(context.getPackageName(), "database1").getSchemas())
.isEmpty();
results =
appSearchImpl.globalQuery(
/*queryExpression=*/ "",
new SearchSpec.Builder().addFilterSchemas("Type1").build(),
context.getPackageName(),
/*visibilityStore=*/ null,
Process.INVALID_UID,
/*callerHasSystemAccess=*/ false,
/*logger=*/ null);
assertThat(results.getResults()).isEmpty();
// Make sure the index can now be used successfully
appSearchImpl.setSchema(
context.getPackageName(),
"database1",
Collections.singletonList(new AppSearchSchema.Builder("Type1").build()),
/*visibilityStore=*/ null,
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
/*version=*/ 0);
// Insert a valid doc
appSearchImpl.putDocument(
context.getPackageName(), "database1", validDoc, /*logger=*/ null);
// Query it via global query.
results =
appSearchImpl.globalQuery(
/*queryExpression=*/ "",
new SearchSpec.Builder().addFilterSchemas("Type1").build(),
context.getPackageName(),
/*visibilityStore=*/ null,
Process.INVALID_UID,
/*callerHasSystemAccess=*/ false,
/*logger=*/ null);
assertThat(results.getResults()).hasSize(1);
assertThat(results.getResults().get(0).getGenericDocument()).isEqualTo(validDoc);
}
@Test
public void testRewriteSearchSpec_oneInstance() throws Exception {
SearchSpecProto.Builder searchSpecProto = SearchSpecProto.newBuilder().setQuery("");
// Insert schema
List<AppSearchSchema> schemas =
Collections.singletonList(new AppSearchSchema.Builder("type").build());
mAppSearchImpl.setSchema(
"package",
"database",
schemas,
/*visibilityStore=*/ null,
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
/*version=*/ 0);
// Insert document
GenericDocument document = new GenericDocument.Builder<>("namespace", "id", "type").build();
mAppSearchImpl.putDocument("package", "database", document, /*logger=*/ null);
// Rewrite SearchSpec
mAppSearchImpl.rewriteSearchSpecForPrefixesLocked(
searchSpecProto,
Collections.singleton(createPrefix("package", "database")),
ImmutableSet.of("package$database/type"));
assertThat(searchSpecProto.getSchemaTypeFiltersList())
.containsExactly("package$database/type");
assertThat(searchSpecProto.getNamespaceFiltersList())
.containsExactly("package$database/namespace");
}
@Test
public void testRewriteSearchSpec_twoInstances() throws Exception {
SearchSpecProto.Builder searchSpecProto = SearchSpecProto.newBuilder().setQuery("");
// Insert schema
List<AppSearchSchema> schemas =
ImmutableList.of(
new AppSearchSchema.Builder("typeA").build(),
new AppSearchSchema.Builder("typeB").build());
mAppSearchImpl.setSchema(
"package",
"database1",
schemas,
/*visibilityStore=*/ null,
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
/*version=*/ 0);
mAppSearchImpl.setSchema(
"package",
"database2",
schemas,
/*visibilityStore=*/ null,
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
/*version=*/ 0);
// Insert documents
GenericDocument document1 =
new GenericDocument.Builder<>("namespace", "id", "typeA").build();
mAppSearchImpl.putDocument("package", "database1", document1, /*logger=*/ null);
GenericDocument document2 =
new GenericDocument.Builder<>("namespace", "id", "typeB").build();
mAppSearchImpl.putDocument("package", "database2", document2, /*logger=*/ null);
// Rewrite SearchSpec
mAppSearchImpl.rewriteSearchSpecForPrefixesLocked(
searchSpecProto,
ImmutableSet.of(
createPrefix("package", "database1"), createPrefix("package", "database2")),
ImmutableSet.of(
"package$database1/typeA", "package$database1/typeB",
"package$database2/typeA", "package$database2/typeB"));
assertThat(searchSpecProto.getSchemaTypeFiltersList())
.containsExactly(
"package$database1/typeA",
"package$database1/typeB",
"package$database2/typeA",
"package$database2/typeB");
assertThat(searchSpecProto.getNamespaceFiltersList())
.containsExactly("package$database1/namespace", "package$database2/namespace");
}
@Test
public void testRewriteSearchSpec_ignoresSearchSpecSchemaFilters() throws Exception {
SearchSpecProto.Builder searchSpecProto =
SearchSpecProto.newBuilder().setQuery("").addSchemaTypeFilters("type");
// Insert schema
List<AppSearchSchema> schemas =
Collections.singletonList(new AppSearchSchema.Builder("type").build());
mAppSearchImpl.setSchema(
"package",
"database",
schemas,
/*visibilityStore=*/ null,
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
/*version=*/ 0);
// Insert document
GenericDocument document = new GenericDocument.Builder<>("namespace", "id", "type").build();
mAppSearchImpl.putDocument("package", "database", document, /*logger=*/ null);
// If 'allowedPrefixedSchemas' is empty, this returns false since there's nothing to
// search over. Despite the searchSpecProto having schema type filters.
assertThat(
mAppSearchImpl.rewriteSearchSpecForPrefixesLocked(
searchSpecProto,
Collections.singleton(createPrefix("package", "database")),
/*allowedPrefixedSchemas=*/ Collections.emptySet()))
.isFalse();
}
@Test
public void testQueryEmptyDatabase() throws Exception {
SearchSpec searchSpec =
new SearchSpec.Builder().setTermMatch(TermMatchType.Code.PREFIX_VALUE).build();
SearchResultPage searchResultPage =
mAppSearchImpl.query("package", "EmptyDatabase", "", searchSpec, /*logger=*/ null);
assertThat(searchResultPage.getResults()).isEmpty();
}
/**
* TODO(b/169883602): This should be an integration test at the cts-level. This is a short-term
* test until we have official support for multiple-apps indexing at once.
*/
@Test
public void testQueryWithMultiplePackages_noPackageFilters() throws Exception {
// Insert package1 schema
List<AppSearchSchema> schema1 =
ImmutableList.of(new AppSearchSchema.Builder("schema1").build());
mAppSearchImpl.setSchema(
"package1",
"database1",
schema1,
/*visibilityStore=*/ null,
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
/*version=*/ 0);
// Insert package2 schema
List<AppSearchSchema> schema2 =
ImmutableList.of(new AppSearchSchema.Builder("schema2").build());
mAppSearchImpl.setSchema(
"package2",
"database2",
schema2,
/*visibilityStore=*/ null,
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
/*version=*/ 0);
// Insert package1 document
GenericDocument document =
new GenericDocument.Builder<>("namespace", "id", "schema1").build();
mAppSearchImpl.putDocument("package1", "database1", document, /*logger=*/ null);
// No query filters specified, package2 shouldn't be able to query for package1's documents.
SearchSpec searchSpec =
new SearchSpec.Builder().setTermMatch(TermMatchType.Code.PREFIX_VALUE).build();
SearchResultPage searchResultPage =
mAppSearchImpl.query("package2", "database2", "", searchSpec, /*logger=*/ null);
assertThat(searchResultPage.getResults()).isEmpty();
// Insert package2 document
document = new GenericDocument.Builder<>("namespace", "id", "schema2").build();
mAppSearchImpl.putDocument("package2", "database2", document, /*logger=*/ null);
// No query filters specified. package2 should only get its own documents back.
searchResultPage =
mAppSearchImpl.query("package2", "database2", "", searchSpec, /*logger=
*/ null);
assertThat(searchResultPage.getResults()).hasSize(1);
assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document);
}
/**
* TODO(b/169883602): This should be an integration test at the cts-level. This is a short-term
* test until we have official support for multiple-apps indexing at once.
*/
@Test
public void testQueryWithMultiplePackages_withPackageFilters() throws Exception {
// Insert package1 schema
List<AppSearchSchema> schema1 =
ImmutableList.of(new AppSearchSchema.Builder("schema1").build());
mAppSearchImpl.setSchema(
"package1",
"database1",
schema1,
/*visibilityStore=*/ null,
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
/*version=*/ 0);
// Insert package2 schema
List<AppSearchSchema> schema2 =
ImmutableList.of(new AppSearchSchema.Builder("schema2").build());
mAppSearchImpl.setSchema(
"package2",
"database2",
schema2,
/*visibilityStore=*/ null,
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
/*version=*/ 0);
// Insert package1 document
GenericDocument document =
new GenericDocument.Builder<>("namespace", "id", "schema1").build();
mAppSearchImpl.putDocument("package1", "database1", document, /*logger=*/ null);
// "package1" filter specified, but package2 shouldn't be able to query for package1's
// documents.
SearchSpec searchSpec =
new SearchSpec.Builder()
.setTermMatch(TermMatchType.Code.PREFIX_VALUE)
.addFilterPackageNames("package1")
.build();
SearchResultPage searchResultPage =
mAppSearchImpl.query("package2", "database2", "", searchSpec, /*logger=*/ null);
assertThat(searchResultPage.getResults()).isEmpty();
// Insert package2 document
document = new GenericDocument.Builder<>("namespace", "id", "schema2").build();
mAppSearchImpl.putDocument("package2", "database2", document, /*logger=*/ null);
// "package2" filter specified, package2 should only get its own documents back.
searchSpec =
new SearchSpec.Builder()
.setTermMatch(TermMatchType.Code.PREFIX_VALUE)
.addFilterPackageNames("package2")
.build();
searchResultPage =
mAppSearchImpl.query("package2", "database2", "", searchSpec, /*logger=
*/ null);
assertThat(searchResultPage.getResults()).hasSize(1);
assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document);
}
@Test
public void testGlobalQueryEmptyDatabase() throws Exception {
SearchSpec searchSpec =
new SearchSpec.Builder().setTermMatch(TermMatchType.Code.PREFIX_VALUE).build();
SearchResultPage searchResultPage =
mAppSearchImpl.globalQuery(
"",
searchSpec,
/*callerPackageName=*/ "",
/*visibilityStore=*/ null,
Process.INVALID_UID,
/*callerHasSystemAccess=*/ false,
/*logger=*/ null);
assertThat(searchResultPage.getResults()).isEmpty();
}
@Test
public void testGetNextPageToken_query() throws Exception {
// Insert package1 schema
List<AppSearchSchema> schema1 =
ImmutableList.of(new AppSearchSchema.Builder("schema1").build());
mAppSearchImpl.setSchema(
"package1",
"database1",
schema1,
/*visibilityStore=*/ null,
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
/*version=*/ 0);
// Insert two package1 documents
GenericDocument document1 =
new GenericDocument.Builder<>("namespace", "id1", "schema1").build();
GenericDocument document2 =
new GenericDocument.Builder<>("namespace", "id2", "schema1").build();
mAppSearchImpl.putDocument("package1", "database1", document1, /*logger=*/ null);
mAppSearchImpl.putDocument("package1", "database1", document2, /*logger=*/ null);
// Query for only 1 result per page
SearchSpec searchSpec =
new SearchSpec.Builder()
.setTermMatch(TermMatchType.Code.PREFIX_VALUE)
.setResultCountPerPage(1)
.build();
SearchResultPage searchResultPage =
mAppSearchImpl.query("package1", "database1", "", searchSpec, /*logger=*/ null);
// Document2 will come first because it was inserted last and default return order is
// most recent.
assertThat(searchResultPage.getResults()).hasSize(1);
assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document2);
long nextPageToken = searchResultPage.getNextPageToken();
searchResultPage = mAppSearchImpl.getNextPage("package1", nextPageToken);
assertThat(searchResultPage.getResults()).hasSize(1);
assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document1);
}
@Test
public void testGetNextPageWithDifferentPackage_query() throws Exception {
// Insert package1 schema
List<AppSearchSchema> schema1 =
ImmutableList.of(new AppSearchSchema.Builder("schema1").build());
mAppSearchImpl.setSchema(
"package1",
"database1",
schema1,
/*visibilityStore=*/ null,
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
/*version=*/ 0);
// Insert two package1 documents
GenericDocument document1 =
new GenericDocument.Builder<>("namespace", "id1", "schema1").build();
GenericDocument document2 =
new GenericDocument.Builder<>("namespace", "id2", "schema1").build();
mAppSearchImpl.putDocument("package1", "database1", document1, /*logger=*/ null);
mAppSearchImpl.putDocument("package1", "database1", document2, /*logger=*/ null);
// Query for only 1 result per page
SearchSpec searchSpec =
new SearchSpec.Builder()
.setTermMatch(TermMatchType.Code.PREFIX_VALUE)
.setResultCountPerPage(1)
.build();
SearchResultPage searchResultPage =
mAppSearchImpl.query("package1", "database1", "", searchSpec, /*logger=*/ null);
// Document2 will come first because it was inserted last and default return order is
// most recent.
assertThat(searchResultPage.getResults()).hasSize(1);
assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document2);
long nextPageToken = searchResultPage.getNextPageToken();
// Try getting next page with the wrong package, package2
AppSearchException e =
assertThrows(
AppSearchException.class,
() -> mAppSearchImpl.getNextPage("package2", nextPageToken));
assertThat(e)
.hasMessageThat()
.contains("Package \"package2\" cannot use nextPageToken: " + nextPageToken);
assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_SECURITY_ERROR);
// Can continue getting next page for package1
searchResultPage = mAppSearchImpl.getNextPage("package1", nextPageToken);
assertThat(searchResultPage.getResults()).hasSize(1);
assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document1);
}
@Test
public void testGetNextPageToken_globalQuery() throws Exception {
// Insert package1 schema
List<AppSearchSchema> schema1 =
ImmutableList.of(new AppSearchSchema.Builder("schema1").build());
mAppSearchImpl.setSchema(
"package1",
"database1",
schema1,
/*visibilityStore=*/ null,
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
/*version=*/ 0);
// Insert two package1 documents
GenericDocument document1 =
new GenericDocument.Builder<>("namespace", "id1", "schema1").build();
GenericDocument document2 =
new GenericDocument.Builder<>("namespace", "id2", "schema1").build();
mAppSearchImpl.putDocument("package1", "database1", document1, /*logger=*/ null);
mAppSearchImpl.putDocument("package1", "database1", document2, /*logger=*/ null);
// Query for only 1 result per page
SearchSpec searchSpec =
new SearchSpec.Builder()
.setTermMatch(TermMatchType.Code.PREFIX_VALUE)
.setResultCountPerPage(1)
.build();
SearchResultPage searchResultPage =
mAppSearchImpl.globalQuery(
/*queryExpression=*/ "",
searchSpec,
"package1",
/*visibilityStore=*/ null,
Process.myUid(),
/*callerHasSystemAccess=*/ false,
/*logger=*/ null);
// Document2 will come first because it was inserted last and default return order is
// most recent.
assertThat(searchResultPage.getResults()).hasSize(1);
assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document2);
long nextPageToken = searchResultPage.getNextPageToken();
searchResultPage = mAppSearchImpl.getNextPage("package1", nextPageToken);
assertThat(searchResultPage.getResults()).hasSize(1);
assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document1);
}
@Test
public void testGetNextPageWithDifferentPackage_globalQuery() throws Exception {
// Insert package1 schema
List<AppSearchSchema> schema1 =
ImmutableList.of(new AppSearchSchema.Builder("schema1").build());
mAppSearchImpl.setSchema(
"package1",
"database1",
schema1,
/*visibilityStore=*/ null,
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
/*version=*/ 0);
// Insert two package1 documents
GenericDocument document1 =
new GenericDocument.Builder<>("namespace", "id1", "schema1").build();
GenericDocument document2 =
new GenericDocument.Builder<>("namespace", "id2", "schema1").build();
mAppSearchImpl.putDocument("package1", "database1", document1, /*logger=*/ null);
mAppSearchImpl.putDocument("package1", "database1", document2, /*logger=*/ null);
// Query for only 1 result per page
SearchSpec searchSpec =
new SearchSpec.Builder()
.setTermMatch(TermMatchType.Code.PREFIX_VALUE)
.setResultCountPerPage(1)
.build();
SearchResultPage searchResultPage =
mAppSearchImpl.globalQuery(
/*queryExpression=*/ "",
searchSpec,
"package1",
/*visibilityStore=*/ null,
Process.myUid(),
/*callerHasSystemAccess=*/ false,
/*logger=*/ null);
// Document2 will come first because it was inserted last and default return order is
// most recent.
assertThat(searchResultPage.getResults()).hasSize(1);
assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document2);
long nextPageToken = searchResultPage.getNextPageToken();
// Try getting next page with the wrong package, package2
AppSearchException e =
assertThrows(
AppSearchException.class,
() -> mAppSearchImpl.getNextPage("package2", nextPageToken));
assertThat(e)
.hasMessageThat()
.contains("Package \"package2\" cannot use nextPageToken: " + nextPageToken);
assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_SECURITY_ERROR);
// Can continue getting next page for package1
searchResultPage = mAppSearchImpl.getNextPage("package1", nextPageToken);
assertThat(searchResultPage.getResults()).hasSize(1);
assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document1);
}
@Test
public void testInvalidateNextPageToken_query() throws Exception {
// Insert package1 schema
List<AppSearchSchema> schema1 =
ImmutableList.of(new AppSearchSchema.Builder("schema1").build());
mAppSearchImpl.setSchema(
"package1",
"database1",
schema1,
/*visibilityStore=*/ null,
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
/*version=*/ 0);
// Insert two package1 documents
GenericDocument document1 =
new GenericDocument.Builder<>("namespace", "id1", "schema1").build();
GenericDocument document2 =
new GenericDocument.Builder<>("namespace", "id2", "schema1").build();
mAppSearchImpl.putDocument("package1", "database1", document1, /*logger=*/ null);
mAppSearchImpl.putDocument("package1", "database1", document2, /*logger=*/ null);
// Query for only 1 result per page
SearchSpec searchSpec =
new SearchSpec.Builder()
.setTermMatch(TermMatchType.Code.PREFIX_VALUE)
.setResultCountPerPage(1)
.build();
SearchResultPage searchResultPage =
mAppSearchImpl.query("package1", "database1", "", searchSpec, /*logger=*/ null);
// Document2 will come first because it was inserted last and default return order is
// most recent.
assertThat(searchResultPage.getResults()).hasSize(1);
assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document2);
long nextPageToken = searchResultPage.getNextPageToken();
// Invalidate the token
mAppSearchImpl.invalidateNextPageToken("package1", nextPageToken);
// Can't get next page because we invalidated the token.
AppSearchException e =
assertThrows(
AppSearchException.class,
() -> mAppSearchImpl.getNextPage("package1", nextPageToken));
assertThat(e)
.hasMessageThat()
.contains("Package \"package1\" cannot use nextPageToken: " + nextPageToken);
assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_SECURITY_ERROR);
}
@Test
public void testInvalidateNextPageTokenWithDifferentPackage_query() throws Exception {
// Insert package1 schema
List<AppSearchSchema> schema1 =
ImmutableList.of(new AppSearchSchema.Builder("schema1").build());
mAppSearchImpl.setSchema(
"package1",
"database1",
schema1,
/*visibilityStore=*/ null,
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
/*version=*/ 0);
// Insert two package1 documents
GenericDocument document1 =
new GenericDocument.Builder<>("namespace", "id1", "schema1").build();
GenericDocument document2 =
new GenericDocument.Builder<>("namespace", "id2", "schema1").build();
mAppSearchImpl.putDocument("package1", "database1", document1, /*logger=*/ null);
mAppSearchImpl.putDocument("package1", "database1", document2, /*logger=*/ null);
// Query for only 1 result per page
SearchSpec searchSpec =
new SearchSpec.Builder()
.setTermMatch(TermMatchType.Code.PREFIX_VALUE)
.setResultCountPerPage(1)
.build();
SearchResultPage searchResultPage =
mAppSearchImpl.query("package1", "database1", "", searchSpec, /*logger=*/ null);
// Document2 will come first because it was inserted last and default return order is
// most recent.
assertThat(searchResultPage.getResults()).hasSize(1);
assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document2);
long nextPageToken = searchResultPage.getNextPageToken();
// Try getting next page with the wrong package, package2
AppSearchException e =
assertThrows(
AppSearchException.class,
() -> mAppSearchImpl.invalidateNextPageToken("package2", nextPageToken));
assertThat(e)
.hasMessageThat()
.contains("Package \"package2\" cannot use nextPageToken: " + nextPageToken);
assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_SECURITY_ERROR);
// Can continue getting next page for package1
searchResultPage = mAppSearchImpl.getNextPage("package1", nextPageToken);
assertThat(searchResultPage.getResults()).hasSize(1);
assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document1);
}
@Test
public void testInvalidateNextPageToken_globalQuery() throws Exception {
// Insert package1 schema
List<AppSearchSchema> schema1 =
ImmutableList.of(new AppSearchSchema.Builder("schema1").build());
mAppSearchImpl.setSchema(
"package1",
"database1",
schema1,
/*visibilityStore=*/ null,
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
/*version=*/ 0);
// Insert two package1 documents
GenericDocument document1 =
new GenericDocument.Builder<>("namespace", "id1", "schema1").build();
GenericDocument document2 =
new GenericDocument.Builder<>("namespace", "id2", "schema1").build();
mAppSearchImpl.putDocument("package1", "database1", document1, /*logger=*/ null);
mAppSearchImpl.putDocument("package1", "database1", document2, /*logger=*/ null);
// Query for only 1 result per page
SearchSpec searchSpec =
new SearchSpec.Builder()
.setTermMatch(TermMatchType.Code.PREFIX_VALUE)
.setResultCountPerPage(1)
.build();
SearchResultPage searchResultPage =
mAppSearchImpl.globalQuery(
/*queryExpression=*/ "",
searchSpec,
"package1",
/*visibilityStore=*/ null,
Process.myUid(),
/*callerHasSystemAccess=*/ false,
/*logger=*/ null);
// Document2 will come first because it was inserted last and default return order is
// most recent.
assertThat(searchResultPage.getResults()).hasSize(1);
assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document2);
long nextPageToken = searchResultPage.getNextPageToken();
// Invalidate the token
mAppSearchImpl.invalidateNextPageToken("package1", nextPageToken);
// Can't get next page because we invalidated the token.
AppSearchException e =
assertThrows(
AppSearchException.class,
() -> mAppSearchImpl.getNextPage("package1", nextPageToken));
assertThat(e)
.hasMessageThat()
.contains("Package \"package1\" cannot use nextPageToken: " + nextPageToken);
assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_SECURITY_ERROR);
}
@Test
public void testInvalidateNextPageTokenWithDifferentPackage_globalQuery() throws Exception {
// Insert package1 schema
List<AppSearchSchema> schema1 =
ImmutableList.of(new AppSearchSchema.Builder("schema1").build());
mAppSearchImpl.setSchema(
"package1",
"database1",
schema1,
/*visibilityStore=*/ null,
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
/*version=*/ 0);
// Insert two package1 documents
GenericDocument document1 =
new GenericDocument.Builder<>("namespace", "id1", "schema1").build();
GenericDocument document2 =
new GenericDocument.Builder<>("namespace", "id2", "schema1").build();
mAppSearchImpl.putDocument("package1", "database1", document1, /*logger=*/ null);
mAppSearchImpl.putDocument("package1", "database1", document2, /*logger=*/ null);
// Query for only 1 result per page
SearchSpec searchSpec =
new SearchSpec.Builder()
.setTermMatch(TermMatchType.Code.PREFIX_VALUE)
.setResultCountPerPage(1)
.build();
SearchResultPage searchResultPage =
mAppSearchImpl.globalQuery(
/*queryExpression=*/ "",
searchSpec,
"package1",
/*visibilityStore=*/ null,
Process.myUid(),
/*callerHasSystemAccess=*/ false,
/*logger=*/ null);
// Document2 will come first because it was inserted last and default return order is
// most recent.
assertThat(searchResultPage.getResults()).hasSize(1);
assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document2);
long nextPageToken = searchResultPage.getNextPageToken();
// Try getting next page with the wrong package, package2
AppSearchException e =
assertThrows(
AppSearchException.class,
() -> mAppSearchImpl.invalidateNextPageToken("package2", nextPageToken));
assertThat(e)
.hasMessageThat()
.contains("Package \"package2\" cannot use nextPageToken: " + nextPageToken);
assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_SECURITY_ERROR);
// Can continue getting next page for package1
searchResultPage = mAppSearchImpl.getNextPage("package1", nextPageToken);
assertThat(searchResultPage.getResults()).hasSize(1);
assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document1);
}
@Test
public void testRemoveEmptyDatabase_noExceptionThrown() throws Exception {
SearchSpec searchSpec =
new SearchSpec.Builder()
.addFilterSchemas("FakeType")
.setTermMatch(TermMatchType.Code.PREFIX_VALUE)
.build();
mAppSearchImpl.removeByQuery(
"package", "EmptyDatabase", "", searchSpec, /*statsBuilder=*/ null);
searchSpec =
new SearchSpec.Builder()
.addFilterNamespaces("FakeNamespace")
.setTermMatch(TermMatchType.Code.PREFIX_VALUE)
.build();
mAppSearchImpl.removeByQuery(
"package", "EmptyDatabase", "", searchSpec, /*statsBuilder=*/ null);
searchSpec = new SearchSpec.Builder().setTermMatch(TermMatchType.Code.PREFIX_VALUE).build();
mAppSearchImpl.removeByQuery(
"package", "EmptyDatabase", "", searchSpec, /*statsBuilder=*/ null);
}
@Test
public void testSetSchema() throws Exception {
List<SchemaTypeConfigProto> existingSchemas =
mAppSearchImpl.getSchemaProtoLocked().getTypesList();
List<AppSearchSchema> schemas =
Collections.singletonList(new AppSearchSchema.Builder("Email").build());
// Set schema Email to AppSearch database1
mAppSearchImpl.setSchema(
"package",
"database1",
schemas,
/*visibilityStore=*/ null,
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
/*version=*/ 0);
// Create expected schemaType proto.
SchemaProto expectedProto =
SchemaProto.newBuilder()
.addTypes(
SchemaTypeConfigProto.newBuilder()
.setSchemaType("package$database1/Email")
.setVersion(0))
.build();
List<SchemaTypeConfigProto> expectedTypes = new ArrayList<>();
expectedTypes.addAll(existingSchemas);
expectedTypes.addAll(expectedProto.getTypesList());
assertThat(mAppSearchImpl.getSchemaProtoLocked().getTypesList())
.containsExactlyElementsIn(expectedTypes);
}
@Test
public void testSetSchema_incompatible() throws Exception {
List<AppSearchSchema> oldSchemas = new ArrayList<>();
oldSchemas.add(
new AppSearchSchema.Builder("Email")
.addProperty(
new AppSearchSchema.StringPropertyConfig.Builder("foo")
.setCardinality(
AppSearchSchema.PropertyConfig.CARDINALITY_REQUIRED)
.setTokenizerType(
AppSearchSchema.StringPropertyConfig
.TOKENIZER_TYPE_PLAIN)
.setIndexingType(
AppSearchSchema.StringPropertyConfig
.INDEXING_TYPE_PREFIXES)
.build())
.build());
oldSchemas.add(new AppSearchSchema.Builder("Text").build());
// Set schema Email to AppSearch database1
mAppSearchImpl.setSchema(
"package",
"database1",
oldSchemas,
/*visibilityStore=*/ null,
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
/*version=*/ 0);
// Create incompatible schema
List<AppSearchSchema> newSchemas =
Collections.singletonList(new AppSearchSchema.Builder("Email").build());
// set email incompatible and delete text
SetSchemaResponse setSchemaResponse =
mAppSearchImpl.setSchema(
"package",
"database1",
newSchemas,
/*visibilityStore=*/ null,
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ true,
/*version=*/ 0);
assertThat(setSchemaResponse.getDeletedTypes()).containsExactly("Text");
assertThat(setSchemaResponse.getIncompatibleTypes()).containsExactly("Email");
}
@Test
public void testRemoveSchema() throws Exception {
List<SchemaTypeConfigProto> existingSchemas =
mAppSearchImpl.getSchemaProtoLocked().getTypesList();
List<AppSearchSchema> schemas =
ImmutableList.of(
new AppSearchSchema.Builder("Email").build(),
new AppSearchSchema.Builder("Document").build());
// Set schema Email and Document to AppSearch database1
mAppSearchImpl.setSchema(
"package",
"database1",
schemas,
/*visibilityStore=*/ null,
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
/*version=*/ 0);
// Create expected schemaType proto.
SchemaProto expectedProto =
SchemaProto.newBuilder()
.addTypes(
SchemaTypeConfigProto.newBuilder()
.setSchemaType("package$database1/Email")
.setVersion(0))
.addTypes(
SchemaTypeConfigProto.newBuilder()
.setSchemaType("package$database1/Document")
.setVersion(0))
.build();
// Check both schema Email and Document saved correctly.
List<SchemaTypeConfigProto> expectedTypes = new ArrayList<>();
expectedTypes.addAll(existingSchemas);
expectedTypes.addAll(expectedProto.getTypesList());
assertThat(mAppSearchImpl.getSchemaProtoLocked().getTypesList())
.containsExactlyElementsIn(expectedTypes);
final List<AppSearchSchema> finalSchemas =
Collections.singletonList(new AppSearchSchema.Builder("Email").build());
SetSchemaResponse setSchemaResponse =
mAppSearchImpl.setSchema(
"package",
"database1",
finalSchemas,
/*visibilityStore=*/ null,
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
/*version=*/ 0);
// Check the Document type has been deleted.
assertThat(setSchemaResponse.getDeletedTypes()).containsExactly("Document");
// ForceOverride to delete.
mAppSearchImpl.setSchema(
"package",
"database1",
finalSchemas,
/*visibilityStore=*/ null,
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ true,
/*version=*/ 0);
// Check Document schema is removed.
expectedProto =
SchemaProto.newBuilder()
.addTypes(
SchemaTypeConfigProto.newBuilder()
.setSchemaType("package$database1/Email")
.setVersion(0))
.build();
expectedTypes = new ArrayList<>();
expectedTypes.addAll(existingSchemas);
expectedTypes.addAll(expectedProto.getTypesList());
assertThat(mAppSearchImpl.getSchemaProtoLocked().getTypesList())
.containsExactlyElementsIn(expectedTypes);
}
@Test
public void testRemoveSchema_differentDataBase() throws Exception {
List<SchemaTypeConfigProto> existingSchemas =
mAppSearchImpl.getSchemaProtoLocked().getTypesList();
// Create schemas
List<AppSearchSchema> schemas =
ImmutableList.of(
new AppSearchSchema.Builder("Email").build(),
new AppSearchSchema.Builder("Document").build());
// Set schema Email and Document to AppSearch database1 and 2
mAppSearchImpl.setSchema(
"package",
"database1",
schemas,
/*visibilityStore=*/ null,
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
/*version=*/ 0);
mAppSearchImpl.setSchema(
"package",
"database2",
schemas,
/*visibilityStore=*/ null,
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
/*version=*/ 0);
// Create expected schemaType proto.
SchemaProto expectedProto =
SchemaProto.newBuilder()
.addTypes(
SchemaTypeConfigProto.newBuilder()
.setSchemaType("package$database1/Email")
.setVersion(0))
.addTypes(
SchemaTypeConfigProto.newBuilder()
.setSchemaType("package$database1/Document")
.setVersion(0))
.addTypes(
SchemaTypeConfigProto.newBuilder()
.setSchemaType("package$database2/Email")
.setVersion(0))
.addTypes(
SchemaTypeConfigProto.newBuilder()
.setSchemaType("package$database2/Document")
.setVersion(0))
.build();
// Check Email and Document is saved in database 1 and 2 correctly.
List<SchemaTypeConfigProto> expectedTypes = new ArrayList<>();
expectedTypes.addAll(existingSchemas);
expectedTypes.addAll(expectedProto.getTypesList());
assertThat(mAppSearchImpl.getSchemaProtoLocked().getTypesList())
.containsExactlyElementsIn(expectedTypes);
// Save only Email to database1 this time.
schemas = Collections.singletonList(new AppSearchSchema.Builder("Email").build());
mAppSearchImpl.setSchema(
"package",
"database1",
schemas,
/*visibilityStore=*/ null,
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ true,
/*version=*/ 0);
// Create expected schemaType list, database 1 should only contain Email but database 2
// remains in same.
expectedProto =
SchemaProto.newBuilder()
.addTypes(
SchemaTypeConfigProto.newBuilder()
.setSchemaType("package$database1/Email")
.setVersion(0))
.addTypes(
SchemaTypeConfigProto.newBuilder()
.setSchemaType("package$database2/Email")
.setVersion(0))
.addTypes(
SchemaTypeConfigProto.newBuilder()
.setSchemaType("package$database2/Document")
.setVersion(0))
.build();
// Check nothing changed in database2.
expectedTypes = new ArrayList<>();
expectedTypes.addAll(existingSchemas);
expectedTypes.addAll(expectedProto.getTypesList());
assertThat(mAppSearchImpl.getSchemaProtoLocked().getTypesList())
.containsExactlyElementsIn(expectedTypes);
}
@Test
public void testClearPackageData() throws AppSearchException {
List<SchemaTypeConfigProto> existingSchemas =
mAppSearchImpl.getSchemaProtoLocked().getTypesList();
Map<String, Set<String>> existingDatabases = mAppSearchImpl.getPackageToDatabases();
// Insert package schema
List<AppSearchSchema> schema =
ImmutableList.of(new AppSearchSchema.Builder("schema").build());
mAppSearchImpl.setSchema(
"package",
"database",
schema,
/*visibilityStore=*/ null,
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
/*version=*/ 0);
// Insert package document
GenericDocument document =
new GenericDocument.Builder<>("namespace", "id", "schema").build();
mAppSearchImpl.putDocument("package", "database", document, /*logger=*/ null);
// Verify the document is indexed.
SearchSpec searchSpec =
new SearchSpec.Builder().setTermMatch(TermMatchType.Code.PREFIX_VALUE).build();
SearchResultPage searchResultPage =
mAppSearchImpl.query(
"package",
"database",
/*queryExpression=*/ "",
searchSpec,
/*logger=*/ null);
assertThat(searchResultPage.getResults()).hasSize(1);
assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document);
// Remove the package
mAppSearchImpl.clearPackageData("package");
// Verify the document is cleared.
searchResultPage =
mAppSearchImpl.query(
"package2",
"database2",
/*queryExpression=*/ "",
searchSpec,
/*logger=*/ null);
assertThat(searchResultPage.getResults()).isEmpty();
// Verify the schema is cleared.
assertThat(mAppSearchImpl.getSchemaProtoLocked().getTypesList())
.containsExactlyElementsIn(existingSchemas);
assertThat(mAppSearchImpl.getPackageToDatabases())
.containsExactlyEntriesIn(existingDatabases);
}
@Test
public void testPrunePackageData() throws AppSearchException {
List<SchemaTypeConfigProto> existingSchemas =
mAppSearchImpl.getSchemaProtoLocked().getTypesList();
Map<String, Set<String>> existingDatabases = mAppSearchImpl.getPackageToDatabases();
Set<String> existingPackages = new ArraySet<>(existingSchemas.size());
for (int i = 0; i < existingSchemas.size(); i++) {
existingPackages.add(PrefixUtil.getPackageName(existingSchemas.get(i).getSchemaType()));
}
// Insert schema for package A and B.
List<AppSearchSchema> schema =
ImmutableList.of(new AppSearchSchema.Builder("schema").build());
mAppSearchImpl.setSchema(
"packageA",
"database",
schema,
/*visibilityStore=*/ null,
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
/*version=*/ 0);
mAppSearchImpl.setSchema(
"packageB",
"database",
schema,
/*visibilityStore=*/ null,
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
/*version=*/ 0);
// Verify these two packages is stored in AppSearch
SchemaProto expectedProto =
SchemaProto.newBuilder()
.addTypes(
SchemaTypeConfigProto.newBuilder()
.setSchemaType("packageA$database/schema")
.setVersion(0))
.addTypes(
SchemaTypeConfigProto.newBuilder()
.setSchemaType("packageB$database/schema")
.setVersion(0))
.build();
List<SchemaTypeConfigProto> expectedTypes = new ArrayList<>();
expectedTypes.addAll(existingSchemas);
expectedTypes.addAll(expectedProto.getTypesList());
assertThat(mAppSearchImpl.getSchemaProtoLocked().getTypesList())
.containsExactlyElementsIn(expectedTypes);
// Prune packages
mAppSearchImpl.prunePackageData(existingPackages);
// Verify the schema is same as beginning.
assertThat(mAppSearchImpl.getSchemaProtoLocked().getTypesList())
.containsExactlyElementsIn(existingSchemas);
assertThat(mAppSearchImpl.getPackageToDatabases())
.containsExactlyEntriesIn(existingDatabases);
}
@Test
public void testGetPackageToDatabases() throws Exception {
Map<String, Set<String>> existingMapping = mAppSearchImpl.getPackageToDatabases();
Map<String, Set<String>> expectedMapping = new ArrayMap<>();
expectedMapping.putAll(existingMapping);
// Has database1
expectedMapping.put("package1", ImmutableSet.of("database1"));
mAppSearchImpl.setSchema(
"package1",
"database1",
Collections.singletonList(new AppSearchSchema.Builder("schema").build()),
/*visibilityStore=*/ null,
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
/*version=*/ 0);
assertThat(mAppSearchImpl.getPackageToDatabases())
.containsExactlyEntriesIn(expectedMapping);
// Has both databases
expectedMapping.put("package1", ImmutableSet.of("database1", "database2"));
mAppSearchImpl.setSchema(
"package1",
"database2",
Collections.singletonList(new AppSearchSchema.Builder("schema").build()),
/*visibilityStore=*/ null,
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
/*version=*/ 0);
assertThat(mAppSearchImpl.getPackageToDatabases())
.containsExactlyEntriesIn(expectedMapping);
// Has both packages
expectedMapping.put("package2", ImmutableSet.of("database1"));
mAppSearchImpl.setSchema(
"package2",
"database1",
Collections.singletonList(new AppSearchSchema.Builder("schema").build()),
/*visibilityStore=*/ null,
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
/*version=*/ 0);
assertThat(mAppSearchImpl.getPackageToDatabases())
.containsExactlyEntriesIn(expectedMapping);
}
@Test
public void testRewriteSearchResultProto() throws Exception {
final String prefix =
"com.package.foo"
+ PrefixUtil.PACKAGE_DELIMITER
+ "databaseName"
+ PrefixUtil.DATABASE_DELIMITER;
final String id = "id";
final String namespace = prefix + "namespace";
final String schemaType = prefix + "schema";
// Building the SearchResult received from query.
DocumentProto documentProto =
DocumentProto.newBuilder()
.setUri(id)
.setNamespace(namespace)
.setSchema(schemaType)
.build();
SearchResultProto.ResultProto resultProto =
SearchResultProto.ResultProto.newBuilder().setDocument(documentProto).build();
SearchResultProto searchResultProto =
SearchResultProto.newBuilder().addResults(resultProto).build();
SchemaTypeConfigProto schemaTypeConfigProto =
SchemaTypeConfigProto.newBuilder().setSchemaType(schemaType).build();
Map<String, Map<String, SchemaTypeConfigProto>> schemaMap =
ImmutableMap.of(prefix, ImmutableMap.of(schemaType, schemaTypeConfigProto));
DocumentProto.Builder strippedDocumentProto = documentProto.toBuilder();
removePrefixesFromDocument(strippedDocumentProto);
SearchResultPage searchResultPage =
AppSearchImpl.rewriteSearchResultProto(searchResultProto, schemaMap);
for (SearchResult result : searchResultPage.getResults()) {
assertThat(result.getPackageName()).isEqualTo("com.package.foo");
assertThat(result.getDatabaseName()).isEqualTo("databaseName");
assertThat(result.getGenericDocument())
.isEqualTo(
GenericDocumentToProtoConverter.toGenericDocument(
strippedDocumentProto.build(), prefix, schemaMap.get(prefix)));
}
}
@Test
public void testReportUsage() throws Exception {
// Insert schema
List<AppSearchSchema> schemas =
Collections.singletonList(new AppSearchSchema.Builder("type").build());
mAppSearchImpl.setSchema(
"package",
"database",
schemas,
/*visibilityStore=*/ null,
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
/*version=*/ 0);
// Insert two docs
GenericDocument document1 =
new GenericDocument.Builder<>("namespace", "id1", "type").build();
GenericDocument document2 =
new GenericDocument.Builder<>("namespace", "id2", "type").build();
mAppSearchImpl.putDocument("package", "database", document1, /*logger=*/ null);
mAppSearchImpl.putDocument("package", "database", document2, /*logger=*/ null);
// Report some usages. id1 has 2 app and 1 system usage, id2 has 1 app and 2 system usage.
mAppSearchImpl.reportUsage(
"package",
"database",
"namespace",
"id1",
/*usageTimestampMillis=*/ 10,
/*systemUsage=*/ false);
mAppSearchImpl.reportUsage(
"package",
"database",
"namespace",
"id1",
/*usageTimestampMillis=*/ 20,
/*systemUsage=*/ false);
mAppSearchImpl.reportUsage(
"package",
"database",
"namespace",
"id1",
/*usageTimestampMillis=*/ 1000,
/*systemUsage=*/ true);
mAppSearchImpl.reportUsage(
"package",
"database",
"namespace",
"id2",
/*usageTimestampMillis=*/ 100,
/*systemUsage=*/ false);
mAppSearchImpl.reportUsage(
"package",
"database",
"namespace",
"id2",
/*usageTimestampMillis=*/ 200,
/*systemUsage=*/ true);
mAppSearchImpl.reportUsage(
"package",
"database",
"namespace",
"id2",
/*usageTimestampMillis=*/ 150,
/*systemUsage=*/ true);
// Sort by app usage count: id1 should win
List<SearchResult> page =
mAppSearchImpl
.query(
"package",
"database",
"",
new SearchSpec.Builder()
.setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
.setRankingStrategy(SearchSpec.RANKING_STRATEGY_USAGE_COUNT)
.build(),
/*logger=*/ null)
.getResults();
assertThat(page).hasSize(2);
assertThat(page.get(0).getGenericDocument().getId()).isEqualTo("id1");
assertThat(page.get(1).getGenericDocument().getId()).isEqualTo("id2");
// Sort by app usage timestamp: id2 should win
page =
mAppSearchImpl
.query(
"package",
"database",
"",
new SearchSpec.Builder()
.setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
.setRankingStrategy(
SearchSpec
.RANKING_STRATEGY_USAGE_LAST_USED_TIMESTAMP)
.build(),
/*logger=*/ null)
.getResults();
assertThat(page).hasSize(2);
assertThat(page.get(0).getGenericDocument().getId()).isEqualTo("id2");
assertThat(page.get(1).getGenericDocument().getId()).isEqualTo("id1");
// Sort by system usage count: id2 should win
page =
mAppSearchImpl
.query(
"package",
"database",
"",
new SearchSpec.Builder()
.setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
.setRankingStrategy(
SearchSpec.RANKING_STRATEGY_SYSTEM_USAGE_COUNT)
.build(),
/*logger=*/ null)
.getResults();
assertThat(page).hasSize(2);
assertThat(page.get(0).getGenericDocument().getId()).isEqualTo("id2");
assertThat(page.get(1).getGenericDocument().getId()).isEqualTo("id1");
// Sort by system usage timestamp: id1 should win
page =
mAppSearchImpl
.query(
"package",
"database",
"",
new SearchSpec.Builder()
.setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
.setRankingStrategy(
SearchSpec
.RANKING_STRATEGY_SYSTEM_USAGE_LAST_USED_TIMESTAMP)
.build(),
/*logger=*/ null)
.getResults();
assertThat(page).hasSize(2);
assertThat(page.get(0).getGenericDocument().getId()).isEqualTo("id1");
assertThat(page.get(1).getGenericDocument().getId()).isEqualTo("id2");
}
@Test
public void testGetStorageInfoForPackage_nonexistentPackage() throws Exception {
// "package2" doesn't exist yet, so it shouldn't have any storage size
StorageInfo storageInfo = mAppSearchImpl.getStorageInfoForPackage("nonexistent.package");
assertThat(storageInfo.getSizeBytes()).isEqualTo(0);
assertThat(storageInfo.getAliveDocumentsCount()).isEqualTo(0);
assertThat(storageInfo.getAliveNamespacesCount()).isEqualTo(0);
}
@Test
public void testGetStorageInfoForPackage_withoutDocument() throws Exception {
// Insert schema for "package1"
List<AppSearchSchema> schemas =
Collections.singletonList(new AppSearchSchema.Builder("type").build());
mAppSearchImpl.setSchema(
"package1",
"database",
schemas,
/*visibilityStore=*/ null,
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
/*version=*/ 0);
// Since "package1" doesn't have a document, it get any space attributed to it.
StorageInfo storageInfo = mAppSearchImpl.getStorageInfoForPackage("package1");
assertThat(storageInfo.getSizeBytes()).isEqualTo(0);
assertThat(storageInfo.getAliveDocumentsCount()).isEqualTo(0);
assertThat(storageInfo.getAliveNamespacesCount()).isEqualTo(0);
}
@Test
public void testGetStorageInfoForPackage_proportionalToDocuments() throws Exception {
List<AppSearchSchema> schemas =
Collections.singletonList(new AppSearchSchema.Builder("type").build());
// Insert schema for "package1"
mAppSearchImpl.setSchema(
"package1",
"database",
schemas,
/*visibilityStore=*/ null,
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
/*version=*/ 0);
// Insert document for "package1"
GenericDocument document =
new GenericDocument.Builder<>("namespace", "id1", "type").build();
mAppSearchImpl.putDocument("package1", "database", document, /*logger=*/ null);
// Insert schema for "package2"
mAppSearchImpl.setSchema(
"package2",
"database",
schemas,
/*visibilityStore=*/ null,
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
/*version=*/ 0);
// Insert two documents for "package2"
document = new GenericDocument.Builder<>("namespace", "id1", "type").build();
mAppSearchImpl.putDocument("package2", "database", document, /*logger=*/ null);
document = new GenericDocument.Builder<>("namespace", "id2", "type").build();
mAppSearchImpl.putDocument("package2", "database", document, /*logger=*/ null);
StorageInfo storageInfo = mAppSearchImpl.getStorageInfoForPackage("package1");
long size1 = storageInfo.getSizeBytes();
assertThat(size1).isGreaterThan(0);
assertThat(storageInfo.getAliveDocumentsCount()).isEqualTo(1);
assertThat(storageInfo.getAliveNamespacesCount()).isEqualTo(1);
storageInfo = mAppSearchImpl.getStorageInfoForPackage("package2");
long size2 = storageInfo.getSizeBytes();
assertThat(size2).isGreaterThan(0);
assertThat(storageInfo.getAliveDocumentsCount()).isEqualTo(2);
assertThat(storageInfo.getAliveNamespacesCount()).isEqualTo(1);
// Size is proportional to number of documents. Since "package2" has twice as many
// documents as "package1", its size is twice as much too.
assertThat(size2).isAtLeast(2 * size1);
}
@Test
public void testGetStorageInfoForDatabase_nonexistentPackage() throws Exception {
// "package2" doesn't exist yet, so it shouldn't have any storage size
StorageInfo storageInfo =
mAppSearchImpl.getStorageInfoForDatabase(
"nonexistent.package", "nonexistentDatabase");
assertThat(storageInfo.getSizeBytes()).isEqualTo(0);
assertThat(storageInfo.getAliveDocumentsCount()).isEqualTo(0);
assertThat(storageInfo.getAliveNamespacesCount()).isEqualTo(0);
}
@Test
public void testGetStorageInfoForDatabase_nonexistentDatabase() throws Exception {
// Insert schema for "package1"
List<AppSearchSchema> schemas =
Collections.singletonList(new AppSearchSchema.Builder("type").build());
mAppSearchImpl.setSchema(
"package1",
"database",
schemas,
/*visibilityStore=*/ null,
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
/*version=*/ 0);
// "package2" doesn't exist yet, so it shouldn't have any storage size
StorageInfo storageInfo =
mAppSearchImpl.getStorageInfoForDatabase("package1", "nonexistentDatabase");
assertThat(storageInfo.getSizeBytes()).isEqualTo(0);
assertThat(storageInfo.getAliveDocumentsCount()).isEqualTo(0);
assertThat(storageInfo.getAliveNamespacesCount()).isEqualTo(0);
}
@Test
public void testGetStorageInfoForDatabase_withoutDocument() throws Exception {
// Insert schema for "package1"
List<AppSearchSchema> schemas =
Collections.singletonList(new AppSearchSchema.Builder("type").build());
mAppSearchImpl.setSchema(
"package1",
"database1",
schemas,
/*visibilityStore=*/ null,
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
/*version=*/ 0);
// Since "package1", "database1" doesn't have a document, it get any space attributed to it.
StorageInfo storageInfo = mAppSearchImpl.getStorageInfoForDatabase("package1", "database1");
assertThat(storageInfo.getSizeBytes()).isEqualTo(0);
assertThat(storageInfo.getAliveDocumentsCount()).isEqualTo(0);
assertThat(storageInfo.getAliveNamespacesCount()).isEqualTo(0);
}
@Test
public void testGetStorageInfoForDatabase_proportionalToDocuments() throws Exception {
// Insert schema for "package1", "database1" and "database2"
List<AppSearchSchema> schemas =
Collections.singletonList(new AppSearchSchema.Builder("type").build());
mAppSearchImpl.setSchema(
"package1",
"database1",
schemas,
/*visibilityStore=*/ null,
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
/*version=*/ 0);
mAppSearchImpl.setSchema(
"package1",
"database2",
schemas,
/*visibilityStore=*/ null,
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
/*version=*/ 0);
// Add a document for "package1", "database1"
GenericDocument document =
new GenericDocument.Builder<>("namespace1", "id1", "type").build();
mAppSearchImpl.putDocument("package1", "database1", document, /*logger=*/ null);
// Add two documents for "package1", "database2"
document = new GenericDocument.Builder<>("namespace1", "id1", "type").build();
mAppSearchImpl.putDocument("package1", "database2", document, /*logger=*/ null);
document = new GenericDocument.Builder<>("namespace1", "id2", "type").build();
mAppSearchImpl.putDocument("package1", "database2", document, /*logger=*/ null);
StorageInfo storageInfo = mAppSearchImpl.getStorageInfoForDatabase("package1", "database1");
long size1 = storageInfo.getSizeBytes();
assertThat(size1).isGreaterThan(0);
assertThat(storageInfo.getAliveDocumentsCount()).isEqualTo(1);
assertThat(storageInfo.getAliveNamespacesCount()).isEqualTo(1);
storageInfo = mAppSearchImpl.getStorageInfoForDatabase("package1", "database2");
long size2 = storageInfo.getSizeBytes();
assertThat(size2).isGreaterThan(0);
assertThat(storageInfo.getAliveDocumentsCount()).isEqualTo(2);
assertThat(storageInfo.getAliveNamespacesCount()).isEqualTo(1);
// Size is proportional to number of documents. Since "database2" has twice as many
// documents as "database1", its size is twice as much too.
assertThat(size2).isAtLeast(2 * size1);
}
@Test
public void testThrowsExceptionIfClosed() throws Exception {
AppSearchImpl appSearchImpl =
AppSearchImpl.create(
mTemporaryFolder.newFolder(),
new UnlimitedLimitConfig(),
/*initStatsBuilder=*/ null,
ALWAYS_OPTIMIZE);
// Initial check that we could do something at first.
List<AppSearchSchema> schemas =
Collections.singletonList(new AppSearchSchema.Builder("type").build());
appSearchImpl.setSchema(
"package",
"database",
schemas,
/*visibilityStore=*/ null,
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
/*version=*/ 0);
appSearchImpl.close();
// Check all our public APIs
assertThrows(
IllegalStateException.class,
() ->
appSearchImpl.setSchema(
"package",
"database",
schemas,
/*visibilityStore=*/ null,
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
/*version=*/ 0));
assertThrows(
IllegalStateException.class, () -> appSearchImpl.getSchema("package", "database"));
assertThrows(
IllegalStateException.class,
() ->
appSearchImpl.putDocument(
"package",
"database",
new GenericDocument.Builder<>("namespace", "id", "type").build(),
/*logger=*/ null));
assertThrows(
IllegalStateException.class,
() ->
appSearchImpl.getDocument(
"package", "database", "namespace", "id", Collections.emptyMap()));
assertThrows(
IllegalStateException.class,
() ->
appSearchImpl.query(
"package",
"database",
"query",
new SearchSpec.Builder().build(),
/*logger=*/ null));
assertThrows(
IllegalStateException.class,
() ->
appSearchImpl.globalQuery(
"query",
new SearchSpec.Builder().build(),
"package",
/*visibilityStore=*/ null,
Process.INVALID_UID,
/*callerHasSystemAccess=*/ false,
/*logger=*/ null));
assertThrows(
IllegalStateException.class,
() -> appSearchImpl.getNextPage("package", /*nextPageToken=*/ 1L));
assertThrows(
IllegalStateException.class,
() -> appSearchImpl.invalidateNextPageToken("package", /*nextPageToken=*/ 1L));
assertThrows(
IllegalStateException.class,
() ->
appSearchImpl.reportUsage(
"package",
"database",
"namespace",
"id",
/*usageTimestampMillis=*/ 1000L,
/*systemUsage=*/ false));
assertThrows(
IllegalStateException.class,
() ->
appSearchImpl.remove(
"package",
"database",
"namespace",
"id",
/*removeStatsBuilder=*/ null));
assertThrows(
IllegalStateException.class,
() ->
appSearchImpl.removeByQuery(
"package",
"database",
"query",
new SearchSpec.Builder().build(),
/*removeStatsBuilder=*/ null));
assertThrows(
IllegalStateException.class,
() -> appSearchImpl.getStorageInfoForPackage("package"));
assertThrows(
IllegalStateException.class,
() -> appSearchImpl.getStorageInfoForDatabase("package", "database"));
assertThrows(
IllegalStateException.class,
() -> appSearchImpl.persistToDisk(PersistType.Code.FULL));
}
@Test
public void testPutPersistsWithLiteFlush() throws Exception {
// Setup the index
File appsearchDir = mTemporaryFolder.newFolder();
AppSearchImpl appSearchImpl =
AppSearchImpl.create(
appsearchDir,
new UnlimitedLimitConfig(),
/*initStatsBuilder=*/ null,
ALWAYS_OPTIMIZE);
List<AppSearchSchema> schemas =
Collections.singletonList(new AppSearchSchema.Builder("type").build());
appSearchImpl.setSchema(
"package",
"database",
schemas,
/*visibilityStore=*/ null,
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
/*version=*/ 0);
// Add a document and persist it.
GenericDocument document =
new GenericDocument.Builder<>("namespace1", "id1", "type").build();
appSearchImpl.putDocument("package", "database", document, /*logger=*/ null);
appSearchImpl.persistToDisk(PersistType.Code.LITE);
GenericDocument getResult =
appSearchImpl.getDocument(
"package", "database", "namespace1", "id1", Collections.emptyMap());
assertThat(getResult).isEqualTo(document);
// That document should be visible even from another instance.
AppSearchImpl appSearchImpl2 =
AppSearchImpl.create(
appsearchDir,
new UnlimitedLimitConfig(),
/*initStatsBuilder=*/ null,
ALWAYS_OPTIMIZE);
getResult =
appSearchImpl2.getDocument(
"package", "database", "namespace1", "id1", Collections.emptyMap());
assertThat(getResult).isEqualTo(document);
}
@Test
public void testDeletePersistsWithLiteFlush() throws Exception {
// Setup the index
File appsearchDir = mTemporaryFolder.newFolder();
AppSearchImpl appSearchImpl =
AppSearchImpl.create(
appsearchDir,
new UnlimitedLimitConfig(),
/*initStatsBuilder=*/ null,
ALWAYS_OPTIMIZE);
List<AppSearchSchema> schemas =
Collections.singletonList(new AppSearchSchema.Builder("type").build());
appSearchImpl.setSchema(
"package",
"database",
schemas,
/*visibilityStore=*/ null,
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
/*version=*/ 0);
// Add two documents and persist them.
GenericDocument document1 =
new GenericDocument.Builder<>("namespace1", "id1", "type").build();
appSearchImpl.putDocument("package", "database", document1, /*logger=*/ null);
GenericDocument document2 =
new GenericDocument.Builder<>("namespace1", "id2", "type").build();
appSearchImpl.putDocument("package", "database", document2, /*logger=*/ null);
appSearchImpl.persistToDisk(PersistType.Code.LITE);
GenericDocument getResult =
appSearchImpl.getDocument(
"package", "database", "namespace1", "id1", Collections.emptyMap());
assertThat(getResult).isEqualTo(document1);
getResult =
appSearchImpl.getDocument(
"package", "database", "namespace1", "id2", Collections.emptyMap());
assertThat(getResult).isEqualTo(document2);
// Delete the first document
appSearchImpl.remove("package", "database", "namespace1", "id1", /*statsBuilder=*/ null);
appSearchImpl.persistToDisk(PersistType.Code.LITE);
assertThrows(
AppSearchException.class,
() ->
appSearchImpl.getDocument(
"package",
"database",
"namespace1",
"id1",
Collections.emptyMap()));
getResult =
appSearchImpl.getDocument(
"package", "database", "namespace1", "id2", Collections.emptyMap());
assertThat(getResult).isEqualTo(document2);
// Only the second document should be retrievable from another instance.
AppSearchImpl appSearchImpl2 =
AppSearchImpl.create(
appsearchDir,
new UnlimitedLimitConfig(),
/*initStatsBuilder=*/ null,
ALWAYS_OPTIMIZE);
assertThrows(
AppSearchException.class,
() ->
appSearchImpl2.getDocument(
"package",
"database",
"namespace1",
"id1",
Collections.emptyMap()));
getResult =
appSearchImpl2.getDocument(
"package", "database", "namespace1", "id2", Collections.emptyMap());
assertThat(getResult).isEqualTo(document2);
}
@Test
public void testDeleteByQueryPersistsWithLiteFlush() throws Exception {
// Setup the index
File appsearchDir = mTemporaryFolder.newFolder();
AppSearchImpl appSearchImpl =
AppSearchImpl.create(
appsearchDir,
new UnlimitedLimitConfig(),
/*initStatsBuilder=*/ null,
ALWAYS_OPTIMIZE);
List<AppSearchSchema> schemas =
Collections.singletonList(new AppSearchSchema.Builder("type").build());
appSearchImpl.setSchema(
"package",
"database",
schemas,
/*visibilityStore=*/ null,
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
/*version=*/ 0);
// Add two documents and persist them.
GenericDocument document1 =
new GenericDocument.Builder<>("namespace1", "id1", "type").build();
appSearchImpl.putDocument("package", "database", document1, /*logger=*/ null);
GenericDocument document2 =
new GenericDocument.Builder<>("namespace2", "id2", "type").build();
appSearchImpl.putDocument("package", "database", document2, /*logger=*/ null);
appSearchImpl.persistToDisk(PersistType.Code.LITE);
GenericDocument getResult =
appSearchImpl.getDocument(
"package", "database", "namespace1", "id1", Collections.emptyMap());
assertThat(getResult).isEqualTo(document1);
getResult =
appSearchImpl.getDocument(
"package", "database", "namespace2", "id2", Collections.emptyMap());
assertThat(getResult).isEqualTo(document2);
// Delete the first document
appSearchImpl.removeByQuery(
"package",
"database",
"",
new SearchSpec.Builder()
.addFilterNamespaces("namespace1")
.setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
.build(),
/*statsBuilder=*/ null);
appSearchImpl.persistToDisk(PersistType.Code.LITE);
assertThrows(
AppSearchException.class,
() ->
appSearchImpl.getDocument(
"package",
"database",
"namespace1",
"id1",
Collections.emptyMap()));
getResult =
appSearchImpl.getDocument(
"package", "database", "namespace2", "id2", Collections.emptyMap());
assertThat(getResult).isEqualTo(document2);
// Only the second document should be retrievable from another instance.
AppSearchImpl appSearchImpl2 =
AppSearchImpl.create(
appsearchDir,
new UnlimitedLimitConfig(),
/*initStatsBuilder=*/ null,
ALWAYS_OPTIMIZE);
assertThrows(
AppSearchException.class,
() ->
appSearchImpl2.getDocument(
"package",
"database",
"namespace1",
"id1",
Collections.emptyMap()));
getResult =
appSearchImpl2.getDocument(
"package", "database", "namespace2", "id2", Collections.emptyMap());
assertThat(getResult).isEqualTo(document2);
}
@Test
public void testGetIcingSearchEngineStorageInfo() throws Exception {
// Setup the index
File appsearchDir = mTemporaryFolder.newFolder();
AppSearchImpl appSearchImpl =
AppSearchImpl.create(
appsearchDir,
new UnlimitedLimitConfig(),
/*initStatsBuilder=*/ null,
ALWAYS_OPTIMIZE);
List<AppSearchSchema> schemas =
Collections.singletonList(new AppSearchSchema.Builder("type").build());
appSearchImpl.setSchema(
"package",
"database",
schemas,
/*visibilityStore=*/ null,
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
/*version=*/ 0);
// Add two documents
GenericDocument document1 =
new GenericDocument.Builder<>("namespace1", "id1", "type").build();
appSearchImpl.putDocument("package", "database", document1, /*logger=*/ null);
GenericDocument document2 =
new GenericDocument.Builder<>("namespace1", "id2", "type").build();
appSearchImpl.putDocument("package", "database", document2, /*logger=*/ null);
StorageInfoProto storageInfo = appSearchImpl.getRawStorageInfoProto();
// Simple checks to verify if we can get correct StorageInfoProto from IcingSearchEngine
// No need to cover all the fields
assertThat(storageInfo.getTotalStorageSize()).isGreaterThan(0);
assertThat(storageInfo.getDocumentStorageInfo().getNumAliveDocuments()).isEqualTo(2);
assertThat(storageInfo.getSchemaStoreStorageInfo().getNumSchemaTypes()).isEqualTo(1);
}
@Test
public void testLimitConfig_DocumentSize() throws Exception {
// Create a new mAppSearchImpl with a lower limit
mAppSearchImpl.close();
mAppSearchImpl =
AppSearchImpl.create(
mTemporaryFolder.newFolder(),
new LimitConfig() {
@Override
public int getMaxDocumentSizeBytes() {
return 80;
}
@Override
public int getMaxDocumentCount() {
return 1;
}
},
/*initStatsBuilder=*/ null,
ALWAYS_OPTIMIZE);
// Insert schema
List<AppSearchSchema> schemas =
Collections.singletonList(new AppSearchSchema.Builder("type").build());
mAppSearchImpl.setSchema(
"package",
"database",
schemas,
/*visibilityStore=*/ null,
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
/*version=*/ 0);
// Insert a document which is too large
GenericDocument document =
new GenericDocument.Builder<>(
"this_namespace_is_long_to_make_the_doc_big", "id", "type")
.build();
AppSearchException e =
assertThrows(
AppSearchException.class,
() ->
mAppSearchImpl.putDocument(
"package", "database", document, /*logger=*/ null));
assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_OUT_OF_SPACE);
assertThat(e)
.hasMessageThat()
.contains(
"Document \"id\" for package \"package\" serialized to 99 bytes, which"
+ " exceeds limit of 80 bytes");
// Make sure this failure didn't increase our document count. We should still be able to
// index 1 document.
GenericDocument document2 =
new GenericDocument.Builder<>("namespace", "id2", "type").build();
mAppSearchImpl.putDocument("package", "database", document2, /*logger=*/ null);
// Now we should get a failure
GenericDocument document3 =
new GenericDocument.Builder<>("namespace", "id3", "type").build();
e =
assertThrows(
AppSearchException.class,
() ->
mAppSearchImpl.putDocument(
"package", "database", document3, /*logger=*/ null));
assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_OUT_OF_SPACE);
assertThat(e)
.hasMessageThat()
.contains("Package \"package\" exceeded limit of 1 documents");
}
@Test
public void testLimitConfig_Init() throws Exception {
// Create a new mAppSearchImpl with a lower limit
mAppSearchImpl.close();
File tempFolder = mTemporaryFolder.newFolder();
mAppSearchImpl =
AppSearchImpl.create(
tempFolder,
new LimitConfig() {
@Override
public int getMaxDocumentSizeBytes() {
return 80;
}
@Override
public int getMaxDocumentCount() {
return 1;
}
},
/*initStatsBuilder=*/ null,
ALWAYS_OPTIMIZE);
// Insert schema
List<AppSearchSchema> schemas =
Collections.singletonList(new AppSearchSchema.Builder("type").build());
mAppSearchImpl.setSchema(
"package",
"database",
schemas,
/*visibilityStore=*/ null,
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
/*version=*/ 0);
// Index a document
mAppSearchImpl.putDocument(
"package",
"database",
new GenericDocument.Builder<>("namespace", "id1", "type").build(),
/*logger=*/ null);
// Now we should get a failure
GenericDocument document2 =
new GenericDocument.Builder<>("namespace", "id2", "type").build();
AppSearchException e =
assertThrows(
AppSearchException.class,
() ->
mAppSearchImpl.putDocument(
"package", "database", document2, /*logger=*/ null));
assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_OUT_OF_SPACE);
assertThat(e)
.hasMessageThat()
.contains("Package \"package\" exceeded limit of 1 documents");
// Close and reinitialize AppSearchImpl
mAppSearchImpl.close();
mAppSearchImpl =
AppSearchImpl.create(
tempFolder,
new LimitConfig() {
@Override
public int getMaxDocumentSizeBytes() {
return 80;
}
@Override
public int getMaxDocumentCount() {
return 1;
}
},
/*initStatsBuilder=*/ null,
ALWAYS_OPTIMIZE);
// Make sure the limit is maintained
e =
assertThrows(
AppSearchException.class,
() ->
mAppSearchImpl.putDocument(
"package", "database", document2, /*logger=*/ null));
assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_OUT_OF_SPACE);
assertThat(e)
.hasMessageThat()
.contains("Package \"package\" exceeded limit of 1 documents");
}
@Test
public void testLimitConfig_Remove() throws Exception {
// Create a new mAppSearchImpl with a lower limit
mAppSearchImpl.close();
mAppSearchImpl =
AppSearchImpl.create(
mTemporaryFolder.newFolder(),
new LimitConfig() {
@Override
public int getMaxDocumentSizeBytes() {
return Integer.MAX_VALUE;
}
@Override
public int getMaxDocumentCount() {
return 3;
}
},
/*initStatsBuilder=*/ null,
ALWAYS_OPTIMIZE);
// Insert schema
List<AppSearchSchema> schemas =
Collections.singletonList(new AppSearchSchema.Builder("type").build());
mAppSearchImpl.setSchema(
"package",
"database",
schemas,
/*visibilityStore=*/ null,
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
/*version=*/ 0);
// Index 3 documents
mAppSearchImpl.putDocument(
"package",
"database",
new GenericDocument.Builder<>("namespace", "id1", "type").build(),
/*logger=*/ null);
mAppSearchImpl.putDocument(
"package",
"database",
new GenericDocument.Builder<>("namespace", "id2", "type").build(),
/*logger=*/ null);
mAppSearchImpl.putDocument(
"package",
"database",
new GenericDocument.Builder<>("namespace", "id3", "type").build(),
/*logger=*/ null);
// Now we should get a failure
GenericDocument document4 =
new GenericDocument.Builder<>("namespace", "id4", "type").build();
AppSearchException e =
assertThrows(
AppSearchException.class,
() ->
mAppSearchImpl.putDocument(
"package", "database", document4, /*logger=*/ null));
assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_OUT_OF_SPACE);
assertThat(e)
.hasMessageThat()
.contains("Package \"package\" exceeded limit of 3 documents");
// Remove a document that doesn't exist
assertThrows(
AppSearchException.class,
() ->
mAppSearchImpl.remove(
"package",
"database",
"namespace",
"id4",
/*removeStatsBuilder=*/ null));
// Should still fail
e =
assertThrows(
AppSearchException.class,
() ->
mAppSearchImpl.putDocument(
"package", "database", document4, /*logger=*/ null));
assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_OUT_OF_SPACE);
assertThat(e)
.hasMessageThat()
.contains("Package \"package\" exceeded limit of 3 documents");
// Remove a document that does exist
mAppSearchImpl.remove(
"package", "database", "namespace", "id2", /*removeStatsBuilder=*/ null);
// Now doc4 should work
mAppSearchImpl.putDocument("package", "database", document4, /*logger=*/ null);
// The next one should fail again
e =
assertThrows(
AppSearchException.class,
() ->
mAppSearchImpl.putDocument(
"package",
"database",
new GenericDocument.Builder<>("namespace", "id5", "type")
.build(),
/*logger=*/ null));
assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_OUT_OF_SPACE);
assertThat(e)
.hasMessageThat()
.contains("Package \"package\" exceeded limit of 3 documents");
}
@Test
public void testLimitConfig_DifferentPackages() throws Exception {
// Create a new mAppSearchImpl with a lower limit
mAppSearchImpl.close();
File tempFolder = mTemporaryFolder.newFolder();
mAppSearchImpl =
AppSearchImpl.create(
tempFolder,
new LimitConfig() {
@Override
public int getMaxDocumentSizeBytes() {
return Integer.MAX_VALUE;
}
@Override
public int getMaxDocumentCount() {
return 2;
}
},
/*initStatsBuilder=*/ null,
ALWAYS_OPTIMIZE);
// Insert schema
List<AppSearchSchema> schemas =
Collections.singletonList(new AppSearchSchema.Builder("type").build());
mAppSearchImpl.setSchema(
"package1",
"database1",
schemas,
/*visibilityStore=*/ null,
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
/*version=*/ 0);
mAppSearchImpl.setSchema(
"package1",
"database2",
schemas,
/*visibilityStore=*/ null,
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
/*version=*/ 0);
mAppSearchImpl.setSchema(
"package2",
"database1",
schemas,
/*visibilityStore=*/ null,
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
/*version=*/ 0);
mAppSearchImpl.setSchema(
"package2",
"database2",
schemas,
/*visibilityStore=*/ null,
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
/*version=*/ 0);
// Index documents in package1/database1
mAppSearchImpl.putDocument(
"package1",
"database1",
new GenericDocument.Builder<>("namespace", "id1", "type").build(),
/*logger=*/ null);
mAppSearchImpl.putDocument(
"package1",
"database2",
new GenericDocument.Builder<>("namespace", "id2", "type").build(),
/*logger=*/ null);
// Indexing a third doc into package1 should fail (here we use database3)
AppSearchException e =
assertThrows(
AppSearchException.class,
() ->
mAppSearchImpl.putDocument(
"package1",
"database3",
new GenericDocument.Builder<>("namespace", "id3", "type")
.build(),
/*logger=*/ null));
assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_OUT_OF_SPACE);
assertThat(e)
.hasMessageThat()
.contains("Package \"package1\" exceeded limit of 2 documents");
// Indexing a doc into package2 should succeed
mAppSearchImpl.putDocument(
"package2",
"database1",
new GenericDocument.Builder<>("namespace", "id1", "type").build(),
/*logger=*/ null);
// Reinitialize to make sure packages are parsed correctly on init
mAppSearchImpl.close();
mAppSearchImpl =
AppSearchImpl.create(
tempFolder,
new LimitConfig() {
@Override
public int getMaxDocumentSizeBytes() {
return Integer.MAX_VALUE;
}
@Override
public int getMaxDocumentCount() {
return 2;
}
},
/*initStatsBuilder=*/ null,
ALWAYS_OPTIMIZE);
// package1 should still be out of space
e =
assertThrows(
AppSearchException.class,
() ->
mAppSearchImpl.putDocument(
"package1",
"database4",
new GenericDocument.Builder<>("namespace", "id4", "type")
.build(),
/*logger=*/ null));
assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_OUT_OF_SPACE);
assertThat(e)
.hasMessageThat()
.contains("Package \"package1\" exceeded limit of 2 documents");
// package2 has room for one more
mAppSearchImpl.putDocument(
"package2",
"database2",
new GenericDocument.Builder<>("namespace", "id2", "type").build(),
/*logger=*/ null);
// now package2 really is out of space
e =
assertThrows(
AppSearchException.class,
() ->
mAppSearchImpl.putDocument(
"package2",
"database3",
new GenericDocument.Builder<>("namespace", "id3", "type")
.build(),
/*logger=*/ null));
assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_OUT_OF_SPACE);
assertThat(e)
.hasMessageThat()
.contains("Package \"package2\" exceeded limit of 2 documents");
}
@Test
public void testLimitConfig_RemoveByQyery() throws Exception {
// Create a new mAppSearchImpl with a lower limit
mAppSearchImpl.close();
mAppSearchImpl =
AppSearchImpl.create(
mTemporaryFolder.newFolder(),
new LimitConfig() {
@Override
public int getMaxDocumentSizeBytes() {
return Integer.MAX_VALUE;
}
@Override
public int getMaxDocumentCount() {
return 3;
}
},
/*initStatsBuilder=*/ null,
ALWAYS_OPTIMIZE);
// Insert schema
List<AppSearchSchema> schemas =
Collections.singletonList(
new AppSearchSchema.Builder("type")
.addProperty(
new AppSearchSchema.StringPropertyConfig.Builder("body")
.setIndexingType(
AppSearchSchema.StringPropertyConfig
.INDEXING_TYPE_PREFIXES)
.setTokenizerType(
AppSearchSchema.StringPropertyConfig
.TOKENIZER_TYPE_PLAIN)
.build())
.build());
mAppSearchImpl.setSchema(
"package",
"database",
schemas,
/*visibilityStore=*/ null,
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
/*version=*/ 0);
// Index 3 documents
mAppSearchImpl.putDocument(
"package",
"database",
new GenericDocument.Builder<>("namespace", "id1", "type")
.setPropertyString("body", "tablet")
.build(),
/*logger=*/ null);
mAppSearchImpl.putDocument(
"package",
"database",
new GenericDocument.Builder<>("namespace", "id2", "type")
.setPropertyString("body", "tabby")
.build(),
/*logger=*/ null);
mAppSearchImpl.putDocument(
"package",
"database",
new GenericDocument.Builder<>("namespace", "id3", "type")
.setPropertyString("body", "grabby")
.build(),
/*logger=*/ null);
// Now we should get a failure
GenericDocument document4 =
new GenericDocument.Builder<>("namespace", "id4", "type").build();
AppSearchException e =
assertThrows(
AppSearchException.class,
() ->
mAppSearchImpl.putDocument(
"package", "database", document4, /*logger=*/ null));
assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_OUT_OF_SPACE);
assertThat(e)
.hasMessageThat()
.contains("Package \"package\" exceeded limit of 3 documents");
// Run removebyquery, deleting nothing
mAppSearchImpl.removeByQuery(
"package",
"database",
"nothing",
new SearchSpec.Builder().build(),
/*removeStatsBuilder=*/ null);
// Should still fail
e =
assertThrows(
AppSearchException.class,
() ->
mAppSearchImpl.putDocument(
"package", "database", document4, /*logger=*/ null));
assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_OUT_OF_SPACE);
assertThat(e)
.hasMessageThat()
.contains("Package \"package\" exceeded limit of 3 documents");
// Remove "tab*"
mAppSearchImpl.removeByQuery(
"package",
"database",
"tab",
new SearchSpec.Builder().build(),
/*removeStatsBuilder=*/ null);
// Now doc4 and doc5 should work
mAppSearchImpl.putDocument("package", "database", document4, /*logger=*/ null);
mAppSearchImpl.putDocument(
"package",
"database",
new GenericDocument.Builder<>("namespace", "id5", "type").build(),
/*logger=*/ null);
// We only deleted 2 docs so the next one should fail again
e =
assertThrows(
AppSearchException.class,
() ->
mAppSearchImpl.putDocument(
"package",
"database",
new GenericDocument.Builder<>("namespace", "id6", "type")
.build(),
/*logger=*/ null));
assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_OUT_OF_SPACE);
assertThat(e)
.hasMessageThat()
.contains("Package \"package\" exceeded limit of 3 documents");
}
@Test
public void testLimitConfig_Replace() throws Exception {
// Create a new mAppSearchImpl with a lower limit
mAppSearchImpl.close();
mAppSearchImpl =
AppSearchImpl.create(
mTemporaryFolder.newFolder(),
new LimitConfig() {
@Override
public int getMaxDocumentSizeBytes() {
return Integer.MAX_VALUE;
}
@Override
public int getMaxDocumentCount() {
return 2;
}
},
/*initStatsBuilder=*/ null,
ALWAYS_OPTIMIZE);
// Insert schema
List<AppSearchSchema> schemas =
Collections.singletonList(
new AppSearchSchema.Builder("type")
.addProperty(
new AppSearchSchema.StringPropertyConfig.Builder("body")
.build())
.build());
mAppSearchImpl.setSchema(
"package",
"database",
schemas,
/*visibilityStore=*/ null,
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
/*version=*/ 0);
// Index a document
mAppSearchImpl.putDocument(
"package",
"database",
new GenericDocument.Builder<>("namespace", "id1", "type")
.setPropertyString("body", "id1.orig")
.build(),
/*logger=*/ null);
// Replace it with another doc
mAppSearchImpl.putDocument(
"package",
"database",
new GenericDocument.Builder<>("namespace", "id1", "type")
.setPropertyString("body", "id1.new")
.build(),
/*logger=*/ null);
// Index id2. This should pass but only because we check for replacements.
mAppSearchImpl.putDocument(
"package",
"database",
new GenericDocument.Builder<>("namespace", "id2", "type").build(),
/*logger=*/ null);
// Now we should get a failure on id3
GenericDocument document3 =
new GenericDocument.Builder<>("namespace", "id3", "type").build();
AppSearchException e =
assertThrows(
AppSearchException.class,
() ->
mAppSearchImpl.putDocument(
"package", "database", document3, /*logger=*/ null));
assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_OUT_OF_SPACE);
assertThat(e)
.hasMessageThat()
.contains("Package \"package\" exceeded limit of 2 documents");
}
@Test
public void testLimitConfig_ReplaceReinit() throws Exception {
// Create a new mAppSearchImpl with a lower limit
mAppSearchImpl.close();
File tempFolder = mTemporaryFolder.newFolder();
mAppSearchImpl =
AppSearchImpl.create(
tempFolder,
new LimitConfig() {
@Override
public int getMaxDocumentSizeBytes() {
return Integer.MAX_VALUE;
}
@Override
public int getMaxDocumentCount() {
return 2;
}
},
/*initStatsBuilder=*/ null,
ALWAYS_OPTIMIZE);
// Insert schema
List<AppSearchSchema> schemas =
Collections.singletonList(
new AppSearchSchema.Builder("type")
.addProperty(
new AppSearchSchema.StringPropertyConfig.Builder("body")
.build())
.build());
mAppSearchImpl.setSchema(
"package",
"database",
schemas,
/*visibilityStore=*/ null,
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
/*version=*/ 0);
// Index a document
mAppSearchImpl.putDocument(
"package",
"database",
new GenericDocument.Builder<>("namespace", "id1", "type")
.setPropertyString("body", "id1.orig")
.build(),
/*logger=*/ null);
// Replace it with another doc
mAppSearchImpl.putDocument(
"package",
"database",
new GenericDocument.Builder<>("namespace", "id1", "type")
.setPropertyString("body", "id1.new")
.build(),
/*logger=*/ null);
// Reinitialize to make sure replacements are correctly accounted for by init
mAppSearchImpl.close();
mAppSearchImpl =
AppSearchImpl.create(
tempFolder,
new LimitConfig() {
@Override
public int getMaxDocumentSizeBytes() {
return Integer.MAX_VALUE;
}
@Override
public int getMaxDocumentCount() {
return 2;
}
},
/*initStatsBuilder=*/ null,
ALWAYS_OPTIMIZE);
// Index id2. This should pass but only because we check for replacements.
mAppSearchImpl.putDocument(
"package",
"database",
new GenericDocument.Builder<>("namespace", "id2", "type").build(),
/*logger=*/ null);
// Now we should get a failure on id3
GenericDocument document3 =
new GenericDocument.Builder<>("namespace", "id3", "type").build();
AppSearchException e =
assertThrows(
AppSearchException.class,
() ->
mAppSearchImpl.putDocument(
"package", "database", document3, /*logger=*/ null));
assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_OUT_OF_SPACE);
assertThat(e)
.hasMessageThat()
.contains("Package \"package\" exceeded limit of 2 documents");
}
}