diff --git a/java/arcs/android/storage/database/AndroidSqliteDatabaseManager.kt b/java/arcs/android/storage/database/AndroidSqliteDatabaseManager.kt index d35f64ff60d..6a6ded70aa8 100644 --- a/java/arcs/android/storage/database/AndroidSqliteDatabaseManager.kt +++ b/java/arcs/android/storage/database/AndroidSqliteDatabaseManager.kt @@ -16,6 +16,7 @@ import androidx.lifecycle.LifecycleObserver import arcs.core.storage.StorageKey import arcs.core.storage.StorageKeyManager import arcs.core.storage.database.Database +import arcs.core.storage.database.DatabaseConfig import arcs.core.storage.database.DatabaseIdentifier import arcs.core.storage.database.DatabaseManager import arcs.core.storage.database.DatabasePerformanceStatistics.Snapshot @@ -25,6 +26,7 @@ import arcs.core.util.guardedBy import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock +import java.util.concurrent.atomic.AtomicReference /** * [DatabaseManager] implementation which constructs [DatabaseImpl] instances for use on Android @@ -34,13 +36,15 @@ import kotlinx.coroutines.sync.withLock class AndroidSqliteDatabaseManager( context: Context, // Maximum size of the database file, if it surpasses this size, the database gets reset. - maxDbSizeBytes: Int? = null + maxDbSizeBytes: Int? = null, + databaseConfig: DatabaseConfig = DatabaseConfig() ) : DatabaseManager, LifecycleObserver { private val context = context.applicationContext private val mutex = Mutex() private val dbCache by guardedBy(mutex, mutableMapOf()) private val maxDbSize = maxDbSizeBytes ?: MAX_DB_SIZE_BYTES override val registry = AndroidSqliteDatabaseRegistry(context) + override val databaseConfig: AtomicReference = AtomicReference(databaseConfig) // TODO(b/174432505): Don't use the GLOBAL_INSTANCE, accept as a constructor param instead. private val storageKeyManager = StorageKeyManager.GLOBAL_INSTANCE @@ -58,7 +62,7 @@ class AndroidSqliteDatabaseManager( val entry = registry.register(name, persistent) return mutex.withLock { dbCache[entry.name to entry.isPersistent] - ?: DatabaseImpl(context, storageKeyManager, name, persistent) + ?: DatabaseImpl(context, storageKeyManager, name, persistent, databaseConfig::get) .also { dbCache[entry.name to entry.isPersistent] = it } diff --git a/java/arcs/android/storage/database/DatabaseImpl.kt b/java/arcs/android/storage/database/DatabaseImpl.kt index e498b242e4f..3254ff69ef4 100644 --- a/java/arcs/android/storage/database/DatabaseImpl.kt +++ b/java/arcs/android/storage/database/DatabaseImpl.kt @@ -56,6 +56,7 @@ import arcs.core.storage.StorageKey import arcs.core.storage.StorageKeyManager import arcs.core.storage.database.Database import arcs.core.storage.database.DatabaseClient +import arcs.core.storage.database.DatabaseConfig import arcs.core.storage.database.DatabaseData import arcs.core.storage.database.DatabaseOp import arcs.core.storage.database.DatabasePerformanceStatistics @@ -125,6 +126,7 @@ class DatabaseImpl( private val storageKeyManager: StorageKeyManager, databaseName: String, persistent: Boolean = true, + @VisibleForTesting val databaseConfigGetter: () -> DatabaseConfig, val onDatabaseClose: suspend () -> Unit = {} ) : Database, SQLiteOpenHelper( context, diff --git a/java/arcs/core/storage/database/DatabaseManager.kt b/java/arcs/core/storage/database/DatabaseManager.kt index e9159a20ef2..b412f6c0009 100644 --- a/java/arcs/core/storage/database/DatabaseManager.kt +++ b/java/arcs/core/storage/database/DatabaseManager.kt @@ -13,6 +13,7 @@ package arcs.core.storage.database import arcs.core.common.collectExceptions import arcs.core.storage.StorageKey +import java.util.concurrent.atomic.AtomicReference import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.supervisorScope @@ -26,6 +27,12 @@ interface DatabaseManager { /** Manifest of [Database]s managed by this [DatabaseManager]. */ val registry: DatabaseRegistry + /** + * Database Configuration. The config can change at any time, subclasses should get fresh reads of + * it every time it's needed. + */ + val databaseConfig: AtomicReference + /** * Gets a [Database] for the given [name]. If [persistent] is `false`, the [Database] should * only exist in-memory (if possible for the current platform). @@ -87,6 +94,11 @@ interface DatabaseManager { * Extracts all IDs of any hard reference that points to the given [backingStorageKey]. */ suspend fun getAllHardReferenceIds(backingStorageKey: StorageKey): Set + + /** + * Updates the database configuration. + */ + fun updateDatabaseConfig(databaseConfig: DatabaseConfig) = this.databaseConfig.set(databaseConfig) } /** @@ -136,3 +148,9 @@ val DatabaseIdentifier.name: String /** Whether or not the [Database] should be persisted to disk. */ val DatabaseIdentifier.persistent: Boolean get() = second + +/** Database configurations of the runtime flags relevant to the database and their values. */ +data class DatabaseConfig( + /** Determines whether to use the diffbased approach when inserting entities into a collection. */ + val diffbasedEntityInsertion: Boolean = false +) diff --git a/java/arcs/jvm/storage/database/testutil/FakeDatabaseManager.kt b/java/arcs/jvm/storage/database/testutil/FakeDatabaseManager.kt index 5eb51c5b5cf..de6f3b911ea 100644 --- a/java/arcs/jvm/storage/database/testutil/FakeDatabaseManager.kt +++ b/java/arcs/jvm/storage/database/testutil/FakeDatabaseManager.kt @@ -15,6 +15,7 @@ import arcs.core.data.Schema import arcs.core.storage.StorageKey import arcs.core.storage.database.Database import arcs.core.storage.database.DatabaseClient +import arcs.core.storage.database.DatabaseConfig import arcs.core.storage.database.DatabaseData import arcs.core.storage.database.DatabaseIdentifier import arcs.core.storage.database.DatabaseManager @@ -39,6 +40,7 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock +import java.util.concurrent.atomic.AtomicReference /** [DatabaseManager] which generates fake [Database] objects. */ open class FakeDatabaseManager(val onGarbageCollection: () -> Unit = {}) : DatabaseManager { @@ -49,6 +51,7 @@ open class FakeDatabaseManager(val onGarbageCollection: () -> Unit = {}) : Datab private val _manifest = FakeDatabaseRegistry() private val clients = arrayListOf() override val registry: FakeDatabaseRegistry = _manifest + override val databaseConfig: AtomicReference = AtomicReference(DatabaseConfig()) fun addClients(vararg clients: DatabaseClient) = this.clients.addAll(clients) diff --git a/javatests/arcs/android/storage/database/AndroidSqliteDatabaseManagerTest.kt b/javatests/arcs/android/storage/database/AndroidSqliteDatabaseManagerTest.kt index 3de7c20c239..d29ca11c560 100644 --- a/javatests/arcs/android/storage/database/AndroidSqliteDatabaseManagerTest.kt +++ b/javatests/arcs/android/storage/database/AndroidSqliteDatabaseManagerTest.kt @@ -21,6 +21,7 @@ import arcs.core.data.SchemaFields import arcs.core.data.util.toReferencable import arcs.core.storage.RawReference import arcs.core.storage.StorageKeyManager +import arcs.core.storage.database.DatabaseConfig import arcs.core.storage.database.DatabaseData import arcs.core.storage.database.DatabaseManager import arcs.core.storage.testutil.DummyStorageKey @@ -267,6 +268,27 @@ class AndroidSqliteDatabaseManagerTest { assertThat(manager.removeEntitiesHardReferencing(refKey, "id2")).isEqualTo(0) } + @Test + fun updateDatabaseConfig_propagatesToDatabases() = runBlockingTest { + val database = manager.getDatabase("foo", true) as DatabaseImpl + + val managerDatabaseConfigBefore = manager.databaseConfig.get() + val databaseDatabaseConfigBefore = database.databaseConfigGetter() + + manager.updateDatabaseConfig(DatabaseConfig(diffbasedEntityInsertion = true)) + + val managerDatabaseConfigAfter = manager.databaseConfig.get() + val databaseDatabaseConfigAfter = database.databaseConfigGetter() + + val expectedBefore = DatabaseConfig() + val expectedAfter = DatabaseConfig(diffbasedEntityInsertion = true) + + assertThat(managerDatabaseConfigBefore).isEqualTo(expectedBefore) + assertThat(databaseDatabaseConfigBefore).isEqualTo(expectedBefore) + assertThat(managerDatabaseConfigAfter).isEqualTo(expectedAfter) + assertThat(databaseDatabaseConfigAfter).isEqualTo(expectedAfter) + } + private fun entityWithHardRef(refId: String) = DatabaseData.Entity( RawEntity( collections = mapOf( diff --git a/javatests/arcs/android/storage/database/DatabaseDowngradeTest.kt b/javatests/arcs/android/storage/database/DatabaseDowngradeTest.kt index ea6a4d5a6e1..c4a5c530436 100644 --- a/javatests/arcs/android/storage/database/DatabaseDowngradeTest.kt +++ b/javatests/arcs/android/storage/database/DatabaseDowngradeTest.kt @@ -18,6 +18,7 @@ import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import arcs.android.common.map import arcs.android.common.transaction +import arcs.core.storage.database.DatabaseConfig import arcs.core.storage.testutil.DummyStorageKeyManager import com.google.common.truth.Truth.assertThat import org.junit.Test @@ -47,7 +48,8 @@ class DatabaseDowngradeTest { ApplicationProvider.getApplicationContext(), DummyStorageKeyManager(), "arcs", - true + true, + { DatabaseConfig() } ) // Open up the databaseImpl, so it performs a downgrade. diff --git a/javatests/arcs/android/storage/database/DatabaseImplFuzzTest.kt b/javatests/arcs/android/storage/database/DatabaseImplFuzzTest.kt index be4b9db5158..4efaac0459a 100644 --- a/javatests/arcs/android/storage/database/DatabaseImplFuzzTest.kt +++ b/javatests/arcs/android/storage/database/DatabaseImplFuzzTest.kt @@ -20,6 +20,7 @@ import arcs.android.storage.database.testutil.SmallIntegerIdGenerator import arcs.android.util.testutil.AndroidLogRule import arcs.core.data.SchemaRegistry import arcs.core.storage.StorageKeyManager +import arcs.core.storage.database.DatabaseConfig import arcs.core.storage.testutil.DummyStorageKey import arcs.core.storage.testutil.DummyStorageKeyManager import arcs.core.testutil.runFuzzTest @@ -48,7 +49,8 @@ class DatabaseImplFuzzTest { database = DatabaseImpl( ApplicationProvider.getApplicationContext(), DummyStorageKeyManager(), - "test.sqlite3" + "test.sqlite3", + databaseConfigGetter = { DatabaseConfig() } ) StorageKeyManager.GLOBAL_INSTANCE.addParser(DummyStorageKey) } diff --git a/javatests/arcs/android/storage/database/DatabaseImplTest.kt b/javatests/arcs/android/storage/database/DatabaseImplTest.kt index 0bea734dc47..4c0b5307f30 100644 --- a/javatests/arcs/android/storage/database/DatabaseImplTest.kt +++ b/javatests/arcs/android/storage/database/DatabaseImplTest.kt @@ -36,6 +36,7 @@ import arcs.core.storage.RawReference import arcs.core.storage.StorageKey import arcs.core.storage.StorageKeyManager import arcs.core.storage.database.DatabaseClient +import arcs.core.storage.database.DatabaseConfig import arcs.core.storage.database.DatabaseData import arcs.core.storage.database.DatabaseOp import arcs.core.storage.database.ReferenceWithVersion @@ -91,7 +92,8 @@ class DatabaseImplTest(private val parameters: ParameterizedBuildFlags) { database = DatabaseImpl( ApplicationProvider.getApplicationContext(), DummyStorageKeyManager(), - "test.sqlite3" + "test.sqlite3", + databaseConfigGetter = { DatabaseConfig() } ) db = database.writableDatabase StorageKeyManager.GLOBAL_INSTANCE.addParser(DummyStorageKey) @@ -3974,7 +3976,8 @@ class DatabaseImplTest(private val parameters: ParameterizedBuildFlags) { ApplicationProvider.getApplicationContext(), DummyStorageKeyManager(), "test.sqlite3", - persistent = false + persistent = false, + { DatabaseConfig() } ) assertThat(inMemoryDatabase.getSize()).isGreaterThan(0) @@ -4134,6 +4137,7 @@ class DatabaseImplTest(private val parameters: ParameterizedBuildFlags) { ApplicationProvider.getApplicationContext(), DummyStorageKeyManager(), "test.sqlite3", + databaseConfigGetter = { DatabaseConfig() }, onDatabaseClose = { onCloseCalled = true }