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<KClass> 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