Merge "Add ability to enforce limits on docs per package and size of doc." into sc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
733537f1a6
@@ -60,6 +60,11 @@ public final class AppSearchConfig implements AutoCloseable {
|
||||
@VisibleForTesting
|
||||
static final int DEFAULT_SAMPLING_INTERVAL = 10;
|
||||
|
||||
@VisibleForTesting
|
||||
static final int DEFAULT_LIMIT_CONFIG_MAX_DOCUMENT_SIZE_BYTES = 512 * 1024; // 512KiB
|
||||
@VisibleForTesting
|
||||
static final int DEFAULT_LIMIT_CONFIG_MAX_DOCUMENT_COUNT = 20_000;
|
||||
|
||||
/*
|
||||
* Keys for ALL the flags stored in DeviceConfig.
|
||||
*/
|
||||
@@ -70,13 +75,19 @@ public final class AppSearchConfig implements AutoCloseable {
|
||||
"sampling_interval_for_batch_call_stats";
|
||||
public static final String KEY_SAMPLING_INTERVAL_FOR_PUT_DOCUMENT_STATS =
|
||||
"sampling_interval_for_put_document_stats";
|
||||
public static final String KEY_LIMIT_CONFIG_MAX_DOCUMENT_SIZE_BYTES =
|
||||
"limit_config_max_document_size_bytes";
|
||||
public static final String KEY_LIMIT_CONFIG_MAX_DOCUMENT_COUNT =
|
||||
"limit_config_max_document_docunt";
|
||||
|
||||
// Array contains all the corresponding keys for the cached values.
|
||||
private static final String[] KEYS_TO_ALL_CACHED_VALUES = {
|
||||
KEY_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS,
|
||||
KEY_SAMPLING_INTERVAL_DEFAULT,
|
||||
KEY_SAMPLING_INTERVAL_FOR_BATCH_CALL_STATS,
|
||||
KEY_SAMPLING_INTERVAL_FOR_PUT_DOCUMENT_STATS
|
||||
KEY_SAMPLING_INTERVAL_FOR_PUT_DOCUMENT_STATS,
|
||||
KEY_LIMIT_CONFIG_MAX_DOCUMENT_SIZE_BYTES,
|
||||
KEY_LIMIT_CONFIG_MAX_DOCUMENT_COUNT,
|
||||
};
|
||||
|
||||
// Lock needed for all the operations in this class.
|
||||
@@ -222,6 +233,24 @@ public final class AppSearchConfig implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns the maximum serialized size an indexed document can be, in bytes. */
|
||||
public int getCachedLimitConfigMaxDocumentSizeBytes() {
|
||||
synchronized (mLock) {
|
||||
throwIfClosedLocked();
|
||||
return mBundleLocked.getInt(KEY_LIMIT_CONFIG_MAX_DOCUMENT_SIZE_BYTES,
|
||||
DEFAULT_LIMIT_CONFIG_MAX_DOCUMENT_SIZE_BYTES);
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns the maximum number of active docs allowed per package. */
|
||||
public int getCachedLimitConfigMaxDocumentCount() {
|
||||
synchronized (mLock) {
|
||||
throwIfClosedLocked();
|
||||
return mBundleLocked.getInt(KEY_LIMIT_CONFIG_MAX_DOCUMENT_COUNT,
|
||||
DEFAULT_LIMIT_CONFIG_MAX_DOCUMENT_COUNT);
|
||||
}
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private void throwIfClosedLocked() {
|
||||
if (mIsClosedLocked) {
|
||||
@@ -264,6 +293,20 @@ public final class AppSearchConfig implements AutoCloseable {
|
||||
mBundleLocked.putInt(key, properties.getInt(key, DEFAULT_SAMPLING_INTERVAL));
|
||||
}
|
||||
break;
|
||||
case KEY_LIMIT_CONFIG_MAX_DOCUMENT_SIZE_BYTES:
|
||||
synchronized (mLock) {
|
||||
mBundleLocked.putInt(
|
||||
key,
|
||||
properties.getInt(key, DEFAULT_LIMIT_CONFIG_MAX_DOCUMENT_SIZE_BYTES));
|
||||
}
|
||||
break;
|
||||
case KEY_LIMIT_CONFIG_MAX_DOCUMENT_COUNT:
|
||||
synchronized (mLock) {
|
||||
mBundleLocked.putInt(
|
||||
key,
|
||||
properties.getInt(key, DEFAULT_LIMIT_CONFIG_MAX_DOCUMENT_COUNT));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -173,8 +173,11 @@ public final class AppSearchUserInstanceManager {
|
||||
File appSearchDir = getAppSearchDir(userHandle);
|
||||
File icingDir = new File(appSearchDir, "icing");
|
||||
Log.i(TAG, "Creating new AppSearch instance at: " + icingDir);
|
||||
AppSearchImpl appSearchImpl =
|
||||
AppSearchImpl.create(icingDir, initStatsBuilder, new FrameworkOptimizeStrategy());
|
||||
AppSearchImpl appSearchImpl = AppSearchImpl.create(
|
||||
icingDir,
|
||||
new FrameworkLimitConfig(config),
|
||||
initStatsBuilder,
|
||||
new FrameworkOptimizeStrategy());
|
||||
|
||||
long prepareVisibilityStoreLatencyStartMillis = SystemClock.elapsedRealtime();
|
||||
VisibilityStoreImpl visibilityStore =
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
|
||||
import com.android.server.appsearch.external.localstorage.LimitConfig;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
class FrameworkLimitConfig implements LimitConfig {
|
||||
private final AppSearchConfig mAppSearchConfig;
|
||||
|
||||
FrameworkLimitConfig(@NonNull AppSearchConfig appSearchConfig) {
|
||||
mAppSearchConfig = Objects.requireNonNull(appSearchConfig);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxDocumentSizeBytes() {
|
||||
return mAppSearchConfig.getCachedLimitConfigMaxDocumentSizeBytes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxDocumentCount() {
|
||||
return mAppSearchConfig.getCachedLimitConfigMaxDocumentCount();
|
||||
}
|
||||
}
|
||||
@@ -88,6 +88,7 @@ import com.google.android.icing.proto.SearchResultProto;
|
||||
import com.google.android.icing.proto.SearchSpecProto;
|
||||
import com.google.android.icing.proto.SetSchemaResultProto;
|
||||
import com.google.android.icing.proto.StatusProto;
|
||||
import com.google.android.icing.proto.StorageInfoProto;
|
||||
import com.google.android.icing.proto.StorageInfoResultProto;
|
||||
import com.google.android.icing.proto.TypePropertyMask;
|
||||
import com.google.android.icing.proto.UsageReport;
|
||||
@@ -147,10 +148,9 @@ public final class AppSearchImpl implements Closeable {
|
||||
@VisibleForTesting static final int CHECK_OPTIMIZE_INTERVAL = 100;
|
||||
|
||||
private final ReadWriteLock mReadWriteLock = new ReentrantReadWriteLock();
|
||||
|
||||
private final LogUtil mLogUtil = new LogUtil(TAG);
|
||||
|
||||
private final OptimizeStrategy mOptimizeStrategy;
|
||||
private final LimitConfig mLimitConfig;
|
||||
|
||||
@GuardedBy("mReadWriteLock")
|
||||
@VisibleForTesting
|
||||
@@ -169,6 +169,10 @@ public final class AppSearchImpl implements Closeable {
|
||||
@GuardedBy("mReadWriteLock")
|
||||
private final Map<String, Set<String>> mNamespaceMapLocked = new HashMap<>();
|
||||
|
||||
/** Maps package name to active document count. */
|
||||
@GuardedBy("mReadWriteLock")
|
||||
private final Map<String, Integer> mDocumentCountMapLocked = new ArrayMap<>();
|
||||
|
||||
/**
|
||||
* The counter to check when to call {@link #checkForOptimize}. The interval is {@link
|
||||
* #CHECK_OPTIMIZE_INTERVAL}.
|
||||
@@ -196,19 +200,22 @@ public final class AppSearchImpl implements Closeable {
|
||||
@NonNull
|
||||
public static AppSearchImpl create(
|
||||
@NonNull File icingDir,
|
||||
@NonNull LimitConfig limitConfig,
|
||||
@Nullable InitializeStats.Builder initStatsBuilder,
|
||||
@NonNull OptimizeStrategy optimizeStrategy)
|
||||
throws AppSearchException {
|
||||
return new AppSearchImpl(icingDir, initStatsBuilder, optimizeStrategy);
|
||||
return new AppSearchImpl(icingDir, limitConfig, initStatsBuilder, optimizeStrategy);
|
||||
}
|
||||
|
||||
/** @param initStatsBuilder collects stats for initialization if provided. */
|
||||
private AppSearchImpl(
|
||||
@NonNull File icingDir,
|
||||
@NonNull LimitConfig limitConfig,
|
||||
@Nullable InitializeStats.Builder initStatsBuilder,
|
||||
@NonNull OptimizeStrategy optimizeStrategy)
|
||||
throws AppSearchException {
|
||||
Objects.requireNonNull(icingDir);
|
||||
mLimitConfig = Objects.requireNonNull(limitConfig);
|
||||
mOptimizeStrategy = Objects.requireNonNull(optimizeStrategy);
|
||||
|
||||
mReadWriteLock.writeLock().lock();
|
||||
@@ -244,9 +251,9 @@ public final class AppSearchImpl implements Closeable {
|
||||
AppSearchLoggerHelper.copyNativeStats(
|
||||
initializeResultProto.getInitializeStats(), initStatsBuilder);
|
||||
}
|
||||
|
||||
checkSuccess(initializeResultProto.getStatus());
|
||||
|
||||
// Read all protos we need to construct AppSearchImpl's cache maps
|
||||
long prepareSchemaAndNamespacesLatencyStartMillis = SystemClock.elapsedRealtime();
|
||||
SchemaProto schemaProto = getSchemaProtoLocked();
|
||||
|
||||
@@ -258,6 +265,9 @@ public final class AppSearchImpl implements Closeable {
|
||||
getAllNamespacesResultProto.getNamespacesCount(),
|
||||
getAllNamespacesResultProto);
|
||||
|
||||
StorageInfoProto storageInfoProto = getRawStorageInfoProto();
|
||||
|
||||
// Log the time it took to read the data that goes into the cache maps
|
||||
if (initStatsBuilder != null) {
|
||||
initStatsBuilder
|
||||
.setStatusCode(
|
||||
@@ -268,20 +278,27 @@ public final class AppSearchImpl implements Closeable {
|
||||
(SystemClock.elapsedRealtime()
|
||||
- prepareSchemaAndNamespacesLatencyStartMillis));
|
||||
}
|
||||
|
||||
checkSuccess(getAllNamespacesResultProto.getStatus());
|
||||
|
||||
// Populate schema map
|
||||
for (SchemaTypeConfigProto schema : schemaProto.getTypesList()) {
|
||||
List<SchemaTypeConfigProto> schemaProtoTypesList = schemaProto.getTypesList();
|
||||
for (int i = 0; i < schemaProtoTypesList.size(); i++) {
|
||||
SchemaTypeConfigProto schema = schemaProtoTypesList.get(i);
|
||||
String prefixedSchemaType = schema.getSchemaType();
|
||||
addToMap(mSchemaMapLocked, getPrefix(prefixedSchemaType), schema);
|
||||
}
|
||||
|
||||
// Populate namespace map
|
||||
for (String prefixedNamespace : getAllNamespacesResultProto.getNamespacesList()) {
|
||||
List<String> prefixedNamespaceList =
|
||||
getAllNamespacesResultProto.getNamespacesList();
|
||||
for (int i = 0; i < prefixedNamespaceList.size(); i++) {
|
||||
String prefixedNamespace = prefixedNamespaceList.get(i);
|
||||
addToMap(mNamespaceMapLocked, getPrefix(prefixedNamespace), prefixedNamespace);
|
||||
}
|
||||
|
||||
// Populate document count map
|
||||
rebuildDocumentCountMapLocked(storageInfoProto);
|
||||
|
||||
// logging prepare_schema_and_namespaces latency
|
||||
if (initStatsBuilder != null) {
|
||||
initStatsBuilder.setPrepareSchemaAndNamespacesLatencyMillis(
|
||||
@@ -596,10 +613,19 @@ public final class AppSearchImpl implements Closeable {
|
||||
long rewriteDocumentTypeEndTimeMillis = SystemClock.elapsedRealtime();
|
||||
DocumentProto finalDocument = documentBuilder.build();
|
||||
|
||||
// Check limits
|
||||
int newDocumentCount =
|
||||
enforceLimitConfigLocked(
|
||||
packageName, finalDocument.getUri(), finalDocument.getSerializedSize());
|
||||
|
||||
// Insert document
|
||||
mLogUtil.piiTrace("putDocument, request", finalDocument.getUri(), finalDocument);
|
||||
PutResultProto putResultProto = mIcingSearchEngineLocked.put(documentBuilder.build());
|
||||
PutResultProto putResultProto = mIcingSearchEngineLocked.put(finalDocument);
|
||||
mLogUtil.piiTrace("putDocument, response", putResultProto.getStatus(), putResultProto);
|
||||
addToMap(mNamespaceMapLocked, prefix, documentBuilder.getNamespace());
|
||||
|
||||
// Update caches
|
||||
addToMap(mNamespaceMapLocked, prefix, finalDocument.getNamespace());
|
||||
mDocumentCountMapLocked.put(packageName, newDocumentCount);
|
||||
|
||||
// Logging stats
|
||||
if (pStatsBuilder != null) {
|
||||
@@ -630,6 +656,71 @@ public final class AppSearchImpl implements Closeable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that a new document can be added to the given packageName with the given serialized
|
||||
* size without violating our {@link LimitConfig}.
|
||||
*
|
||||
* @return the new count of documents for the given package, including the new document.
|
||||
* @throws AppSearchException with a code of {@link AppSearchResult#RESULT_OUT_OF_SPACE} if the
|
||||
* limits are violated by the new document.
|
||||
*/
|
||||
@GuardedBy("mReadWriteLock")
|
||||
private int enforceLimitConfigLocked(String packageName, String newDocUri, int newDocSize)
|
||||
throws AppSearchException {
|
||||
// Limits check: size of document
|
||||
if (newDocSize > mLimitConfig.getMaxDocumentSizeBytes()) {
|
||||
throw new AppSearchException(
|
||||
AppSearchResult.RESULT_OUT_OF_SPACE,
|
||||
"Document \""
|
||||
+ newDocUri
|
||||
+ "\" for package \""
|
||||
+ packageName
|
||||
+ "\" serialized to "
|
||||
+ newDocSize
|
||||
+ " bytes, which exceeds "
|
||||
+ "limit of "
|
||||
+ mLimitConfig.getMaxDocumentSizeBytes()
|
||||
+ " bytes");
|
||||
}
|
||||
|
||||
// Limits check: number of documents
|
||||
Integer oldDocumentCount = mDocumentCountMapLocked.get(packageName);
|
||||
int newDocumentCount;
|
||||
if (oldDocumentCount == null) {
|
||||
newDocumentCount = 1;
|
||||
} else {
|
||||
newDocumentCount = oldDocumentCount + 1;
|
||||
}
|
||||
if (newDocumentCount > mLimitConfig.getMaxDocumentCount()) {
|
||||
// Our management of mDocumentCountMapLocked doesn't account for document
|
||||
// replacements, so our counter might have overcounted if the app has replaced docs.
|
||||
// Rebuild the counter from StorageInfo in case this is so.
|
||||
// TODO(b/170371356): If Icing lib exposes something in the result which says
|
||||
// whether the document was a replacement, we could subtract 1 again after the put
|
||||
// to keep the count accurate. That would allow us to remove this code.
|
||||
rebuildDocumentCountMapLocked(getRawStorageInfoProto());
|
||||
oldDocumentCount = mDocumentCountMapLocked.get(packageName);
|
||||
if (oldDocumentCount == null) {
|
||||
newDocumentCount = 1;
|
||||
} else {
|
||||
newDocumentCount = oldDocumentCount + 1;
|
||||
}
|
||||
}
|
||||
if (newDocumentCount > mLimitConfig.getMaxDocumentCount()) {
|
||||
// Now we really can't fit it in, even accounting for replacements.
|
||||
throw new AppSearchException(
|
||||
AppSearchResult.RESULT_OUT_OF_SPACE,
|
||||
"Package \""
|
||||
+ packageName
|
||||
+ "\" exceeded limit of "
|
||||
+ mLimitConfig.getMaxDocumentCount()
|
||||
+ " documents. Some documents "
|
||||
+ "must be removed to index additional ones.");
|
||||
}
|
||||
|
||||
return newDocumentCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a document from the AppSearch index by namespace and document ID.
|
||||
*
|
||||
@@ -1121,6 +1212,9 @@ public final class AppSearchImpl implements Closeable {
|
||||
deleteResultProto.getDeleteStats(), removeStatsBuilder);
|
||||
}
|
||||
checkSuccess(deleteResultProto.getStatus());
|
||||
|
||||
// Update derived maps
|
||||
updateDocumentCountAfterRemovalLocked(packageName, /*numDocumentsDeleted=*/ 1);
|
||||
} finally {
|
||||
mReadWriteLock.writeLock().unlock();
|
||||
if (removeStatsBuilder != null) {
|
||||
@@ -1196,6 +1290,11 @@ public final class AppSearchImpl implements Closeable {
|
||||
// not in the DB because it was not there or was successfully deleted.
|
||||
checkCodeOneOf(
|
||||
deleteResultProto.getStatus(), StatusProto.Code.OK, StatusProto.Code.NOT_FOUND);
|
||||
|
||||
// Update derived maps
|
||||
int numDocumentsDeleted =
|
||||
deleteResultProto.getDeleteStats().getNumDocumentsDeleted();
|
||||
updateDocumentCountAfterRemovalLocked(packageName, numDocumentsDeleted);
|
||||
} finally {
|
||||
mReadWriteLock.writeLock().unlock();
|
||||
if (removeStatsBuilder != null) {
|
||||
@@ -1205,6 +1304,22 @@ public final class AppSearchImpl implements Closeable {
|
||||
}
|
||||
}
|
||||
|
||||
@GuardedBy("mReadWriteLock")
|
||||
private void updateDocumentCountAfterRemovalLocked(
|
||||
@NonNull String packageName, int numDocumentsDeleted) {
|
||||
if (numDocumentsDeleted > 0) {
|
||||
Integer oldDocumentCount = mDocumentCountMapLocked.get(packageName);
|
||||
// This should always be true: how can we delete documents for a package without
|
||||
// having seen that package during init? This is just a safeguard.
|
||||
if (oldDocumentCount != null) {
|
||||
// This should always be >0; how can we remove more documents than we've indexed?
|
||||
// This is just a safeguard.
|
||||
int newDocumentCount = Math.max(oldDocumentCount - numDocumentsDeleted, 0);
|
||||
mDocumentCountMapLocked.put(packageName, newDocumentCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Estimates the storage usage info for a specific package. */
|
||||
@NonNull
|
||||
public StorageInfo getStorageInfoForPackage(@NonNull String packageName)
|
||||
@@ -1233,7 +1348,7 @@ public final class AppSearchImpl implements Closeable {
|
||||
return new StorageInfo.Builder().build();
|
||||
}
|
||||
|
||||
return getStorageInfoForNamespacesLocked(wantedPrefixedNamespaces);
|
||||
return getStorageInfoForNamespaces(getRawStorageInfoProto(), wantedPrefixedNamespaces);
|
||||
} finally {
|
||||
mReadWriteLock.readLock().unlock();
|
||||
}
|
||||
@@ -1264,29 +1379,45 @@ public final class AppSearchImpl implements Closeable {
|
||||
return new StorageInfo.Builder().build();
|
||||
}
|
||||
|
||||
return getStorageInfoForNamespacesLocked(wantedPrefixedNamespaces);
|
||||
return getStorageInfoForNamespaces(getRawStorageInfoProto(), wantedPrefixedNamespaces);
|
||||
} finally {
|
||||
mReadWriteLock.readLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@GuardedBy("mReadWriteLock")
|
||||
/**
|
||||
* Returns the native storage info capsuled in {@link StorageInfoResultProto} directly from
|
||||
* IcingSearchEngine.
|
||||
*/
|
||||
@NonNull
|
||||
private StorageInfo getStorageInfoForNamespacesLocked(@NonNull Set<String> prefixedNamespaces)
|
||||
throws AppSearchException {
|
||||
mLogUtil.piiTrace("getStorageInfo, request");
|
||||
StorageInfoResultProto storageInfoResult = mIcingSearchEngineLocked.getStorageInfo();
|
||||
mLogUtil.piiTrace(
|
||||
"getStorageInfo, response", storageInfoResult.getStatus(), storageInfoResult);
|
||||
checkSuccess(storageInfoResult.getStatus());
|
||||
if (!storageInfoResult.hasStorageInfo()
|
||||
|| !storageInfoResult.getStorageInfo().hasDocumentStorageInfo()) {
|
||||
public StorageInfoProto getRawStorageInfoProto() throws AppSearchException {
|
||||
mReadWriteLock.readLock().lock();
|
||||
try {
|
||||
throwIfClosedLocked();
|
||||
mLogUtil.piiTrace("getStorageInfo, request");
|
||||
StorageInfoResultProto storageInfoResult = mIcingSearchEngineLocked.getStorageInfo();
|
||||
mLogUtil.piiTrace(
|
||||
"getStorageInfo, response", storageInfoResult.getStatus(), storageInfoResult);
|
||||
checkSuccess(storageInfoResult.getStatus());
|
||||
return storageInfoResult.getStorageInfo();
|
||||
} finally {
|
||||
mReadWriteLock.readLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts and returns {@link StorageInfo} from {@link StorageInfoProto} based on prefixed
|
||||
* namespaces.
|
||||
*/
|
||||
@NonNull
|
||||
private static StorageInfo getStorageInfoForNamespaces(
|
||||
@NonNull StorageInfoProto storageInfoProto, @NonNull Set<String> prefixedNamespaces) {
|
||||
if (!storageInfoProto.hasDocumentStorageInfo()) {
|
||||
return new StorageInfo.Builder().build();
|
||||
}
|
||||
long totalStorageSize = storageInfoResult.getStorageInfo().getTotalStorageSize();
|
||||
|
||||
DocumentStorageInfoProto documentStorageInfo =
|
||||
storageInfoResult.getStorageInfo().getDocumentStorageInfo();
|
||||
long totalStorageSize = storageInfoProto.getTotalStorageSize();
|
||||
DocumentStorageInfoProto documentStorageInfo = storageInfoProto.getDocumentStorageInfo();
|
||||
int totalDocuments =
|
||||
documentStorageInfo.getNumAliveDocuments()
|
||||
+ documentStorageInfo.getNumExpiredDocuments();
|
||||
@@ -1436,6 +1567,7 @@ public final class AppSearchImpl implements Closeable {
|
||||
String packageName = entry.getKey();
|
||||
Set<String> databaseNames = entry.getValue();
|
||||
if (!installedPackages.contains(packageName) && databaseNames != null) {
|
||||
mDocumentCountMapLocked.remove(packageName);
|
||||
for (String databaseName : databaseNames) {
|
||||
String removedPrefix = createPrefix(packageName, databaseName);
|
||||
mSchemaMapLocked.remove(removedPrefix);
|
||||
@@ -1468,6 +1600,7 @@ public final class AppSearchImpl implements Closeable {
|
||||
mOptimizeIntervalCountLocked = 0;
|
||||
mSchemaMapLocked.clear();
|
||||
mNamespaceMapLocked.clear();
|
||||
mDocumentCountMapLocked.clear();
|
||||
if (initStatsBuilder != null) {
|
||||
initStatsBuilder
|
||||
.setHasReset(true)
|
||||
@@ -1477,6 +1610,26 @@ public final class AppSearchImpl implements Closeable {
|
||||
checkSuccess(resetResultProto.getStatus());
|
||||
}
|
||||
|
||||
@GuardedBy("mReadWriteLock")
|
||||
private void rebuildDocumentCountMapLocked(@NonNull StorageInfoProto storageInfoProto) {
|
||||
mDocumentCountMapLocked.clear();
|
||||
List<NamespaceStorageInfoProto> namespaceStorageInfoProtoList =
|
||||
storageInfoProto.getDocumentStorageInfo().getNamespaceStorageInfoList();
|
||||
for (int i = 0; i < namespaceStorageInfoProtoList.size(); i++) {
|
||||
NamespaceStorageInfoProto namespaceStorageInfoProto =
|
||||
namespaceStorageInfoProtoList.get(i);
|
||||
String packageName = getPackageName(namespaceStorageInfoProto.getNamespace());
|
||||
Integer oldCount = mDocumentCountMapLocked.get(packageName);
|
||||
int newCount;
|
||||
if (oldCount == null) {
|
||||
newCount = namespaceStorageInfoProto.getNumAliveDocuments();
|
||||
} else {
|
||||
newCount = oldCount + namespaceStorageInfoProto.getNumAliveDocuments();
|
||||
}
|
||||
mDocumentCountMapLocked.put(packageName, newCount);
|
||||
}
|
||||
}
|
||||
|
||||
/** Wrapper around schema changes */
|
||||
@VisibleForTesting
|
||||
static class RewrittenSchemaResults {
|
||||
|
||||
57
apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/LimitConfig.java
vendored
Normal file
57
apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/LimitConfig.java
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 2021 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;
|
||||
|
||||
|
||||
/**
|
||||
* Defines limits placed on users of AppSearch and enforced by {@link AppSearchImpl}.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public interface LimitConfig {
|
||||
/**
|
||||
* The maximum number of bytes a single document is allowed to be.
|
||||
*
|
||||
* <p>Enforced at the time of serializing the document into a proto.
|
||||
*
|
||||
* <p>This limit has two purposes:
|
||||
*
|
||||
* <ol>
|
||||
* <li>Prevent the system service from using too much memory during indexing or querying by
|
||||
* capping the size of the data structures it needs to buffer
|
||||
* <li>Prevent apps from using a very large amount of data by storing exceptionally large
|
||||
* documents.
|
||||
* </ol>
|
||||
*/
|
||||
int getMaxDocumentSizeBytes();
|
||||
|
||||
/**
|
||||
* The maximum number of documents a single app is allowed to index.
|
||||
*
|
||||
* <p>Enforced at indexing time.
|
||||
*
|
||||
* <p>This limit has two purposes:
|
||||
*
|
||||
* <ol>
|
||||
* <li>Protect icing lib's docid space from being overwhelmed by a single app. The overall
|
||||
* docid limit is currently 2^20 (~1 million)
|
||||
* <li>Prevent apps from using a very large amount of data on the system by storing too many
|
||||
* documents.
|
||||
* </ol>
|
||||
*/
|
||||
int getMaxDocumentCount();
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2021 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;
|
||||
|
||||
|
||||
/**
|
||||
* In Jetpack, AppSearch doesn't enforce artificial limits on number of documents or size of
|
||||
* documents, since the app is the only user of the Icing instance. Icing still enforces a docid
|
||||
* limit of 1M docs.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public class UnlimitedLimitConfig implements LimitConfig {
|
||||
@Override
|
||||
public int getMaxDocumentSizeBytes() {
|
||||
return Integer.MAX_VALUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxDocumentCount() {
|
||||
return Integer.MAX_VALUE;
|
||||
}
|
||||
}
|
||||
@@ -50,6 +50,10 @@ public class AppSearchConfigTest {
|
||||
AppSearchConfig.DEFAULT_SAMPLING_INTERVAL);
|
||||
assertThat(appSearchConfig.getCachedSamplingIntervalForPutDocumentStats()).isEqualTo(
|
||||
AppSearchConfig.DEFAULT_SAMPLING_INTERVAL);
|
||||
assertThat(appSearchConfig.getCachedLimitConfigMaxDocumentSizeBytes()).isEqualTo(
|
||||
AppSearchConfig.DEFAULT_LIMIT_CONFIG_MAX_DOCUMENT_SIZE_BYTES);
|
||||
assertThat(appSearchConfig.getCachedLimitConfigMaxDocumentCount()).isEqualTo(
|
||||
AppSearchConfig.DEFAULT_LIMIT_CONFIG_MAX_DOCUMENT_COUNT);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -264,6 +268,22 @@ public class AppSearchConfigTest {
|
||||
samplingIntervalBatchCallStats);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCustomizedValue() {
|
||||
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
|
||||
AppSearchConfig.KEY_LIMIT_CONFIG_MAX_DOCUMENT_SIZE_BYTES,
|
||||
Integer.toString(2001),
|
||||
/*makeDefault=*/ false);
|
||||
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
|
||||
AppSearchConfig.KEY_LIMIT_CONFIG_MAX_DOCUMENT_COUNT,
|
||||
Integer.toString(2002),
|
||||
/*makeDefault=*/ false);
|
||||
|
||||
AppSearchConfig appSearchConfig = AppSearchConfig.create(DIRECT_EXECUTOR);
|
||||
assertThat(appSearchConfig.getCachedLimitConfigMaxDocumentSizeBytes()).isEqualTo(2001);
|
||||
assertThat(appSearchConfig.getCachedLimitConfigMaxDocumentCount()).isEqualTo(2002);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotUsable_afterClose() {
|
||||
AppSearchConfig appSearchConfig = AppSearchConfig.create(DIRECT_EXECUTOR);
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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;
|
||||
|
||||
import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import android.provider.DeviceConfig;
|
||||
|
||||
import com.android.server.testables.TestableDeviceConfig;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Tests for {@link FrameworkLimitConfig}.
|
||||
*
|
||||
* <p>Build/Install/Run: atest FrameworksMockingServicesTests:AppSearchConfigTest
|
||||
*/
|
||||
public class FrameworkLimitConfigTest {
|
||||
@Rule
|
||||
public final TestableDeviceConfig.TestableDeviceConfigRule
|
||||
mDeviceConfigRule = new TestableDeviceConfig.TestableDeviceConfigRule();
|
||||
|
||||
@Test
|
||||
public void testDefaultValues() {
|
||||
AppSearchConfig appSearchConfig = AppSearchConfig.create(DIRECT_EXECUTOR);
|
||||
FrameworkLimitConfig config = new FrameworkLimitConfig(appSearchConfig);
|
||||
assertThat(config.getMaxDocumentSizeBytes()).isEqualTo(
|
||||
AppSearchConfig.DEFAULT_LIMIT_CONFIG_MAX_DOCUMENT_SIZE_BYTES);
|
||||
assertThat(appSearchConfig.getCachedLimitConfigMaxDocumentCount()).isEqualTo(
|
||||
AppSearchConfig.DEFAULT_LIMIT_CONFIG_MAX_DOCUMENT_COUNT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCustomizedValues() {
|
||||
AppSearchConfig appSearchConfig = AppSearchConfig.create(DIRECT_EXECUTOR);
|
||||
FrameworkLimitConfig config = new FrameworkLimitConfig(appSearchConfig);
|
||||
DeviceConfig.setProperty(
|
||||
DeviceConfig.NAMESPACE_APPSEARCH,
|
||||
AppSearchConfig.KEY_LIMIT_CONFIG_MAX_DOCUMENT_SIZE_BYTES,
|
||||
"2001",
|
||||
/*makeDefault=*/ false);
|
||||
DeviceConfig.setProperty(
|
||||
DeviceConfig.NAMESPACE_APPSEARCH,
|
||||
AppSearchConfig.KEY_LIMIT_CONFIG_MAX_DOCUMENT_COUNT,
|
||||
"2002",
|
||||
/*makeDefault=*/ false);
|
||||
|
||||
assertThat(config.getMaxDocumentSizeBytes()).isEqualTo(2001);
|
||||
assertThat(appSearchConfig.getCachedLimitConfigMaxDocumentCount()).isEqualTo(2002);
|
||||
}
|
||||
}
|
||||
@@ -92,7 +92,10 @@ public class AppSearchImplPlatformTest {
|
||||
|
||||
// Give ourselves global query permissions
|
||||
mAppSearchImpl = AppSearchImpl.create(
|
||||
mTemporaryFolder.newFolder(), /*initStatsBuilder=*/ null, ALWAYS_OPTIMIZE);
|
||||
mTemporaryFolder.newFolder(),
|
||||
new UnlimitedLimitConfig(),
|
||||
/*initStatsBuilder=*/ null,
|
||||
ALWAYS_OPTIMIZE);
|
||||
mVisibilityStore = VisibilityStoreImpl.create(mAppSearchImpl, mContext);
|
||||
mGlobalQuerierUid =
|
||||
mContext.getPackageManager().getPackageUid(mContext.getPackageName(), /*flags=*/ 0);
|
||||
|
||||
@@ -54,6 +54,7 @@ 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;
|
||||
|
||||
@@ -85,7 +86,10 @@ public class AppSearchImplTest {
|
||||
public void setUp() throws Exception {
|
||||
mAppSearchImpl =
|
||||
AppSearchImpl.create(
|
||||
mTemporaryFolder.newFolder(), /*initStatsBuilder=*/ null, ALWAYS_OPTIMIZE);
|
||||
mTemporaryFolder.newFolder(),
|
||||
new UnlimitedLimitConfig(),
|
||||
/*initStatsBuilder=*/ null,
|
||||
ALWAYS_OPTIMIZE);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -468,7 +472,11 @@ public class AppSearchImplTest {
|
||||
Context context = ApplicationProvider.getApplicationContext();
|
||||
File appsearchDir = mTemporaryFolder.newFolder();
|
||||
AppSearchImpl appSearchImpl =
|
||||
AppSearchImpl.create(appsearchDir, /*initStatsBuilder=*/ null, ALWAYS_OPTIMIZE);
|
||||
AppSearchImpl.create(
|
||||
appsearchDir,
|
||||
new UnlimitedLimitConfig(),
|
||||
/*initStatsBuilder=*/ null,
|
||||
ALWAYS_OPTIMIZE);
|
||||
|
||||
// Insert schema
|
||||
List<AppSearchSchema> schemas =
|
||||
@@ -529,7 +537,12 @@ public class AppSearchImplTest {
|
||||
// Initialize AppSearchImpl. This should cause a reset.
|
||||
InitializeStats.Builder initStatsBuilder = new InitializeStats.Builder();
|
||||
appSearchImpl.close();
|
||||
appSearchImpl = AppSearchImpl.create(appsearchDir, initStatsBuilder, ALWAYS_OPTIMIZE);
|
||||
appSearchImpl =
|
||||
AppSearchImpl.create(
|
||||
appsearchDir,
|
||||
new UnlimitedLimitConfig(),
|
||||
initStatsBuilder,
|
||||
ALWAYS_OPTIMIZE);
|
||||
|
||||
// Check recovery state
|
||||
InitializeStats initStats = initStatsBuilder.build();
|
||||
@@ -1688,7 +1701,10 @@ public class AppSearchImplTest {
|
||||
public void testThrowsExceptionIfClosed() throws Exception {
|
||||
AppSearchImpl appSearchImpl =
|
||||
AppSearchImpl.create(
|
||||
mTemporaryFolder.newFolder(), /*initStatsBuilder=*/ null, ALWAYS_OPTIMIZE);
|
||||
mTemporaryFolder.newFolder(),
|
||||
new UnlimitedLimitConfig(),
|
||||
/*initStatsBuilder=*/ null,
|
||||
ALWAYS_OPTIMIZE);
|
||||
|
||||
// Initial check that we could do something at first.
|
||||
List<AppSearchSchema> schemas =
|
||||
@@ -1816,7 +1832,11 @@ public class AppSearchImplTest {
|
||||
// Setup the index
|
||||
File appsearchDir = mTemporaryFolder.newFolder();
|
||||
AppSearchImpl appSearchImpl =
|
||||
AppSearchImpl.create(appsearchDir, /*initStatsBuilder=*/ null, ALWAYS_OPTIMIZE);
|
||||
AppSearchImpl.create(
|
||||
appsearchDir,
|
||||
new UnlimitedLimitConfig(),
|
||||
/*initStatsBuilder=*/ null,
|
||||
ALWAYS_OPTIMIZE);
|
||||
|
||||
List<AppSearchSchema> schemas =
|
||||
Collections.singletonList(new AppSearchSchema.Builder("type").build());
|
||||
@@ -1843,7 +1863,11 @@ public class AppSearchImplTest {
|
||||
|
||||
// That document should be visible even from another instance.
|
||||
AppSearchImpl appSearchImpl2 =
|
||||
AppSearchImpl.create(appsearchDir, /*initStatsBuilder=*/ null, ALWAYS_OPTIMIZE);
|
||||
AppSearchImpl.create(
|
||||
appsearchDir,
|
||||
new UnlimitedLimitConfig(),
|
||||
/*initStatsBuilder=*/ null,
|
||||
ALWAYS_OPTIMIZE);
|
||||
getResult =
|
||||
appSearchImpl2.getDocument(
|
||||
"package", "database", "namespace1", "id1", Collections.emptyMap());
|
||||
@@ -1855,7 +1879,11 @@ public class AppSearchImplTest {
|
||||
// Setup the index
|
||||
File appsearchDir = mTemporaryFolder.newFolder();
|
||||
AppSearchImpl appSearchImpl =
|
||||
AppSearchImpl.create(appsearchDir, /*initStatsBuilder=*/ null, ALWAYS_OPTIMIZE);
|
||||
AppSearchImpl.create(
|
||||
appsearchDir,
|
||||
new UnlimitedLimitConfig(),
|
||||
/*initStatsBuilder=*/ null,
|
||||
ALWAYS_OPTIMIZE);
|
||||
|
||||
List<AppSearchSchema> schemas =
|
||||
Collections.singletonList(new AppSearchSchema.Builder("type").build());
|
||||
@@ -1906,7 +1934,11 @@ public class AppSearchImplTest {
|
||||
|
||||
// Only the second document should be retrievable from another instance.
|
||||
AppSearchImpl appSearchImpl2 =
|
||||
AppSearchImpl.create(appsearchDir, /*initStatsBuilder=*/ null, ALWAYS_OPTIMIZE);
|
||||
AppSearchImpl.create(
|
||||
appsearchDir,
|
||||
new UnlimitedLimitConfig(),
|
||||
/*initStatsBuilder=*/ null,
|
||||
ALWAYS_OPTIMIZE);
|
||||
assertThrows(
|
||||
AppSearchException.class,
|
||||
() ->
|
||||
@@ -1927,7 +1959,11 @@ public class AppSearchImplTest {
|
||||
// Setup the index
|
||||
File appsearchDir = mTemporaryFolder.newFolder();
|
||||
AppSearchImpl appSearchImpl =
|
||||
AppSearchImpl.create(appsearchDir, /*initStatsBuilder=*/ null, ALWAYS_OPTIMIZE);
|
||||
AppSearchImpl.create(
|
||||
appsearchDir,
|
||||
new UnlimitedLimitConfig(),
|
||||
/*initStatsBuilder=*/ null,
|
||||
ALWAYS_OPTIMIZE);
|
||||
|
||||
List<AppSearchSchema> schemas =
|
||||
Collections.singletonList(new AppSearchSchema.Builder("type").build());
|
||||
@@ -1986,7 +2022,11 @@ public class AppSearchImplTest {
|
||||
|
||||
// Only the second document should be retrievable from another instance.
|
||||
AppSearchImpl appSearchImpl2 =
|
||||
AppSearchImpl.create(appsearchDir, /*initStatsBuilder=*/ null, ALWAYS_OPTIMIZE);
|
||||
AppSearchImpl.create(
|
||||
appsearchDir,
|
||||
new UnlimitedLimitConfig(),
|
||||
/*initStatsBuilder=*/ null,
|
||||
ALWAYS_OPTIMIZE);
|
||||
assertThrows(
|
||||
AppSearchException.class,
|
||||
() ->
|
||||
@@ -2001,4 +2041,784 @@ public class AppSearchImplTest {
|
||||
"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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,7 +67,10 @@ public class AppSearchLoggerTest {
|
||||
public void setUp() throws Exception {
|
||||
mAppSearchImpl =
|
||||
AppSearchImpl.create(
|
||||
mTemporaryFolder.newFolder(), /*initStatsBuilder=*/ null, ALWAYS_OPTIMIZE);
|
||||
mTemporaryFolder.newFolder(),
|
||||
new UnlimitedLimitConfig(),
|
||||
/*initStatsBuilder=*/ null,
|
||||
ALWAYS_OPTIMIZE);
|
||||
mLogger = new TestLogger();
|
||||
}
|
||||
|
||||
@@ -290,7 +293,11 @@ public class AppSearchLoggerTest {
|
||||
public void testLoggingStats_initializeWithoutDocuments_success() throws Exception {
|
||||
// Create an unused AppSearchImpl to generated an InitializeStats.
|
||||
InitializeStats.Builder initStatsBuilder = new InitializeStats.Builder();
|
||||
AppSearchImpl.create(mTemporaryFolder.newFolder(), initStatsBuilder, ALWAYS_OPTIMIZE);
|
||||
AppSearchImpl.create(
|
||||
mTemporaryFolder.newFolder(),
|
||||
new UnlimitedLimitConfig(),
|
||||
initStatsBuilder,
|
||||
ALWAYS_OPTIMIZE);
|
||||
InitializeStats iStats = initStatsBuilder.build();
|
||||
|
||||
assertThat(iStats).isNotNull();
|
||||
@@ -314,7 +321,11 @@ public class AppSearchLoggerTest {
|
||||
final File folder = mTemporaryFolder.newFolder();
|
||||
|
||||
AppSearchImpl appSearchImpl =
|
||||
AppSearchImpl.create(folder, /*initStatsBuilder=*/ null, ALWAYS_OPTIMIZE);
|
||||
AppSearchImpl.create(
|
||||
folder,
|
||||
new UnlimitedLimitConfig(),
|
||||
/*initStatsBuilder=*/ null,
|
||||
ALWAYS_OPTIMIZE);
|
||||
List<AppSearchSchema> schemas =
|
||||
ImmutableList.of(
|
||||
new AppSearchSchema.Builder("Type1").build(),
|
||||
@@ -336,7 +347,7 @@ public class AppSearchLoggerTest {
|
||||
|
||||
// Create another appsearchImpl on the same folder
|
||||
InitializeStats.Builder initStatsBuilder = new InitializeStats.Builder();
|
||||
AppSearchImpl.create(folder, initStatsBuilder, ALWAYS_OPTIMIZE);
|
||||
AppSearchImpl.create(folder, new UnlimitedLimitConfig(), initStatsBuilder, ALWAYS_OPTIMIZE);
|
||||
InitializeStats iStats = initStatsBuilder.build();
|
||||
|
||||
assertThat(iStats).isNotNull();
|
||||
@@ -360,7 +371,11 @@ public class AppSearchLoggerTest {
|
||||
final File folder = mTemporaryFolder.newFolder();
|
||||
|
||||
AppSearchImpl appSearchImpl =
|
||||
AppSearchImpl.create(folder, /*initStatsBuilder=*/ null, ALWAYS_OPTIMIZE);
|
||||
AppSearchImpl.create(
|
||||
folder,
|
||||
new UnlimitedLimitConfig(),
|
||||
/*initStatsBuilder=*/ null,
|
||||
ALWAYS_OPTIMIZE);
|
||||
|
||||
List<AppSearchSchema> schemas =
|
||||
ImmutableList.of(
|
||||
@@ -393,7 +408,7 @@ public class AppSearchLoggerTest {
|
||||
|
||||
// Create another appsearchImpl on the same folder
|
||||
InitializeStats.Builder initStatsBuilder = new InitializeStats.Builder();
|
||||
AppSearchImpl.create(folder, initStatsBuilder, ALWAYS_OPTIMIZE);
|
||||
AppSearchImpl.create(folder, new UnlimitedLimitConfig(), initStatsBuilder, ALWAYS_OPTIMIZE);
|
||||
InitializeStats iStats = initStatsBuilder.build();
|
||||
|
||||
// Some of other fields are already covered by AppSearchImplTest#testReset()
|
||||
@@ -484,11 +499,13 @@ public class AppSearchLoggerTest {
|
||||
.setPropertyString("nonExist", "testPut example1")
|
||||
.build();
|
||||
|
||||
// We mainly want to check the status code in stats. So we don't need to inspect the
|
||||
// exception here.
|
||||
Assert.assertThrows(
|
||||
AppSearchException.class,
|
||||
() -> mAppSearchImpl.putDocument(testPackageName, testDatabase, document, mLogger));
|
||||
AppSearchException exception =
|
||||
Assert.assertThrows(
|
||||
AppSearchException.class,
|
||||
() ->
|
||||
mAppSearchImpl.putDocument(
|
||||
testPackageName, testDatabase, document, mLogger));
|
||||
assertThat(exception.getResultCode()).isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
|
||||
|
||||
PutDocumentStats pStats = mLogger.mPutDocumentStats;
|
||||
assertThat(pStats).isNotNull();
|
||||
@@ -676,17 +693,17 @@ public class AppSearchLoggerTest {
|
||||
|
||||
RemoveStats.Builder rStatsBuilder = new RemoveStats.Builder(testPackageName, testDatabase);
|
||||
|
||||
// We mainly want to check the status code in stats. So we don't need to inspect the
|
||||
// exception here.
|
||||
Assert.assertThrows(
|
||||
AppSearchException.class,
|
||||
() ->
|
||||
mAppSearchImpl.remove(
|
||||
testPackageName,
|
||||
testDatabase,
|
||||
testNamespace,
|
||||
"invalidId",
|
||||
rStatsBuilder));
|
||||
AppSearchException exception =
|
||||
Assert.assertThrows(
|
||||
AppSearchException.class,
|
||||
() ->
|
||||
mAppSearchImpl.remove(
|
||||
testPackageName,
|
||||
testDatabase,
|
||||
testNamespace,
|
||||
"invalidId",
|
||||
rStatsBuilder));
|
||||
assertThat(exception.getResultCode()).isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
|
||||
|
||||
RemoveStats rStats = rStatsBuilder.build();
|
||||
assertThat(rStats.getPackageName()).isEqualTo(testPackageName);
|
||||
|
||||
@@ -38,6 +38,7 @@ import androidx.test.core.app.ApplicationProvider;
|
||||
|
||||
import com.android.server.appsearch.external.localstorage.AppSearchImpl;
|
||||
import com.android.server.appsearch.external.localstorage.OptimizeStrategy;
|
||||
import com.android.server.appsearch.external.localstorage.UnlimitedLimitConfig;
|
||||
import com.android.server.appsearch.external.localstorage.util.PrefixUtil;
|
||||
import com.android.server.appsearch.external.localstorage.visibilitystore.VisibilityStore;
|
||||
|
||||
@@ -88,7 +89,10 @@ public class VisibilityStoreImplTest {
|
||||
|
||||
// Give ourselves global query permissions
|
||||
AppSearchImpl appSearchImpl = AppSearchImpl.create(
|
||||
mTemporaryFolder.newFolder(), /*initStatsBuilder=*/ null, ALWAYS_OPTIMIZE);
|
||||
mTemporaryFolder.newFolder(),
|
||||
new UnlimitedLimitConfig(),
|
||||
/*initStatsBuilder=*/ null,
|
||||
ALWAYS_OPTIMIZE);
|
||||
mVisibilityStore = VisibilityStoreImpl.create(appSearchImpl, mContext);
|
||||
mUid = mContext.getPackageManager().getPackageUid(mContext.getPackageName(), /*flags=*/ 0);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user