1 /*
<lambda>null2  * Copyright 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 @file:JvmMultifileClass
17 @file:JvmName("RoomDatabaseKt")
18 
19 package androidx.room
20 
21 import androidx.annotation.RestrictTo
22 import androidx.room.concurrent.CloseBarrier
23 import androidx.room.migration.AutoMigrationSpec
24 import androidx.room.migration.Migration
25 import androidx.sqlite.SQLiteConnection
26 import androidx.sqlite.SQLiteDriver
27 import androidx.sqlite.SQLiteException
28 import kotlin.coroutines.CoroutineContext
29 import kotlin.jvm.JvmMultifileClass
30 import kotlin.jvm.JvmName
31 import kotlin.reflect.KClass
32 import kotlinx.coroutines.CoroutineDispatcher
33 import kotlinx.coroutines.CoroutineScope
34 import kotlinx.coroutines.withContext
35 
36 /**
37  * Base class for all Room databases. All classes that are annotated with [Database] must extend
38  * this class.
39  *
40  * RoomDatabase provides direct access to the underlying database implementation but you should
41  * prefer using [Dao] classes.
42  *
43  * @see Database
44  */
45 expect abstract class RoomDatabase() {
46 
47     /**
48      * The invalidation tracker for this database.
49      *
50      * You can use the invalidation tracker to get notified when certain tables in the database are
51      * modified.
52      *
53      * @return The invalidation tracker for the database.
54      */
55     val invalidationTracker: InvalidationTracker
56 
57     /**
58      * A barrier that prevents the database from closing while the [InvalidationTracker] is using
59      * the database asynchronously.
60      *
61      * @return The barrier for [close].
62      */
63     internal val closeBarrier: CloseBarrier
64 
65     /**
66      * Called by Room when it is initialized.
67      *
68      * @param configuration The database configuration.
69      * @throws IllegalArgumentException if initialization fails.
70      */
71     internal fun init(configuration: DatabaseConfiguration)
72 
73     /**
74      * Creates a connection manager to manage database connection. Note that this function is called
75      * when the [RoomDatabase] is initialized.
76      *
77      * @param configuration The database configuration
78      * @return A new connection manager
79      */
80     internal fun createConnectionManager(
81         configuration: DatabaseConfiguration
82     ): RoomConnectionManager
83 
84     /**
85      * Creates a delegate to configure and initialize the database when it is being opened.
86      *
87      * An implementation of this function is generated by the Room processor. Note that this
88      * function is called when the [RoomDatabase] is initialized.
89      *
90      * @return A new delegate to be used while opening the database
91      */
92     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) // used in generated code
93     protected open fun createOpenDelegate(): RoomOpenDelegateMarker
94 
95     /**
96      * Creates the invalidation tracker
97      *
98      * An implementation of this function is generated by the Room processor. Note that this
99      * function is called when the [RoomDatabase] is initialized.
100      *
101      * @return A new invalidation tracker.
102      */
103     protected abstract fun createInvalidationTracker(): InvalidationTracker
104 
105     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) fun getCoroutineScope(): CoroutineScope
106 
107     /**
108      * Returns a Set of required [AutoMigrationSpec] classes.
109      *
110      * An implementation of this function is generated by the Room processor. Note that this
111      * function is called when the [RoomDatabase] is initialized.
112      *
113      * @return Creates a set that will include the classes of all required auto migration specs for
114      *   this database.
115      */
116     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) // used in generated code
117     open fun getRequiredAutoMigrationSpecClasses(): Set<KClass<out AutoMigrationSpec>>
118 
119     /**
120      * Returns a list of automatic [Migration]s that have been generated.
121      *
122      * An implementation of this function is generated by the Room processor. Note that this
123      * function is called when the [RoomDatabase] is initialized.
124      *
125      * @param autoMigrationSpecs the provided specs needed by certain migrations.
126      * @return A list of migration instances each of which is a generated 'auto migration'.
127      */
128     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) // used in generated code
129     open fun createAutoMigrations(
130         autoMigrationSpecs: Map<KClass<out AutoMigrationSpec>, AutoMigrationSpec>
131     ): List<Migration>
132 
133     /**
134      * Gets the instance of the given type converter class.
135      *
136      * This function should only be called by the generated DAO implementations.
137      *
138      * @param klass The Type Converter class.
139      * @param T The type of the expected Type Converter subclass.
140      * @return An instance of T.
141      */
142     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) // used in generated code
143     fun <T : Any> getTypeConverter(klass: KClass<T>): T
144 
145     /**
146      * Adds a provided type converter to be used in the database DAOs.
147      *
148      * @param kclass the class of the type converter
149      * @param converter an instance of the converter
150      */
151     internal fun addTypeConverter(kclass: KClass<*>, converter: Any)
152 
153     /**
154      * Returns a Map of String -> List&lt;KClass&gt; where each entry has the `key` as the DAO name
155      * and `value` as the list of type converter classes that are necessary for the database to
156      * function.
157      *
158      * An implementation of this function is generated by the Room processor. Note that this method
159      * is called when the [RoomDatabase] is initialized.
160      *
161      * @return A map that will include all required type converters for this database.
162      */
163     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) // used in generated code
164     protected open fun getRequiredTypeConverterClasses(): Map<KClass<*>, List<KClass<*>>>
165 
166     /** Property delegate of [getRequiredTypeConverterClasses] for common ext functionality. */
167     internal val requiredTypeConverterClassesMap: Map<KClass<*>, List<KClass<*>>>
168 
169     /**
170      * Initialize invalidation tracker. Note that this method is called when the [RoomDatabase] is
171      * initialized and opens a database connection.
172      *
173      * @param connection The database connection.
174      */
175     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) // used in generated code
176     protected fun internalInitInvalidationTracker(connection: SQLiteConnection)
177 
178     /**
179      * Closes the database.
180      *
181      * Once a [RoomDatabase] is closed it should no longer be used.
182      */
183     fun close()
184 
185     /**
186      * Use a connection to perform database operations.
187      *
188      * This function is for internal access to the pool, it is an unconfined coroutine function to
189      * be used by Room generated code paths. For the public version see [useReaderConnection] and
190      * [useWriterConnection].
191      */
192     internal suspend fun <R> useConnection(isReadOnly: Boolean, block: suspend (Transactor) -> R): R
193 
194     /**
195      * Journal modes for SQLite database.
196      *
197      * @see Builder#setJournalMode
198      */
199     enum class JournalMode {
200         /** Truncate journal mode. */
201         TRUNCATE,
202 
203         /** Write-Ahead Logging mode. */
204         WRITE_AHEAD_LOGGING
205     }
206 
207     /**
208      * Builder for [RoomDatabase].
209      *
210      * @param T The type of the abstract database class.
211      */
212     class Builder<T : RoomDatabase> {
213         /**
214          * Sets the [SQLiteDriver] implementation to be used by Room to open database connections.
215          *
216          * @param driver The driver
217          * @return This builder instance.
218          */
219         fun setDriver(driver: SQLiteDriver): Builder<T>
220 
221         /**
222          * Adds a migration to the builder.
223          *
224          * Each [Migration] has a start and end versions and Room runs these migrations to bring the
225          * database to the latest version.
226          *
227          * A migration can handle more than 1 version (e.g. if you have a faster path to choose when
228          * going from version 3 to 5 without going to version 4). If Room opens a database at
229          * version 3 and latest version is >= 5, Room will use the migration object that can migrate
230          * from 3 to 5 instead of 3 to 4 and 4 to 5.
231          *
232          * @param migrations The migration objects that modify the database schema with the
233          *   necessary changes for a version change.
234          * @return This builder instance.
235          */
236         fun addMigrations(vararg migrations: Migration): Builder<T>
237 
238         /**
239          * Adds an auto migration spec instance to the builder.
240          *
241          * @param autoMigrationSpec The auto migration object that is annotated with
242          *   [ProvidedAutoMigrationSpec] and is declared in an [AutoMigration] annotation.
243          * @return This builder instance.
244          */
245         fun addAutoMigrationSpec(autoMigrationSpec: AutoMigrationSpec): Builder<T>
246 
247         /**
248          * Allows Room to destructively recreate database tables if [Migration]s that would migrate
249          * old database schemas to the latest schema version are not found.
250          *
251          * When the database version on the device does not match the latest schema version, Room
252          * runs necessary [Migration]s on the database. If it cannot find the set of [Migration]s
253          * that will bring the database to the current version, it will throw an
254          * [IllegalStateException]. You can call this method to change this behavior to re-create
255          * the database tables instead of crashing.
256          *
257          * To let Room fallback to destructive migration only during a schema downgrade then use
258          * [fallbackToDestructiveMigrationOnDowngrade].
259          *
260          * @param dropAllTables Set to `true` if all tables should be dropped during destructive
261          *   migration including those not managed by Room. Recommended value is `true` as otherwise
262          *   Room could leave obsolete data when table names or existence changes between versions.
263          * @return This builder instance.
264          */
265         fun fallbackToDestructiveMigration(dropAllTables: Boolean): Builder<T>
266 
267         /**
268          * Allows Room to destructively recreate database tables if [Migration]s are not available
269          * when downgrading to old schema versions.
270          *
271          * For details, see [Builder.fallbackToDestructiveMigration].
272          *
273          * @param dropAllTables Set to `true` if all tables should be dropped during destructive
274          *   migration including those not managed by Room. Recommended value is `true` as otherwise
275          *   Room could leave obsolete data when table names or existence changes between versions.
276          * @return This builder instance.
277          */
278         fun fallbackToDestructiveMigrationOnDowngrade(dropAllTables: Boolean): Builder<T>
279 
280         /**
281          * Informs Room that it is allowed to destructively recreate database tables from specific
282          * starting schema versions.
283          *
284          * This functionality is the same [fallbackToDestructiveMigration], except that this method
285          * allows the specification of a set of schema versions for which destructive recreation is
286          * allowed.
287          *
288          * Using this method is preferable to [fallbackToDestructiveMigration] if you want to allow
289          * destructive migrations from some schema versions while still taking advantage of
290          * exceptions being thrown due to unintentionally missing migrations.
291          *
292          * Note: No versions passed to this method may also exist as either starting or ending
293          * versions in the [Migration]s provided via [addMigrations]. If a version passed to this
294          * method is found as a starting or ending version in a Migration, an exception will be
295          * thrown.
296          *
297          * @param dropAllTables Set to `true` if all tables should be dropped during destructive
298          *   migration including those not managed by Room. Recommended value is `true` as otherwise
299          *   Room could leave obsolete data when table names or existence changes between versions.
300          * @param startVersions The set of schema versions from which Room should use a destructive
301          *   migration.
302          * @return This builder instance.
303          */
304         fun fallbackToDestructiveMigrationFrom(
305             dropAllTables: Boolean,
306             vararg startVersions: Int
307         ): Builder<T>
308 
309         /**
310          * Adds a type converter instance to the builder.
311          *
312          * @param typeConverter The converter instance that is annotated with
313          *   [ProvidedTypeConverter].
314          * @return This builder instance.
315          */
316         fun addTypeConverter(typeConverter: Any): Builder<T>
317 
318         /**
319          * Sets the journal mode for this database.
320          *
321          * The value is ignored if the builder is for an 'in-memory database'. The journal mode
322          * should be consistent across multiple instances of [RoomDatabase] for a single SQLite
323          * database file.
324          *
325          * The default value is [JournalMode.WRITE_AHEAD_LOGGING].
326          *
327          * @param journalMode The journal mode.
328          * @return This builder instance.
329          */
330         fun setJournalMode(journalMode: JournalMode): Builder<T>
331 
332         /**
333          * Sets the [CoroutineContext] that will be used to execute all asynchronous queries and
334          * tasks, such as `Flow` emissions and [InvalidationTracker] notifications.
335          *
336          * If no [CoroutineDispatcher] is present in the [context] then this function will throw an
337          * [IllegalArgumentException]
338          *
339          * @param context The context
340          * @return This [Builder] instance
341          * @throws IllegalArgumentException if the [context] has no [CoroutineDispatcher]
342          */
343         fun setQueryCoroutineContext(context: CoroutineContext): Builder<T>
344 
345         /**
346          * Adds a [Callback] to this database.
347          *
348          * @param callback The callback.
349          * @return This builder instance.
350          */
351         fun addCallback(callback: Callback): Builder<T>
352 
353         /**
354          * Creates the database and initializes it.
355          *
356          * @return A new database instance.
357          * @throws IllegalArgumentException if the builder was misconfigured.
358          */
359         fun build(): T
360     }
361 
362     /**
363      * A container to hold migrations. It also allows querying its contents to find migrations
364      * between two versions.
365      */
366     class MigrationContainer() {
367         /**
368          * Returns the map of available migrations where the key is the start version of the
369          * migration, and the value is a map of (end version -> Migration).
370          *
371          * @return Map of migrations keyed by the start version
372          */
373         fun getMigrations(): Map<Int, Map<Int, Migration>>
374 
375         /**
376          * Adds the given migrations to the list of available migrations. If 2 migrations have the
377          * same start-end versions, the latter migration overrides the previous one.
378          *
379          * @param migrations List of available migrations.
380          */
381         fun addMigrations(migrations: List<Migration>)
382 
383         /**
384          * Add a [Migration] to the container. If the container already has a migration with the
385          * same start-end versions then it will be overwritten.
386          *
387          * @param migration the migration to add.
388          */
389         @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) fun addMigration(migration: Migration)
390 
391         /**
392          * Indicates if the given migration is contained within the [MigrationContainer] based on
393          * its start-end versions.
394          *
395          * @param startVersion Start version of the migration.
396          * @param endVersion End version of the migration
397          * @return True if it contains a migration with the same start-end version, false otherwise.
398          */
399         fun contains(startVersion: Int, endVersion: Int): Boolean
400 
401         /**
402          * Returns a pair corresponding to an entry in the map of available migrations whose key is
403          * [migrationStart] and its sorted keys in ascending order.
404          */
405         internal fun getSortedNodes(migrationStart: Int): Pair<Map<Int, Migration>, Iterable<Int>>?
406 
407         /**
408          * Returns a pair corresponding to an entry in the map of available migrations whose key is
409          * [migrationStart] and its sorted keys in descending order.
410          */
411         internal fun getSortedDescendingNodes(
412             migrationStart: Int
413         ): Pair<Map<Int, Migration>, Iterable<Int>>?
414     }
415 
416     /** Callback for [RoomDatabase] */
417     abstract class Callback() {
418         /**
419          * Called when the database is created for the first time.
420          *
421          * This function called after all the tables are created.
422          *
423          * @param connection The database connection.
424          */
425         open fun onCreate(connection: SQLiteConnection)
426 
427         /**
428          * Called after the database was destructively migrated.
429          *
430          * @param connection The database connection.
431          */
432         open fun onDestructiveMigration(connection: SQLiteConnection)
433 
434         /**
435          * Called when the database has been opened.
436          *
437          * @param connection The database connection.
438          */
439         open fun onOpen(connection: SQLiteConnection)
440     }
441 }
442 
443 /**
444  * Acquires a READ connection, suspending while waiting if none is available and then calling the
445  * [block] to use the connection once it is acquired. A [RoomDatabase] will have one or more READ
446  * connections. The connection to use in the [block] is an instance of [Transactor] that provides
447  * the capabilities for performing nested transactions.
448  *
449  * Using the connection after [block] completes is prohibited.
450  *
451  * The connection will be confined to the coroutine on which [block] executes, attempting to use the
452  * connection from a different coroutine will result in an error.
453  *
454  * If the current coroutine calling this function already has a confined connection, then that
455  * connection is used.
456  *
457  * A connection is a limited resource and should not be held for more than it is needed. The best
458  * practice in using connections is to avoid executing long-running computations within the [block].
459  * If a caller has to wait too long to acquire a connection a [SQLiteException] will be thrown due
460  * to a timeout.
461  *
462  * @param block The code to use the connection.
463  * @throws SQLiteException when the database is closed or a thread confined connection needs to be
464  *   upgraded or there is a timeout acquiring a connection.
465  * @see [useWriterConnection]
466  */
useReaderConnectionnull467 suspend fun <R> RoomDatabase.useReaderConnection(block: suspend (Transactor) -> R): R =
468     withContext(getCoroutineScope().coroutineContext) { useConnection(isReadOnly = true, block) }
469 
470 /**
471  * Acquires a WRITE connection, suspending while waiting if none is available and then calling the
472  * [block] to use the connection once it is acquired. A [RoomDatabase] will have only one WRITE
473  * connection. The connection to use in the [block] is an instance of [Transactor] that provides the
474  * capabilities for performing nested transactions.
475  *
476  * Using the connection after [block] completes is prohibited.
477  *
478  * The connection will be confined to the coroutine on which [block] executes, attempting to use the
479  * connection from a different coroutine will result in an error.
480  *
481  * If the current coroutine calling this function already has a confined connection, then that
482  * connection is used as long as it isn't required to be upgraded to a writer. If an upgrade is
483  * required then a [SQLiteException] is thrown.
484  *
485  * A connection is a limited resource and should not be held for more than it is needed. The best
486  * practice in using connections is to avoid executing long-running computations within the [block].
487  * If a caller has to wait too long to acquire a connection a [SQLiteException] will be thrown due
488  * to a timeout.
489  *
490  * @param block The code to use the connection.
491  * @throws SQLiteException when the database is closed or a thread confined connection needs to be
492  *   upgraded or there is a timeout acquiring a connection.
493  * @see [useReaderConnection]
494  */
useWriterConnectionnull495 suspend fun <R> RoomDatabase.useWriterConnection(block: suspend (Transactor) -> R): R =
496     withContext(getCoroutineScope().coroutineContext) { useConnection(isReadOnly = false, block) }
<lambda>null497         .also { invalidationTracker.refreshAsync() }
498 
499 /**
500  * Validates that no added migration start or end are also marked as fallback to destructive
501  * migration from.
502  */
validateMigrationsNotRequirednull503 internal fun validateMigrationsNotRequired(
504     migrationStartAndEndVersions: Set<Int>,
505     migrationsNotRequiredFrom: Set<Int>
506 ) {
507     if (migrationStartAndEndVersions.isNotEmpty()) {
508         for (version in migrationStartAndEndVersions) {
509             require(!migrationsNotRequiredFrom.contains(version)) {
510                 "Inconsistency detected. A Migration was supplied to addMigration() that has a " +
511                     "start or end version equal to a start version supplied to " +
512                     "fallbackToDestructiveMigrationFrom(). Start version is: $version"
513             }
514         }
515     }
516 }
517 
validateAutoMigrationsnull518 internal fun RoomDatabase.validateAutoMigrations(configuration: DatabaseConfiguration) {
519     val autoMigrationSpecs = mutableMapOf<KClass<out AutoMigrationSpec>, AutoMigrationSpec>()
520     val requiredAutoMigrationSpecs = getRequiredAutoMigrationSpecClasses()
521     val usedSpecs = BooleanArray(requiredAutoMigrationSpecs.size)
522     for (spec in requiredAutoMigrationSpecs) {
523         var foundIndex = -1
524         for (providedIndex in configuration.autoMigrationSpecs.indices.reversed()) {
525             val provided: Any = configuration.autoMigrationSpecs[providedIndex]
526             if (spec.isInstance(provided)) {
527                 foundIndex = providedIndex
528                 usedSpecs[foundIndex] = true
529                 break
530             }
531         }
532         require(foundIndex >= 0) {
533             "A required auto migration spec (${spec.qualifiedName}) is missing in the " +
534                 "database configuration."
535         }
536         autoMigrationSpecs[spec] = configuration.autoMigrationSpecs[foundIndex]
537     }
538     for (providedIndex in configuration.autoMigrationSpecs.indices.reversed()) {
539         require(providedIndex < usedSpecs.size && usedSpecs[providedIndex]) {
540             "Unexpected auto migration specs found. " +
541                 "Annotate AutoMigrationSpec implementation with " +
542                 "@ProvidedAutoMigrationSpec annotation or remove this spec from the " +
543                 "builder."
544         }
545     }
546     val autoMigrations = createAutoMigrations(autoMigrationSpecs)
547     for (autoMigration in autoMigrations) {
548         val migrationExists =
549             configuration.migrationContainer.contains(
550                 autoMigration.startVersion,
551                 autoMigration.endVersion
552             )
553         if (!migrationExists) {
554             configuration.migrationContainer.addMigration(autoMigration)
555         }
556     }
557 }
558 
validateTypeConvertersnull559 internal fun RoomDatabase.validateTypeConverters(configuration: DatabaseConfiguration) {
560     val requiredFactories = this.requiredTypeConverterClassesMap
561     // Indices for each converter on whether it is used or not so that we can throw an exception
562     // if developer provides an unused converter. It is not necessarily an error but likely
563     // to be because why would developer add a converter if it won't be used?
564     val used = BooleanArray(requiredFactories.size)
565     requiredFactories.forEach { (daoName, converters) ->
566         for (converter in converters) {
567             var foundIndex = -1
568             // traverse provided converters in reverse so that newer one overrides
569             for (providedIndex in configuration.typeConverters.indices.reversed()) {
570                 val provided = configuration.typeConverters[providedIndex]
571                 if (converter.isInstance(provided)) {
572                     foundIndex = providedIndex
573                     used[foundIndex] = true
574                     break
575                 }
576             }
577             require(foundIndex >= 0) {
578                 "A required type converter (${converter.qualifiedName}) for" +
579                     " ${daoName.qualifiedName} is missing in the database configuration."
580             }
581             addTypeConverter(converter, configuration.typeConverters[foundIndex])
582         }
583     }
584     // now, make sure all provided factories are used
585     for (providedIndex in configuration.typeConverters.indices.reversed()) {
586         if (!used[providedIndex]) {
587             val converter = configuration.typeConverters[providedIndex]
588             throw IllegalArgumentException(
589                 "Unexpected type converter $converter. " +
590                     "Annotate TypeConverter class with @ProvidedTypeConverter annotation " +
591                     "or remove this converter from the builder."
592             )
593         }
594     }
595 }
596