1 /*
<lambda>null2 * Copyright (C) 2016 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 android.annotation.SuppressLint
22 import android.app.ActivityManager
23 import android.content.Context
24 import android.content.Intent
25 import android.database.Cursor
26 import android.os.CancellationSignal
27 import android.os.Looper
28 import android.util.Log
29 import androidx.annotation.CallSuper
30 import androidx.annotation.IntRange
31 import androidx.annotation.RestrictTo
32 import androidx.annotation.WorkerThread
33 import androidx.arch.core.executor.ArchTaskExecutor
34 import androidx.room.Room.LOG_TAG
35 import androidx.room.concurrent.CloseBarrier
36 import androidx.room.coroutines.runBlockingUninterruptible
37 import androidx.room.driver.SupportSQLiteConnection
38 import androidx.room.migration.AutoMigrationSpec
39 import androidx.room.migration.Migration
40 import androidx.room.support.AutoCloser
41 import androidx.room.support.AutoClosingRoomOpenHelper
42 import androidx.room.support.AutoClosingRoomOpenHelperFactory
43 import androidx.room.support.PrePackagedCopyOpenHelper
44 import androidx.room.support.PrePackagedCopyOpenHelperFactory
45 import androidx.room.support.QueryInterceptorOpenHelperFactory
46 import androidx.room.util.contains as containsCommon
47 import androidx.room.util.findAndInstantiateDatabaseImpl
48 import androidx.room.util.findMigrationPath as findMigrationPathExt
49 import androidx.room.util.performBlocking
50 import androidx.sqlite.SQLiteConnection
51 import androidx.sqlite.SQLiteDriver
52 import androidx.sqlite.db.SimpleSQLiteQuery
53 import androidx.sqlite.db.SupportSQLiteDatabase
54 import androidx.sqlite.db.SupportSQLiteOpenHelper
55 import androidx.sqlite.db.SupportSQLiteQuery
56 import androidx.sqlite.db.SupportSQLiteStatement
57 import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory
58 import java.io.File
59 import java.io.InputStream
60 import java.util.TreeMap
61 import java.util.concurrent.Callable
62 import java.util.concurrent.Executor
63 import java.util.concurrent.RejectedExecutionException
64 import java.util.concurrent.TimeUnit
65 import java.util.concurrent.atomic.AtomicInteger
66 import kotlin.coroutines.ContinuationInterceptor
67 import kotlin.coroutines.CoroutineContext
68 import kotlin.coroutines.coroutineContext
69 import kotlin.coroutines.resume
70 import kotlin.reflect.KClass
71 import kotlinx.coroutines.CoroutineDispatcher
72 import kotlinx.coroutines.CoroutineScope
73 import kotlinx.coroutines.ExperimentalCoroutinesApi
74 import kotlinx.coroutines.Job
75 import kotlinx.coroutines.SupervisorJob
76 import kotlinx.coroutines.asContextElement
77 import kotlinx.coroutines.asCoroutineDispatcher
78 import kotlinx.coroutines.asExecutor
79 import kotlinx.coroutines.cancel
80 import kotlinx.coroutines.flow.Flow
81 import kotlinx.coroutines.runBlocking
82 import kotlinx.coroutines.suspendCancellableCoroutine
83 import kotlinx.coroutines.withContext
84
85 /**
86 * Base class for all Room databases. All classes that are annotated with [Database] must extend
87 * this class.
88 *
89 * RoomDatabase provides direct access to the underlying database implementation but you should
90 * prefer using [Dao] classes.
91 *
92 * @constructor You cannot create an instance of a database, instead, you should acquire it via
93 * [#Room.databaseBuilder] or [#Room.inMemoryDatabaseBuilder].
94 * @see Database
95 */
96 actual abstract class RoomDatabase {
97 @Volatile
98 @JvmField
99 @Deprecated(
100 message = "This property is always null and will be removed in a future version.",
101 level = DeprecationLevel.ERROR
102 )
103 protected var mDatabase: SupportSQLiteDatabase? = null
104
105 private lateinit var coroutineScope: CoroutineScope
106 private lateinit var transactionContext: CoroutineContext
107
108 /** The Executor in use by this database for async queries. */
109 open val queryExecutor: Executor
110 get() = internalQueryExecutor
111
112 private lateinit var internalQueryExecutor: Executor
113
114 /** The Executor in use by this database for async transactions. */
115 open val transactionExecutor: Executor
116 get() = internalTransactionExecutor
117
118 private lateinit var internalTransactionExecutor: Executor
119
120 /**
121 * The SQLite open helper used by this database.
122 *
123 * @throws IllegalStateException If a [SQLiteDriver] is configured with this database.
124 */
125 open val openHelper: SupportSQLiteOpenHelper
126 get() =
127 connectionManager.supportOpenHelper
128 ?: error(
129 "Cannot return a SupportSQLiteOpenHelper since no " +
130 "SupportSQLiteOpenHelper.Factory was configured with Room."
131 )
132
133 private lateinit var connectionManager: RoomConnectionManager
134
135 /**
136 * The invalidation tracker for this database.
137 *
138 * You can use the invalidation tracker to get notified when certain tables in the database are
139 * modified.
140 *
141 * @return The invalidation tracker for the database.
142 */
143 actual open val invalidationTracker: InvalidationTracker
144 get() = internalTracker
145
146 private lateinit var internalTracker: InvalidationTracker
147
148 /**
149 * A barrier that prevents the database from closing while the [InvalidationTracker] is using
150 * the database asynchronously.
151 *
152 * @return The barrier for [close].
153 */
154 internal actual val closeBarrier = CloseBarrier(::onClosed)
155
156 private var allowMainThreadQueries = false
157
158 @JvmField
159 @Deprecated(
160 message = "This property is always null and will be removed in a future version.",
161 level = DeprecationLevel.ERROR
162 )
163 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) // used in generated code
164 protected var mCallbacks: List<Callback>? = null
165
166 private var autoCloser: AutoCloser? = null
167
168 /**
169 * Suspending transaction id of the current thread.
170 *
171 * This id is only set on threads that are used to dispatch coroutines within a suspending
172 * database transaction.
173 */
174 @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) val suspendingTransactionId = ThreadLocal<Int>()
175
176 private val typeConverters: MutableMap<KClass<*>, Any> = mutableMapOf()
177
178 internal var useTempTrackingTable: Boolean = true
179
180 /**
181 * Gets the instance of the given Type Converter.
182 *
183 * @param klass The Type Converter class.
184 * @param T The type of the expected Type Converter subclass.
185 * @return An instance of T if it is provided in the builder.
186 */
187 @Deprecated("No longer called by generated implementation")
188 @Suppress("UNCHECKED_CAST")
189 open fun <T : Any> getTypeConverter(klass: Class<T>): T? {
190 return typeConverters[klass.kotlin] as T?
191 }
192
193 /**
194 * Gets the instance of the given type converter class.
195 *
196 * This method should only be called by the generated DAO implementations.
197 *
198 * @param klass The Type Converter class.
199 * @param T The type of the expected Type Converter subclass.
200 * @return An instance of T.
201 */
202 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) // used in generated code
203 @Suppress("UNCHECKED_CAST")
204 actual fun <T : Any> getTypeConverter(klass: KClass<T>): T {
205 return typeConverters[klass] as T
206 }
207
208 /**
209 * Adds a provided type converter to be used in the database DAOs.
210 *
211 * @param kclass the class of the type converter
212 * @param converter an instance of the converter
213 */
214 internal actual fun addTypeConverter(kclass: KClass<*>, converter: Any) {
215 typeConverters[kclass] = converter
216 }
217
218 /**
219 * Called by Room when it is initialized.
220 *
221 * @param configuration The database configuration.
222 * @throws IllegalArgumentException if initialization fails.
223 */
224 @CallSuper
225 @OptIn(ExperimentalCoroutinesApi::class) // For limitedParallelism(1)
226 actual open fun init(configuration: DatabaseConfiguration) {
227 useTempTrackingTable = configuration.useTempTrackingTable
228
229 connectionManager = createConnectionManager(configuration)
230 internalTracker = createInvalidationTracker()
231 validateAutoMigrations(configuration)
232 validateTypeConverters(configuration)
233
234 if (configuration.queryCoroutineContext != null) {
235 // For backwards compatibility with internals not converted to Coroutines, use the
236 // provided dispatcher as executor.
237 val dispatcher =
238 configuration.queryCoroutineContext[ContinuationInterceptor] as CoroutineDispatcher
239 internalQueryExecutor = dispatcher.asExecutor()
240 internalTransactionExecutor = TransactionExecutor(internalQueryExecutor)
241 // For Room's coroutine scope, we use the provided context but add a SupervisorJob that
242 // is tied to the given Job (if any).
243 val parentJob = configuration.queryCoroutineContext[Job]
244 coroutineScope =
245 CoroutineScope(configuration.queryCoroutineContext + SupervisorJob(parentJob))
246 transactionContext =
247 if (inCompatibilityMode()) {
248 // To prevent starvation due to primary connection blocking in
249 // SupportSQLiteDatabase a limited dispatcher is used for transactions.
250 coroutineScope.coroutineContext + dispatcher.limitedParallelism(1)
251 } else {
252 // When a SQLiteDriver is provided a suspending connection pool is used and
253 // there is no reason to limit parallelism.
254 coroutineScope.coroutineContext
255 }
256 } else {
257 internalQueryExecutor = configuration.queryExecutor
258 internalTransactionExecutor = TransactionExecutor(configuration.transactionExecutor)
259 // For Room's coroutine scope, we use the provided executor as dispatcher along with a
260 // SupervisorJob.
261 coroutineScope =
262 CoroutineScope(internalQueryExecutor.asCoroutineDispatcher() + SupervisorJob())
263 transactionContext =
264 coroutineScope.coroutineContext +
265 internalTransactionExecutor.asCoroutineDispatcher()
266 }
267
268 allowMainThreadQueries = configuration.allowMainThreadQueries
269
270 // Configure PrePackagedCopyOpenHelper if it is available
271 unwrapOpenHelper<PrePackagedCopyOpenHelper>(connectionManager.supportOpenHelper)
272 ?.setDatabaseConfiguration(configuration)
273
274 // Configure AutoClosingRoomOpenHelper if it is available
275 unwrapOpenHelper<AutoClosingRoomOpenHelper>(connectionManager.supportOpenHelper)?.let {
276 autoCloser = it.autoCloser
277 it.autoCloser.initCoroutineScope(coroutineScope)
278 invalidationTracker.setAutoCloser(it.autoCloser)
279 }
280
281 // Configure multi-instance invalidation, if enabled
282 if (configuration.multiInstanceInvalidationServiceIntent != null) {
283 requireNotNull(configuration.name)
284 invalidationTracker.initMultiInstanceInvalidation(
285 configuration.context,
286 configuration.name,
287 configuration.multiInstanceInvalidationServiceIntent
288 )
289 }
290 }
291
292 /**
293 * Creates a connection manager to manage database connection. Note that this method is called
294 * when the [RoomDatabase] is initialized.
295 *
296 * @return A new connection manager.
297 */
298 internal actual fun createConnectionManager(
299 configuration: DatabaseConfiguration
300 ): RoomConnectionManager {
301 val openDelegate =
302 try {
303 createOpenDelegate() as RoomOpenDelegate
304 } catch (ex: NotImplementedError) {
305 null
306 }
307 // If createOpenDelegate() is not implemented then the database implementation was
308 // generated with an older compiler, we are force to create a connection manager
309 // using the SupportSQLiteOpenHelper returned from createOpenHelper() with the
310 // deprecated RoomOpenHelper installed.
311 return if (openDelegate == null) {
312 @Suppress("DEPRECATION")
313 RoomConnectionManager(
314 config = configuration,
315 supportOpenHelperFactory = { config -> createOpenHelper(config) }
316 )
317 } else {
318 RoomConnectionManager(config = configuration, openDelegate = openDelegate)
319 }
320 }
321
322 /**
323 * Returns a list of [Migration] of a database that have been automatically generated.
324 *
325 * @param autoMigrationSpecs
326 * @return A list of migration instances each of which is a generated autoMigration
327 */
328 @Deprecated("No longer implemented by generated")
329 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) // used in generated code
330 @JvmSuppressWildcards // Suppress wildcards due to generated Java code
331 open fun getAutoMigrations(
332 autoMigrationSpecs: Map<Class<out AutoMigrationSpec>, AutoMigrationSpec>
333 ): List<Migration> {
334 return emptyList()
335 }
336
337 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) // used in generated code
338 actual open fun createAutoMigrations(
339 autoMigrationSpecs: Map<KClass<out AutoMigrationSpec>, AutoMigrationSpec>
340 ): List<Migration> {
341 // For backwards compatibility when newer runtime is used with older generated code,
342 // call the Java version of getAutoMigrations()
343 val javaClassesMap = autoMigrationSpecs.mapKeys { it.key.java }
344 @Suppress("DEPRECATION") return getAutoMigrations(javaClassesMap)
345 }
346
347 /**
348 * Unwraps (delegating) open helpers until it finds [T], otherwise returns null.
349 *
350 * @param openHelper the open helper to search through
351 * @param T the type of open helper type to search for
352 * @return the instance of [T], otherwise null
353 */
354 private inline fun <reified T : SupportSQLiteOpenHelper> unwrapOpenHelper(
355 openHelper: SupportSQLiteOpenHelper?
356 ): T? {
357 if (openHelper == null) {
358 return null
359 }
360 var current: SupportSQLiteOpenHelper = openHelper
361 while (true) {
362 if (current is T) {
363 return current
364 }
365 if (current is DelegatingOpenHelper) {
366 current = current.delegate
367 } else {
368 break
369 }
370 }
371 return null
372 }
373
374 /**
375 * Creates the open helper to access the database. Generated class already implements this
376 * method. Note that this method is called when the RoomDatabase is initialized.
377 *
378 * @param config The configuration of the Room database.
379 * @return A new SupportSQLiteOpenHelper to be used while connecting to the database.
380 * @throws NotImplementedError by default
381 */
382 @Deprecated("No longer implemented by generated")
383 protected open fun createOpenHelper(config: DatabaseConfiguration): SupportSQLiteOpenHelper {
384 throw NotImplementedError()
385 }
386
387 /**
388 * Creates a delegate to configure and initialize the database when it is being opened. An
389 * implementation of this function is generated by the Room processor. Note that this method is
390 * called when the [RoomDatabase] is initialized.
391 *
392 * @return A new delegate to be used while opening the database
393 * @throws NotImplementedError by default
394 */
395 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) // used in generated code
396 protected actual open fun createOpenDelegate(): RoomOpenDelegateMarker {
397 throw NotImplementedError()
398 }
399
400 /**
401 * Creates the invalidation tracker
402 *
403 * An implementation of this function is generated by the Room processor. Note that this method
404 * is called when the [RoomDatabase] is initialized.
405 *
406 * @return A new invalidation tracker.
407 */
408 protected actual abstract fun createInvalidationTracker(): InvalidationTracker
409
410 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
411 actual fun getCoroutineScope(): CoroutineScope {
412 return coroutineScope
413 }
414
415 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
416 fun getQueryContext(): CoroutineContext {
417 return coroutineScope.coroutineContext
418 }
419
420 internal fun getTransactionContext(): CoroutineContext {
421 return transactionContext
422 }
423
424 /**
425 * Returns a Map of String -> List<Class> where each entry has the `key` as the DAO name
426 * and `value` as the list of type converter classes that are necessary for the database to
427 * function.
428 *
429 * This is implemented by the generated code.
430 *
431 * @return Creates a map that will include all required type converters for this database.
432 */
433 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) // used in generated code
434 protected open fun getRequiredTypeConverters(): Map<Class<*>, List<Class<*>>> {
435 return emptyMap()
436 }
437
438 /**
439 * Returns a Map of String -> List<KClass> where each entry has the `key` as the DAO name
440 * and `value` as the list of type converter classes that are necessary for the database to
441 * function.
442 *
443 * An implementation of this function is generated by the Room processor. Note that this method
444 * is called when the [RoomDatabase] is initialized.
445 *
446 * @return A map that will include all required type converters for this database.
447 */
448 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) // used in generated code
449 protected actual open fun getRequiredTypeConverterClasses(): Map<KClass<*>, List<KClass<*>>> {
450 // For backwards compatibility when newer runtime is used with older generated code,
451 // call the Java version this function.
452 return getRequiredTypeConverters().entries.associate { (key, value) ->
453 key.kotlin to value.map { it.kotlin }
454 }
455 }
456
457 /** Property delegate of [getRequiredTypeConverterClasses] for common ext functionality. */
458 internal actual val requiredTypeConverterClassesMap: Map<KClass<*>, List<KClass<*>>>
459 get() = getRequiredTypeConverterClasses()
460
461 /**
462 * Returns a Set of required AutoMigrationSpec classes.
463 *
464 * This is implemented by the generated code.
465 *
466 * @return Creates a set that will include all required auto migration specs for this database.
467 */
468 @Deprecated("No longer implemented by generated")
469 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) // used in generated code
470 open fun getRequiredAutoMigrationSpecs(): Set<Class<out AutoMigrationSpec>> {
471 return emptySet()
472 }
473
474 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) // used in generated code
475 actual open fun getRequiredAutoMigrationSpecClasses(): Set<KClass<out AutoMigrationSpec>> {
476 // For backwards compatibility when newer runtime is used with older generated code,
477 // call the Java version of this function.
478 @Suppress("DEPRECATION") return getRequiredAutoMigrationSpecs().map { it.kotlin }.toSet()
479 }
480
481 /**
482 * Deletes all rows from all the tables that are registered to this database as
483 * [Database.entities].
484 *
485 * This does NOT reset the auto-increment value generated by [PrimaryKey.autoGenerate].
486 *
487 * After deleting the rows, Room will set a WAL checkpoint and run VACUUM. This means that the
488 * data is completely erased. The space will be reclaimed by the system if the amount surpasses
489 * the threshold of database file size.
490 *
491 * See SQLite documentation for details. [FileFormat](https://www.sqlite.org/fileformat.html)
492 */
493 @WorkerThread abstract fun clearAllTables()
494
495 /**
496 * Performs a 'clear all tables' operation.
497 *
498 * This should only be invoked from generated code.
499 *
500 * @see [RoomDatabase.clearAllTables]
501 */
502 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
503 protected fun performClear(hasForeignKeys: Boolean, vararg tableNames: String) {
504 assertNotMainThread()
505 assertNotSuspendingTransaction()
506 runBlockingUninterruptible {
507 connectionManager.useConnection(isReadOnly = false) { connection ->
508 if (!connection.inTransaction()) {
509 invalidationTracker.sync()
510 }
511 connection.withTransaction(Transactor.SQLiteTransactionType.IMMEDIATE) {
512 if (hasForeignKeys) {
513 execSQL("PRAGMA defer_foreign_keys = TRUE")
514 }
515 tableNames.forEach { tableName -> execSQL("DELETE FROM `$tableName`") }
516 }
517 if (!connection.inTransaction()) {
518 connection.execSQL("PRAGMA wal_checkpoint(FULL)")
519 connection.execSQL("VACUUM")
520 invalidationTracker.refreshAsync()
521 }
522 }
523 }
524 }
525
526 /**
527 * True if database connection is open and initialized.
528 *
529 * When Room is configured with [RoomDatabase.Builder.setAutoCloseTimeout] the database is
530 * considered open even if internally the connection has been closed, unless manually closed.
531 *
532 * @return true if the database connection is open, false otherwise.
533 */
534 open val isOpen: Boolean
535 get() = autoCloser?.isActive ?: connectionManager.isSupportDatabaseOpen()
536
537 /** True if the actual database connection is open, regardless of auto-close. */
538 @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
539 val isOpenInternal: Boolean
540 get() = connectionManager.isSupportDatabaseOpen()
541
542 /**
543 * Closes the database.
544 *
545 * Once a [RoomDatabase] is closed it should no longer be used.
546 */
547 actual open fun close() {
548 closeBarrier.close()
549 }
550
551 private fun onClosed() {
552 coroutineScope.cancel()
553 invalidationTracker.stop()
554 connectionManager.close()
555 }
556
557 /** True if the calling thread is the main thread. */
558 internal val isMainThread: Boolean
559 get() = Looper.getMainLooper().thread === Thread.currentThread()
560
561 /** Asserts that we are not on the main thread. */
562 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) // used in generated code
563 open fun assertNotMainThread() {
564 if (allowMainThreadQueries) {
565 return
566 }
567 check(!isMainThread) {
568 "Cannot access database on the main thread since" +
569 " it may potentially lock the UI for a long period of time."
570 }
571 }
572
573 /** Asserts that we are not on a suspending transaction. */
574 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) // used in generated code
575 open fun assertNotSuspendingTransaction() {
576 check(!inCompatibilityMode() || inTransaction() || suspendingTransactionId.get() == null) {
577 "Cannot access database on a different coroutine" +
578 " context inherited from a suspending transaction."
579 }
580 }
581
582 /**
583 * Use a connection to perform database operations.
584 *
585 * This function is for internal access to the pool, it is an unconfined coroutine function to
586 * be used by Room generated code paths. For the public version see [useReaderConnection] and
587 * [useWriterConnection].
588 */
589 internal actual suspend fun <R> useConnection(
590 isReadOnly: Boolean,
591 block: suspend (Transactor) -> R
592 ): R {
593 return connectionManager.useConnection(isReadOnly, block)
594 }
595
596 /**
597 * Return true if this database is operating in compatibility mode, otherwise false.
598 *
599 * Room is considered in compatibility mode in Android when no [SQLiteDriver] was provided and
600 * [androidx.sqlite.db] APIs are used instead (SupportSQLite*).
601 *
602 * @see RoomConnectionManager
603 */
604 internal fun inCompatibilityMode(): Boolean = connectionManager.supportOpenHelper != null
605
606 // Below, there are wrapper methods for SupportSQLiteDatabase. This helps us track which
607 // methods we are using and also helps unit tests to mock this class without mocking
608 // all SQLite database methods.
609 /**
610 * Convenience method to query the database with arguments.
611 *
612 * @param query The sql query
613 * @param args The bind arguments for the placeholders in the query
614 * @return A Cursor obtained by running the given query in the Room database.
615 * @throws IllegalStateException If a [SQLiteDriver] is configured with this database.
616 */
617 open fun query(query: String, args: Array<out Any?>?): Cursor {
618 assertNotMainThread()
619 assertNotSuspendingTransaction()
620 return openHelper.writableDatabase.query(SimpleSQLiteQuery(query, args))
621 }
622
623 /**
624 * Wrapper for [SupportSQLiteDatabase.query].
625 *
626 * @param query The Query which includes the SQL and a bind callback for bind arguments.
627 * @param signal The cancellation signal to be attached to the query.
628 * @return Result of the query.
629 * @throws IllegalStateException If a [SQLiteDriver] is configured with this database.
630 */
631 @JvmOverloads
632 open fun query(query: SupportSQLiteQuery, signal: CancellationSignal? = null): Cursor {
633 assertNotMainThread()
634 assertNotSuspendingTransaction()
635 return if (signal != null) {
636 openHelper.writableDatabase.query(query, signal)
637 } else {
638 openHelper.writableDatabase.query(query)
639 }
640 }
641
642 /**
643 * Wrapper for [SupportSQLiteDatabase.compileStatement].
644 *
645 * @param sql The query to compile.
646 * @return The compiled query.
647 * @throws IllegalStateException If a [SQLiteDriver] is configured with this database.
648 */
649 open fun compileStatement(sql: String): SupportSQLiteStatement {
650 assertNotMainThread()
651 assertNotSuspendingTransaction()
652 return openHelper.writableDatabase.compileStatement(sql)
653 }
654
655 /**
656 * Wrapper for [SupportSQLiteDatabase.beginTransaction].
657 *
658 * @throws IllegalStateException If a [SQLiteDriver] is configured with this database.
659 */
660 @Deprecated("beginTransaction() is deprecated", ReplaceWith("runInTransaction(Runnable)"))
661 open fun beginTransaction() {
662 assertNotMainThread()
663 val autoCloser = autoCloser
664 if (autoCloser == null) {
665 internalBeginTransaction()
666 } else {
667 autoCloser.executeRefCountingFunction { internalBeginTransaction() }
668 }
669 }
670
671 private fun internalBeginTransaction() {
672 assertNotMainThread()
673 val database = openHelper.writableDatabase
674 if (!database.inTransaction()) {
675 invalidationTracker.syncBlocking()
676 }
677 if (database.isWriteAheadLoggingEnabled) {
678 database.beginTransactionNonExclusive()
679 } else {
680 database.beginTransaction()
681 }
682 }
683
684 /**
685 * Wrapper for [SupportSQLiteDatabase.endTransaction].
686 *
687 * @throws IllegalStateException If a [SQLiteDriver] is configured with this database.
688 */
689 @Deprecated("endTransaction() is deprecated", ReplaceWith("runInTransaction(Runnable)"))
690 open fun endTransaction() {
691 val autoCloser = autoCloser
692 if (autoCloser == null) {
693 internalEndTransaction()
694 } else {
695 autoCloser.executeRefCountingFunction { internalEndTransaction() }
696 }
697 }
698
699 private fun internalEndTransaction() {
700 openHelper.writableDatabase.endTransaction()
701 if (!inTransaction()) {
702 // enqueue refresh only if we are NOT in a transaction. Otherwise, wait for the last
703 // endTransaction call to do it.
704 invalidationTracker.refreshVersionsAsync()
705 }
706 }
707
708 /**
709 * Wrapper for [SupportSQLiteDatabase.setTransactionSuccessful].
710 *
711 * @throws IllegalStateException If a [SQLiteDriver] is configured with this database.
712 */
713 @Deprecated(
714 "setTransactionSuccessful() is deprecated",
715 ReplaceWith("runInTransaction(Runnable)")
716 )
717 open fun setTransactionSuccessful() {
718 openHelper.writableDatabase.setTransactionSuccessful()
719 }
720
721 /**
722 * Executes the specified [Runnable] in a database transaction. The transaction will be marked
723 * as successful unless an exception is thrown in the [Runnable].
724 *
725 * Room will only perform at most one transaction at a time.
726 *
727 * If a [SQLiteDriver] is configured with this database, then it is best to use
728 * [useWriterConnection] along with [immediateTransaction] to perform transactional operations.
729 *
730 * @param body The piece of code to execute.
731 */
732 open fun runInTransaction(body: Runnable) {
733 runInTransaction { body.run() }
734 }
735
736 /**
737 * Executes the specified [Callable] in a database transaction. The transaction will be marked
738 * as successful unless an exception is thrown in the [Callable].
739 *
740 * Room will only perform at most one transaction at a time.
741 *
742 * If a [SQLiteDriver] is configured with this database, then it is best to use
743 * [useWriterConnection] along with [immediateTransaction] to perform transactional operations.
744 *
745 * @param body The piece of code to execute.
746 * @param V The type of the return value.
747 * @return The value returned from the [Callable].
748 */
749 open fun <V> runInTransaction(body: Callable<V>): V {
750 return runInTransaction { body.call() }
751 }
752
753 @Suppress("DEPRECATION") // Usage of try-finally transaction idiom APIs
754 private fun <T> runInTransaction(body: () -> T): T {
755 if (inCompatibilityMode()) {
756 beginTransaction()
757 try {
758 val result = body.invoke()
759 setTransactionSuccessful()
760 return result
761 } finally {
762 endTransaction()
763 }
764 } else {
765 return performBlocking(db = this, isReadOnly = false, inTransaction = true) {
766 body.invoke()
767 }
768 }
769 }
770
771 /**
772 * Initialize invalidation tracker. Note that this method is called when the [RoomDatabase] is
773 * initialized and opens a database connection.
774 *
775 * @param db The database instance.
776 */
777 @Deprecated("No longer called by generated")
778 protected open fun internalInitInvalidationTracker(db: SupportSQLiteDatabase) {
779 internalInitInvalidationTracker(SupportSQLiteConnection(db))
780 }
781
782 /**
783 * Initialize invalidation tracker. Note that this method is called when the [RoomDatabase] is
784 * initialized and opens a database connection.
785 *
786 * @param connection The database connection.
787 */
788 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) // used in generated code
789 protected actual fun internalInitInvalidationTracker(connection: SQLiteConnection) {
790 invalidationTracker.internalInit(connection)
791 }
792
793 /**
794 * Wrapper for [SupportSQLiteDatabase.inTransaction]. Returns true if current thread is in a
795 * transaction.
796 *
797 * @return True if there is an active transaction in current thread, false otherwise.
798 * @throws IllegalStateException If a [SQLiteDriver] is configured with this database.
799 * @see SupportSQLiteDatabase.inTransaction
800 */
801 open fun inTransaction(): Boolean {
802 return isOpenInternal && openHelper.writableDatabase.inTransaction()
803 }
804
805 /**
806 * Journal modes for SQLite database.
807 *
808 * @see Builder.setJournalMode
809 */
810 actual enum class JournalMode {
811 /**
812 * Let Room choose the journal mode. This is the default value when no explicit value is
813 * specified.
814 *
815 * The actual value will be [TRUNCATE] when the device runs API Level lower than 16 or it is
816 * a low-RAM device. Otherwise, [WRITE_AHEAD_LOGGING] will be used.
817 */
818 AUTOMATIC,
819
820 /** Truncate journal mode. */
821 TRUNCATE,
822
823 /** Write-Ahead Logging mode. */
824 WRITE_AHEAD_LOGGING;
825
826 /** Resolves [AUTOMATIC] to either [TRUNCATE] or [WRITE_AHEAD_LOGGING]. */
827 internal fun resolve(context: Context): JournalMode {
828 if (this != AUTOMATIC) {
829 return this
830 }
831 val manager = context.getSystemService(Context.ACTIVITY_SERVICE) as? ActivityManager
832 if (manager != null && !manager.isLowRamDevice) {
833 return WRITE_AHEAD_LOGGING
834 }
835 return TRUNCATE
836 }
837 }
838
839 /**
840 * Builder for [RoomDatabase].
841 *
842 * @param T The type of the abstract database class.
843 */
844 @Suppress("GetterOnBuilder") // To keep ABI compatibility from Java
845 actual open class Builder<T : RoomDatabase> {
846 private val klass: KClass<T>
847 private val context: Context
848 private val name: String?
849 private val factory: (() -> T)?
850
851 /**
852 * Constructor for [RoomDatabase.Builder].
853 *
854 * @param klass The abstract database class.
855 * @param name The name of the database or NULL for an in-memory database.
856 * @param factory The lambda calling `initializeImpl()` on the abstract database class which
857 * returns the generated database implementation.
858 * @param context The context for the database, this is usually the Application context.
859 */
860 @PublishedApi
861 internal constructor(
862 klass: KClass<T>,
863 name: String?,
864 factory: (() -> T)?,
865 context: Context
866 ) {
867 this.klass = klass
868 this.context = context
869 this.name = name
870 this.factory = factory
871 }
872
873 /**
874 * Constructor for [RoomDatabase.Builder].
875 *
876 * @param context The context for the database, this is usually the Application context.
877 * @param klass The abstract database class.
878 * @param name The name of the database or NULL for an in-memory database.
879 */
880 internal constructor(context: Context, klass: Class<T>, name: String?) {
881 this.klass = klass.kotlin
882 this.context = context
883 this.name = name
884 this.factory = null
885 }
886
887 private val callbacks: MutableList<Callback> = mutableListOf()
888 private var prepackagedDatabaseCallback: PrepackagedDatabaseCallback? = null
889 private var queryCallback: QueryCallback? = null
890 private var queryCallbackExecutor: Executor? = null
891 private var queryCallbackCoroutineContext: CoroutineContext? = null
892 private val typeConverters: MutableList<Any> = mutableListOf()
893 private var queryExecutor: Executor? = null
894 private var transactionExecutor: Executor? = null
895
896 private var supportOpenHelperFactory: SupportSQLiteOpenHelper.Factory? = null
897 private var allowMainThreadQueries = false
898 private var journalMode: JournalMode = JournalMode.AUTOMATIC
899 private var multiInstanceInvalidationIntent: Intent? = null
900
901 private var autoCloseTimeout = -1L
902 private var autoCloseTimeUnit: TimeUnit? = null
903
904 /** Migrations, mapped by from-to pairs. */
905 private val migrationContainer: MigrationContainer = MigrationContainer()
906
907 /**
908 * Versions that don't require migrations, configured via
909 * [fallbackToDestructiveMigrationFrom].
910 */
911 private var migrationsNotRequiredFrom: MutableSet<Int> = mutableSetOf()
912
913 /**
914 * Keeps track of [Migration.startVersion]s and [Migration.endVersion]s added in
915 * [addMigrations] for later validation that makes those versions don't match any versions
916 * passed to [fallbackToDestructiveMigrationFrom].
917 */
918 private val migrationStartAndEndVersions = mutableSetOf<Int>()
919
920 private val autoMigrationSpecs: MutableList<AutoMigrationSpec> = mutableListOf()
921
922 private var requireMigration: Boolean = true
923 private var allowDestructiveMigrationOnDowngrade = false
924 private var allowDestructiveMigrationForAllTables = false
925
926 private var copyFromAssetPath: String? = null
927 private var copyFromFile: File? = null
928 private var copyFromInputStream: Callable<InputStream>? = null
929
930 private var driver: SQLiteDriver? = null
931 private var queryCoroutineContext: CoroutineContext? = null
932
933 private var inMemoryTrackingTableMode = true
934
935 /**
936 * Configures Room to create and open the database using a pre-packaged database located in
937 * the application 'assets/' folder.
938 *
939 * Room does not open the pre-packaged database, instead it copies it into the internal app
940 * database folder and then opens it. The pre-packaged database file must be located in the
941 * "assets/" folder of your application. For example, the path for a file located in
942 * "assets/databases/products.db" would be "databases/products.db".
943 *
944 * The pre-packaged database schema will be validated. It might be best to create your
945 * pre-packaged database schema utilizing the exported schema files generated when
946 * [Database.exportSchema] is enabled.
947 *
948 * This method is not supported for an in memory database [Builder].
949 *
950 * @param databaseFilePath The file path within the 'assets/' directory of where the
951 * database file is located.
952 * @return This builder instance.
953 */
954 open fun createFromAsset(databaseFilePath: String) = apply {
955 this.copyFromAssetPath = databaseFilePath
956 }
957
958 /**
959 * Configures Room to create and open the database using a pre-packaged database located in
960 * the application 'assets/' folder.
961 *
962 * Room does not open the pre-packaged database, instead it copies it into the internal app
963 * database folder and then opens it. The pre-packaged database file must be located in the
964 * "assets/" folder of your application. For example, the path for a file located in
965 * "assets/databases/products.db" would be "databases/products.db".
966 *
967 * The pre-packaged database schema will be validated. It might be best to create your
968 * pre-packaged database schema utilizing the exported schema files generated when
969 * [Database.exportSchema] is enabled.
970 *
971 * This method is not supported for an in memory database [Builder].
972 *
973 * @param databaseFilePath The file path within the 'assets/' directory of where the
974 * database file is located.
975 * @param callback The pre-packaged callback.
976 * @return This builder instance.
977 */
978 @SuppressLint("BuilderSetStyle") // To keep naming consistency.
979 open fun createFromAsset(databaseFilePath: String, callback: PrepackagedDatabaseCallback) =
980 apply {
981 this.prepackagedDatabaseCallback = callback
982 this.copyFromAssetPath = databaseFilePath
983 }
984
985 /**
986 * Configures Room to create and open the database using a pre-packaged database file.
987 *
988 * Room does not open the pre-packaged database, instead it copies it into the internal app
989 * database folder and then opens it. The given file must be accessible and the right
990 * permissions must be granted for Room to copy the file.
991 *
992 * The pre-packaged database schema will be validated. It might be best to create your
993 * pre-packaged database schema utilizing the exported schema files generated when
994 * [Database.exportSchema] is enabled.
995 *
996 * The [Callback.onOpen] method can be used as an indicator that the pre-packaged database
997 * was successfully opened by Room and can be cleaned up.
998 *
999 * This method is not supported for an in memory database [Builder].
1000 *
1001 * @param databaseFile The database file.
1002 * @return This builder instance.
1003 */
1004 open fun createFromFile(databaseFile: File) = apply { this.copyFromFile = databaseFile }
1005
1006 /**
1007 * Configures Room to create and open the database using a pre-packaged database file.
1008 *
1009 * Room does not open the pre-packaged database, instead it copies it into the internal app
1010 * database folder and then opens it. The given file must be accessible and the right
1011 * permissions must be granted for Room to copy the file.
1012 *
1013 * The pre-packaged database schema will be validated. It might be best to create your
1014 * pre-packaged database schema utilizing the exported schema files generated when
1015 * [Database.exportSchema] is enabled.
1016 *
1017 * The [Callback.onOpen] method can be used as an indicator that the pre-packaged database
1018 * was successfully opened by Room and can be cleaned up.
1019 *
1020 * This method is not supported for an in memory database [Builder].
1021 *
1022 * @param databaseFile The database file.
1023 * @param callback The pre-packaged callback.
1024 * @return This builder instance.
1025 */
1026 @SuppressLint("BuilderSetStyle", "StreamFiles") // To keep naming consistency.
1027 open fun createFromFile(databaseFile: File, callback: PrepackagedDatabaseCallback) = apply {
1028 this.prepackagedDatabaseCallback = callback
1029 this.copyFromFile = databaseFile
1030 }
1031
1032 /**
1033 * Configures Room to create and open the database using a pre-packaged database via an
1034 * [InputStream].
1035 *
1036 * This is useful for processing compressed database files. Room does not open the
1037 * pre-packaged database, instead it copies it into the internal app database folder, and
1038 * then open it. The [InputStream] will be closed once Room is done consuming it.
1039 *
1040 * The pre-packaged database schema will be validated. It might be best to create your
1041 * pre-packaged database schema utilizing the exported schema files generated when
1042 * [Database.exportSchema] is enabled.
1043 *
1044 * The [Callback.onOpen] method can be used as an indicator that the pre-packaged database
1045 * was successfully opened by Room and can be cleaned up.
1046 *
1047 * This method is not supported for an in memory database [Builder].
1048 *
1049 * @param inputStreamCallable A callable that returns an InputStream from which to copy the
1050 * database. The callable will be invoked in a thread from the Executor set via
1051 * [setQueryExecutor]. The callable is only invoked if Room needs to create and open the
1052 * database from the pre-package database, usually the first time it is created or during
1053 * a destructive migration.
1054 * @return This builder instance.
1055 */
1056 @SuppressLint("BuilderSetStyle") // To keep naming consistency.
1057 open fun createFromInputStream(inputStreamCallable: Callable<InputStream>) = apply {
1058 this.copyFromInputStream = inputStreamCallable
1059 }
1060
1061 /**
1062 * Configures Room to create and open the database using a pre-packaged database via an
1063 * [InputStream].
1064 *
1065 * This is useful for processing compressed database files. Room does not open the
1066 * pre-packaged database, instead it copies it into the internal app database folder, and
1067 * then open it. The [InputStream] will be closed once Room is done consuming it.
1068 *
1069 * The pre-packaged database schema will be validated. It might be best to create your
1070 * pre-packaged database schema utilizing the exported schema files generated when
1071 * [Database.exportSchema] is enabled.
1072 *
1073 * The [Callback.onOpen] method can be used as an indicator that the pre-packaged database
1074 * was successfully opened by Room and can be cleaned up.
1075 *
1076 * This method is not supported for an in memory database [Builder].
1077 *
1078 * @param inputStreamCallable A callable that returns an InputStream from which to copy the
1079 * database. The callable will be invoked in a thread from the Executor set via
1080 * [setQueryExecutor]. The callable is only invoked if Room needs to create and open the
1081 * database from the pre-package database, usually the first time it is created or during
1082 * a destructive migration.
1083 * @param callback The pre-packaged callback.
1084 * @return This builder instance.
1085 */
1086 @SuppressLint("BuilderSetStyle", "LambdaLast") // To keep naming consistency.
1087 open fun createFromInputStream(
1088 inputStreamCallable: Callable<InputStream>,
1089 callback: PrepackagedDatabaseCallback
1090 ) = apply {
1091 this.prepackagedDatabaseCallback = callback
1092 this.copyFromInputStream = inputStreamCallable
1093 }
1094
1095 /**
1096 * Sets the database factory. If not set, it defaults to [FrameworkSQLiteOpenHelperFactory].
1097 *
1098 * @param factory The factory to use to access the database.
1099 * @return This builder instance.
1100 */
1101 open fun openHelperFactory(factory: SupportSQLiteOpenHelper.Factory?) = apply {
1102 this.supportOpenHelperFactory = factory
1103 }
1104
1105 /**
1106 * Adds a migration to the builder.
1107 *
1108 * Each [Migration] has a start and end versions and Room runs these migrations to bring the
1109 * database to the latest version.
1110 *
1111 * A migration can handle more than 1 version (e.g. if you have a faster path to choose when
1112 * going from version 3 to 5 without going to version 4). If Room opens a database at
1113 * version 3 and latest version is >= 5, Room will use the migration object that can migrate
1114 * from 3 to 5 instead of 3 to 4 and 4 to 5.
1115 *
1116 * @param migrations The migration objects that modify the database schema with the
1117 * necessary changes for a version change.
1118 * @return This builder instance.
1119 */
1120 actual open fun addMigrations(vararg migrations: Migration) = apply {
1121 for (migration in migrations) {
1122 migrationStartAndEndVersions.add(migration.startVersion)
1123 migrationStartAndEndVersions.add(migration.endVersion)
1124 }
1125 migrationContainer.addMigrations(*migrations)
1126 }
1127
1128 /**
1129 * Adds an auto migration spec instance to the builder.
1130 *
1131 * @param autoMigrationSpec The auto migration object that is annotated with
1132 * [ProvidedAutoMigrationSpec] and is declared in an [AutoMigration] annotation.
1133 * @return This builder instance.
1134 */
1135 @Suppress("MissingGetterMatchingBuilder")
1136 actual open fun addAutoMigrationSpec(autoMigrationSpec: AutoMigrationSpec) = apply {
1137 this.autoMigrationSpecs.add(autoMigrationSpec)
1138 }
1139
1140 /**
1141 * Disables the main thread query check for Room.
1142 *
1143 * Room ensures that Database is never accessed on the main thread because it may lock the
1144 * main thread and trigger an ANR. If you need to access the database from the main thread,
1145 * you should always use async alternatives or manually move the call to a background
1146 * thread.
1147 *
1148 * You may want to turn this check off for testing.
1149 *
1150 * @return This builder instance.
1151 */
1152 open fun allowMainThreadQueries() = apply { this.allowMainThreadQueries = true }
1153
1154 /**
1155 * Sets the journal mode for this database.
1156 *
1157 * The value is ignored if the builder is for an 'in-memory database'. The journal mode
1158 * should be consistent across multiple instances of [RoomDatabase] for a single SQLite
1159 * database file.
1160 *
1161 * The default value is [JournalMode.AUTOMATIC].
1162 *
1163 * @param journalMode The journal mode.
1164 * @return This builder instance.
1165 */
1166 actual open fun setJournalMode(journalMode: JournalMode) = apply {
1167 this.journalMode = journalMode
1168 }
1169
1170 /**
1171 * Sets the [Executor] that will be used to execute all non-blocking asynchronous queries
1172 * and tasks, including `LiveData` invalidation, `Flowable` scheduling and
1173 * `ListenableFuture` tasks.
1174 *
1175 * When both the query executor and transaction executor are unset, then a default
1176 * `Executor` will be used. The default `Executor` allocates and shares threads amongst
1177 * Architecture Components libraries. If the query executor is unset but a transaction
1178 * executor was set via [setTransactionExecutor], then the same `Executor` will be used for
1179 * queries.
1180 *
1181 * For best performance the given `Executor` should be bounded (max number of threads is
1182 * limited).
1183 *
1184 * The input `Executor` cannot run tasks on the UI thread.
1185 *
1186 * If either [setQueryCoroutineContext] has been called, then this function will throw an
1187 * [IllegalArgumentException].
1188 *
1189 * @return This builder instance.
1190 * @throws IllegalArgumentException if this builder was already configured with a
1191 * [CoroutineContext].
1192 */
1193 open fun setQueryExecutor(executor: Executor) = apply {
1194 require(queryCoroutineContext == null) {
1195 "This builder has already been configured with a CoroutineContext. A RoomDatabase" +
1196 "can only be configured with either an Executor or a CoroutineContext."
1197 }
1198 this.queryExecutor = executor
1199 }
1200
1201 /**
1202 * Sets the [Executor] that will be used to execute all non-blocking asynchronous
1203 * transaction queries and tasks, including `LiveData` invalidation, `Flowable` scheduling
1204 * and `ListenableFuture` tasks.
1205 *
1206 * When both the transaction executor and query executor are unset, then a default
1207 * `Executor` will be used. The default `Executor` allocates and shares threads amongst
1208 * Architecture Components libraries. If the transaction executor is unset but a query
1209 * executor was set using [setQueryExecutor], then the same `Executor` will be used for
1210 * transactions.
1211 *
1212 * If the given `Executor` is shared then it should be unbounded to avoid the possibility of
1213 * a deadlock. Room will not use more than one thread at a time from this executor since
1214 * only one transaction at a time can be executed, other transactions will be queued on a
1215 * first come, first serve order.
1216 *
1217 * The input `Executor` cannot run tasks on the UI thread.
1218 *
1219 * If either [setQueryCoroutineContext] has been called, then this function will throw an
1220 * [IllegalArgumentException].
1221 *
1222 * @return This builder instance.
1223 * @throws IllegalArgumentException if this builder was already configured with a
1224 * [CoroutineContext].
1225 */
1226 open fun setTransactionExecutor(executor: Executor) = apply {
1227 require(queryCoroutineContext == null) {
1228 "This builder has already been configured with a CoroutineContext. A RoomDatabase" +
1229 "can only be configured with either an Executor or a CoroutineContext."
1230 }
1231 this.transactionExecutor = executor
1232 }
1233
1234 /**
1235 * Sets whether table invalidation in this instance of [RoomDatabase] should be broadcast
1236 * and synchronized with other instances of the same [RoomDatabase], including those in a
1237 * separate process. In order to enable multi-instance invalidation, this has to be turned
1238 * on both ends.
1239 *
1240 * This is not enabled by default.
1241 *
1242 * This does not work for in-memory databases. This does not work between database instances
1243 * targeting different database files.
1244 *
1245 * @return This builder instance.
1246 */
1247 @OptIn(ExperimentalRoomApi::class)
1248 @Suppress("UnsafeOptInUsageError")
1249 open fun enableMultiInstanceInvalidation() = apply {
1250 this.multiInstanceInvalidationIntent =
1251 if (name != null) {
1252 Intent(context, MultiInstanceInvalidationService::class.java)
1253 } else {
1254 null
1255 }
1256 }
1257
1258 /**
1259 * Sets whether table invalidation in this instance of [RoomDatabase] should be broadcast
1260 * and synchronized with other instances of the same [RoomDatabase], including those in a
1261 * separate process. In order to enable multi-instance invalidation, this has to be turned
1262 * on both ends and need to point to the same [MultiInstanceInvalidationService].
1263 *
1264 * This is not enabled by default.
1265 *
1266 * This does not work for in-memory databases. This does not work between database instances
1267 * targeting different database files.
1268 *
1269 * @param invalidationServiceIntent Intent to bind to the
1270 * [MultiInstanceInvalidationService].
1271 * @return This builder instance.
1272 */
1273 @ExperimentalRoomApi
1274 @Suppress("MissingGetterMatchingBuilder")
1275 open fun setMultiInstanceInvalidationServiceIntent(invalidationServiceIntent: Intent) =
1276 apply {
1277 this.multiInstanceInvalidationIntent =
1278 if (name != null) invalidationServiceIntent else null
1279 }
1280
1281 /**
1282 * Allows Room to destructively recreate database tables if [Migration]s that would migrate
1283 * old database schemas to the latest schema version are not found.
1284 *
1285 * When the database version on the device does not match the latest schema version, Room
1286 * runs necessary [Migration]s on the database.
1287 *
1288 * If it cannot find the set of [Migration]s that will bring the database to the current
1289 * version, it will throw an [IllegalStateException].
1290 *
1291 * You can call this method to change this behavior to re-create the database tables instead
1292 * of crashing.
1293 *
1294 * If the database was create from an asset or a file then Room will try to use the same
1295 * file to re-create the database, otherwise this will delete all of the data in the
1296 * database tables managed by Room.
1297 *
1298 * To let Room fallback to destructive migration only during a schema downgrade then use
1299 * [fallbackToDestructiveMigrationOnDowngrade].
1300 *
1301 * @return This builder instance.
1302 */
1303 @Deprecated(
1304 message =
1305 "Replace by overloaded version with parameter to indicate if all tables " +
1306 "should be dropped or not.",
1307 replaceWith = ReplaceWith("fallbackToDestructiveMigration(false)")
1308 )
1309 @Suppress("BuilderSetStyle") // Overload of exsisting API
1310 open fun fallbackToDestructiveMigration() = apply {
1311 this.requireMigration = false
1312 this.allowDestructiveMigrationOnDowngrade = true
1313 }
1314
1315 /**
1316 * Allows Room to destructively recreate database tables if [Migration]s that would migrate
1317 * old database schemas to the latest schema version are not found.
1318 *
1319 * When the database version on the device does not match the latest schema version, Room
1320 * runs necessary [Migration]s on the database. If it cannot find the set of [Migration]s
1321 * that will bring the database to the current version, it will throw an
1322 * [IllegalStateException]. You can call this method to change this behavior to re-create
1323 * the database tables instead of crashing.
1324 *
1325 * If the database was create from an asset or a file then Room will try to use the same
1326 * file to re-create the database, otherwise this will delete all of the data in the
1327 * database tables managed by Room.
1328 *
1329 * To let Room fallback to destructive migration only during a schema downgrade then use
1330 * [fallbackToDestructiveMigrationOnDowngrade].
1331 *
1332 * @param dropAllTables Set to `true` if all tables should be dropped during destructive
1333 * migration including those not managed by Room. Recommended value is `true` as otherwise
1334 * Room could leave obsolete data when table names or existence changes between versions.
1335 * @return This builder instance.
1336 */
1337 @Suppress("BuilderSetStyle") // Overload of existing API
1338 actual fun fallbackToDestructiveMigration(dropAllTables: Boolean) = apply {
1339 this.requireMigration = false
1340 this.allowDestructiveMigrationOnDowngrade = true
1341 this.allowDestructiveMigrationForAllTables = dropAllTables
1342 }
1343
1344 /**
1345 * Allows Room to destructively recreate database tables if [Migration]s are not available
1346 * when downgrading to old schema versions.
1347 *
1348 * For details, see [Builder.fallbackToDestructiveMigration].
1349 *
1350 * @return This builder instance.
1351 */
1352 @Deprecated(
1353 message =
1354 "Replace by overloaded version with parameter to indicate if all tables " +
1355 "should be dropped or not.",
1356 replaceWith = ReplaceWith("fallbackToDestructiveMigrationOnDowngrade(false)")
1357 )
1358 open fun fallbackToDestructiveMigrationOnDowngrade() = apply {
1359 this.requireMigration = true
1360 this.allowDestructiveMigrationOnDowngrade = true
1361 }
1362
1363 /**
1364 * Allows Room to destructively recreate database tables if [Migration]s are not available
1365 * when downgrading to old schema versions.
1366 *
1367 * For details, see [Builder.fallbackToDestructiveMigration].
1368 *
1369 * @param dropAllTables Set to `true` if all tables should be dropped during destructive
1370 * migration including those not managed by Room. Recommended value is `true` as otherwise
1371 * Room could leave obsolete data when table names or existence changes between versions.
1372 * @return This builder instance.
1373 */
1374 @Suppress("BuilderSetStyle") // Overload of existing API
1375 actual fun fallbackToDestructiveMigrationOnDowngrade(dropAllTables: Boolean) = apply {
1376 this.requireMigration = true
1377 this.allowDestructiveMigrationOnDowngrade = true
1378 this.allowDestructiveMigrationForAllTables = dropAllTables
1379 }
1380
1381 /**
1382 * Informs Room that it is allowed to destructively recreate database tables from specific
1383 * starting schema versions.
1384 *
1385 * This functionality is the same as that provided by [fallbackToDestructiveMigration],
1386 * except that this method allows the specification of a set of schema versions for which
1387 * destructive recreation is allowed.
1388 *
1389 * Using this method is preferable to [fallbackToDestructiveMigration] if you want to allow
1390 * destructive migrations from some schema versions while still taking advantage of
1391 * exceptions being thrown due to unintentionally missing migrations.
1392 *
1393 * Note: No versions passed to this method may also exist as either starting or ending
1394 * versions in the [Migration]s provided to [addMigrations]. If a version passed to this
1395 * method is found as a starting or ending version in a Migration, an exception will be
1396 * thrown.
1397 *
1398 * @param startVersions The set of schema versions from which Room should use a destructive
1399 * migration.
1400 * @return This builder instance.
1401 */
1402 @Deprecated(
1403 message =
1404 "Replace by overloaded version with parameter to indicate if all tables " +
1405 "should be dropped or not.",
1406 replaceWith = ReplaceWith("fallbackToDestructiveMigrationFrom(false, startVersions)")
1407 )
1408 open fun fallbackToDestructiveMigrationFrom(vararg startVersions: Int) = apply {
1409 for (startVersion in startVersions) {
1410 this.migrationsNotRequiredFrom.add(startVersion)
1411 }
1412 }
1413
1414 /**
1415 * Informs Room that it is allowed to destructively recreate database tables from specific
1416 * starting schema versions.
1417 *
1418 * This functionality is the same [fallbackToDestructiveMigration], except that this method
1419 * allows the specification of a set of schema versions for which destructive recreation is
1420 * allowed.
1421 *
1422 * Using this method is preferable to [fallbackToDestructiveMigration] if you want to allow
1423 * destructive migrations from some schema versions while still taking advantage of
1424 * exceptions being thrown due to unintentionally missing migrations.
1425 *
1426 * Note: No versions passed to this method may also exist as either starting or ending
1427 * versions in the [Migration]s provided via [addMigrations]. If a version passed to this
1428 * method is found as a starting or ending version in a Migration, an exception will be
1429 * thrown.
1430 *
1431 * @param dropAllTables Set to `true` if all tables should be dropped during destructive
1432 * migration including those not managed by Room.
1433 * @param startVersions The set of schema versions from which Room should use a destructive
1434 * migration.
1435 * @return This builder instance.
1436 */
1437 @Suppress(
1438 "BuilderSetStyle", // Overload of existing API
1439 "MissingJvmstatic", // No need for @JvmOverloads due to an overload already existing
1440 )
1441 actual open fun fallbackToDestructiveMigrationFrom(
1442 @Suppress("KotlinDefaultParameterOrder") // There is a vararg that must be last
1443 dropAllTables: Boolean,
1444 vararg startVersions: Int
1445 ) = apply {
1446 for (startVersion in startVersions) {
1447 this.migrationsNotRequiredFrom.add(startVersion)
1448 }
1449 this.allowDestructiveMigrationForAllTables = dropAllTables
1450 }
1451
1452 /**
1453 * Adds a [Callback] to this database.
1454 *
1455 * @param callback The callback.
1456 * @return This builder instance.
1457 */
1458 actual open fun addCallback(callback: Callback) = apply { this.callbacks.add(callback) }
1459
1460 /**
1461 * Sets a [QueryCallback] to be invoked when queries are executed.
1462 *
1463 * The callback is invoked whenever a query is executed, note that adding this callback has
1464 * a small cost and should be avoided in production builds unless needed.
1465 *
1466 * A use case for providing a callback is to allow logging executed queries. When the
1467 * callback implementation logs then it is recommended to use an immediate executor.
1468 *
1469 * If a previous callback was set with [setQueryCallback] then this call will override it,
1470 * including removing the Coroutine context previously set, if any.
1471 *
1472 * @param queryCallback The query callback.
1473 * @param executor The executor on which the query callback will be invoked.
1474 * @return This builder instance.
1475 */
1476 @Suppress("MissingGetterMatchingBuilder")
1477 open fun setQueryCallback(queryCallback: QueryCallback, executor: Executor) = apply {
1478 this.queryCallback = queryCallback
1479 this.queryCallbackExecutor = executor
1480 this.queryCallbackCoroutineContext = null
1481 }
1482
1483 /**
1484 * Sets a [QueryCallback] to be invoked when queries are executed.
1485 *
1486 * The callback is invoked whenever a query is executed, note that adding this callback has
1487 * a small cost and should be avoided in production builds unless needed.
1488 *
1489 * A use case for providing a callback is to allow logging executed queries. When the
1490 * callback implementation simply logs then it is recommended to use
1491 * [kotlinx.coroutines.Dispatchers.Unconfined].
1492 *
1493 * If a previous callback was set with [setQueryCallback] then this call will override it,
1494 * including removing the executor previously set, if any.
1495 *
1496 * @param context The coroutine context on which the query callback will be invoked.
1497 * @param queryCallback The query callback.
1498 * @return This builder instance.
1499 */
1500 @Suppress("MissingGetterMatchingBuilder")
1501 fun setQueryCallback(context: CoroutineContext, queryCallback: QueryCallback) = apply {
1502 this.queryCallback = queryCallback
1503 this.queryCallbackExecutor = null
1504 this.queryCallbackCoroutineContext = context
1505 }
1506
1507 /**
1508 * Adds a type converter instance to the builder.
1509 *
1510 * @param typeConverter The converter instance that is annotated with
1511 * [ProvidedTypeConverter].
1512 * @return This builder instance.
1513 */
1514 actual open fun addTypeConverter(typeConverter: Any) = apply {
1515 this.typeConverters.add(typeConverter)
1516 }
1517
1518 /**
1519 * Enables auto-closing for the database to free up unused resources. The underlying
1520 * database will be closed after it's last use after the specified [autoCloseTimeout] has
1521 * elapsed since its last usage. The database will be automatically re-opened the next time
1522 * it is accessed.
1523 *
1524 * Auto-closing is not compatible with in-memory databases since the data will be lost when
1525 * the database is auto-closed.
1526 *
1527 * Also, temp tables and temp triggers will be cleared each time the database is
1528 * auto-closed. If you need to use them, please include them in your callback
1529 * [RoomDatabase.Callback.onOpen].
1530 *
1531 * All configuration should happen in your [RoomDatabase.Callback.onOpen] callback so it is
1532 * re-applied every time the database is re-opened. Note that the
1533 * [RoomDatabase.Callback.onOpen] will be called every time the database is re-opened.
1534 *
1535 * The auto-closing database operation runs on the query executor.
1536 *
1537 * The database will not be re-opened if the RoomDatabase or the SupportSqliteOpenHelper is
1538 * closed manually (by calling [RoomDatabase.close] or [SupportSQLiteOpenHelper.close]. If
1539 * the database is closed manually, you must create a new database using
1540 * [RoomDatabase.Builder.build].
1541 *
1542 * @param autoCloseTimeout the amount of time after the last usage before closing the
1543 * database. Must greater or equal to zero.
1544 * @param autoCloseTimeUnit the timeunit for autoCloseTimeout.
1545 * @return This builder instance.
1546 */
1547 @ExperimentalRoomApi // When experimental is removed, add these parameters to
1548 // DatabaseConfiguration
1549 @Suppress("MissingGetterMatchingBuilder")
1550 open fun setAutoCloseTimeout(
1551 @IntRange(from = 0) autoCloseTimeout: Long,
1552 autoCloseTimeUnit: TimeUnit
1553 ) = apply {
1554 require(autoCloseTimeout >= 0) { "autoCloseTimeout must be >= 0" }
1555 this.autoCloseTimeout = autoCloseTimeout
1556 this.autoCloseTimeUnit = autoCloseTimeUnit
1557 }
1558
1559 /**
1560 * Sets the [SQLiteDriver] implementation to be used by Room to open database connections.
1561 * For example, an instance of [androidx.sqlite.driver.AndroidSQLiteDriver] or
1562 * [androidx.sqlite.driver.bundled.BundledSQLiteDriver].
1563 *
1564 * Once a driver is configured using this function, various callbacks that receive a
1565 * [SupportSQLiteDatabase] will not be invoked, such as [RoomDatabase.Callback.onCreate].
1566 * Moreover, APIs that use SupportSQLite will also throw an exception, such as
1567 * [RoomDatabase.openHelper].
1568 *
1569 * See the documentation on
1570 * [Migrating to SQLite Driver](https://d.android.com/training/data-storage/room/room-kmp-migration#migrate_from_support_sqlite_to_sqlite_driver)
1571 * for more information.
1572 *
1573 * @param driver The driver
1574 * @return This builder instance.
1575 */
1576 @Suppress("MissingGetterMatchingBuilder")
1577 actual fun setDriver(driver: SQLiteDriver) = apply { this.driver = driver }
1578
1579 /**
1580 * Sets the [CoroutineContext] that will be used to execute all asynchronous queries and
1581 * tasks, such as `Flow` emissions and [InvalidationTracker] notifications.
1582 *
1583 * If no [CoroutineDispatcher] is present in the [context] then this function will throw an
1584 * [IllegalArgumentException]
1585 *
1586 * If no context is provided, then Room wil default to using the [Executor] set via
1587 * [setQueryExecutor] as the context via the conversion function [asCoroutineDispatcher].
1588 *
1589 * If either [setQueryExecutor] or [setTransactionExecutor] has been called, then this
1590 * function will throw an [IllegalArgumentException].
1591 *
1592 * @param context The context
1593 * @return This [Builder] instance
1594 * @throws IllegalArgumentException if no [CoroutineDispatcher] is found in the given
1595 * [context] or if this builder was already configured with an [Executor].
1596 */
1597 @Suppress("MissingGetterMatchingBuilder")
1598 actual fun setQueryCoroutineContext(context: CoroutineContext) = apply {
1599 require(queryExecutor == null && transactionExecutor == null) {
1600 "This builder has already been configured with an Executor. A RoomDatabase can" +
1601 "only be configured with either an Executor or a CoroutineContext."
1602 }
1603 require(context[ContinuationInterceptor] != null) {
1604 "It is required that the coroutine context contain a dispatcher."
1605 }
1606 this.queryCoroutineContext = context
1607 }
1608
1609 /**
1610 * Sets whether Room will use an in-memory table or a persisted table to track invalidation.
1611 *
1612 * An in-memory table is used by default. Using an in-memory tables is more performant,
1613 * reduces the journal file size but has an increased memory footprint, where as using a
1614 * real table has the opposite effect.
1615 *
1616 * @param inMemory True if in-memory tables should be used, false otherwise.
1617 * @return This [Builder] instance
1618 */
1619 @ExperimentalRoomApi
1620 @Suppress("MissingGetterMatchingBuilder")
1621 fun setInMemoryTrackingMode(inMemory: Boolean) = apply {
1622 this.inMemoryTrackingTableMode = inMemory
1623 }
1624
1625 /**
1626 * Creates the databases and initializes it.
1627 *
1628 * By default, all RoomDatabases use in memory storage for TEMP tables and enables recursive
1629 * triggers.
1630 *
1631 * @return A new database instance.
1632 * @throws IllegalArgumentException if the builder was misconfigured.
1633 */
1634 actual open fun build(): T {
1635 if (queryExecutor == null && transactionExecutor == null) {
1636 transactionExecutor = ArchTaskExecutor.getIOThreadExecutor()
1637 queryExecutor = transactionExecutor
1638 } else if (queryExecutor != null && transactionExecutor == null) {
1639 transactionExecutor = queryExecutor
1640 } else if (queryExecutor == null) {
1641 queryExecutor = transactionExecutor
1642 }
1643
1644 validateMigrationsNotRequired(migrationStartAndEndVersions, migrationsNotRequiredFrom)
1645
1646 val initialFactory: SupportSQLiteOpenHelper.Factory? =
1647 if (driver == null && supportOpenHelperFactory == null) {
1648 // No driver and no factory, compatibility mode, create the default factory
1649 FrameworkSQLiteOpenHelperFactory()
1650 } else if (driver == null) {
1651 // No driver but a factory was provided, use it in compatibility mode
1652 supportOpenHelperFactory
1653 } else if (supportOpenHelperFactory == null) {
1654 // A driver was provided, no need to create the default factory
1655 null
1656 } else {
1657 // Both driver and factory provided, invalid configuration.
1658 throw IllegalArgumentException(
1659 "A RoomDatabase cannot be configured with both a SQLiteDriver and a " +
1660 "SupportOpenHelper.Factory."
1661 )
1662 }
1663 val autoCloseEnabled = autoCloseTimeout > 0
1664 val prePackagedCopyEnabled =
1665 copyFromAssetPath != null || copyFromFile != null || copyFromInputStream != null
1666 val queryCallbackEnabled = queryCallback != null
1667 val supportOpenHelperFactory =
1668 initialFactory
1669 ?.let {
1670 if (autoCloseEnabled) {
1671 requireNotNull(name) {
1672 "Cannot create auto-closing database for an in-memory database."
1673 }
1674 val autoCloser =
1675 AutoCloser(
1676 timeoutAmount = autoCloseTimeout,
1677 timeUnit = requireNotNull(autoCloseTimeUnit)
1678 )
1679 AutoClosingRoomOpenHelperFactory(delegate = it, autoCloser = autoCloser)
1680 } else {
1681 it
1682 }
1683 }
1684 ?.let {
1685 if (prePackagedCopyEnabled) {
1686 requireNotNull(name) {
1687 "Cannot create from asset or file for an in-memory database."
1688 }
1689
1690 val copyFromAssetPathConfig = if (copyFromAssetPath == null) 0 else 1
1691 val copyFromFileConfig = if (copyFromFile == null) 0 else 1
1692 val copyFromInputStreamConfig =
1693 if (copyFromInputStream == null) 0 else 1
1694 val copyConfigurations =
1695 copyFromAssetPathConfig +
1696 copyFromFileConfig +
1697 copyFromInputStreamConfig
1698
1699 require(copyConfigurations == 1) {
1700 "More than one of createFromAsset(), " +
1701 "createFromInputStream(), and createFromFile() were called on this " +
1702 "Builder, but the database can only be created using one of the " +
1703 "three configurations."
1704 }
1705 PrePackagedCopyOpenHelperFactory(
1706 copyFromAssetPath = copyFromAssetPath,
1707 copyFromFile = copyFromFile,
1708 copyFromInputStream = copyFromInputStream,
1709 delegate = it
1710 )
1711 } else {
1712 it
1713 }
1714 }
1715 ?.let {
1716 if (queryCallbackEnabled) {
1717 val queryCallbackContext =
1718 queryCallbackExecutor?.asCoroutineDispatcher()
1719 ?: requireNotNull(queryCallbackCoroutineContext)
1720 QueryInterceptorOpenHelperFactory(
1721 delegate = it,
1722 queryCallbackScope = CoroutineScope(queryCallbackContext),
1723 queryCallback = requireNotNull(queryCallback)
1724 )
1725 } else {
1726 it
1727 }
1728 }
1729 // No open helper means a driver is to be used.
1730 if (supportOpenHelperFactory == null) {
1731 require(!autoCloseEnabled) {
1732 "Auto Closing Database is not supported when an SQLiteDriver is configured."
1733 }
1734 require(!prePackagedCopyEnabled) {
1735 "Pre-Package Database is not supported when an SQLiteDriver is configured."
1736 }
1737 require(!queryCallbackEnabled) {
1738 "Query Callback is not supported when an SQLiteDriver is configured."
1739 }
1740 }
1741 val configuration =
1742 DatabaseConfiguration(
1743 context = context,
1744 name = name,
1745 sqliteOpenHelperFactory = supportOpenHelperFactory,
1746 migrationContainer = migrationContainer,
1747 callbacks = callbacks,
1748 allowMainThreadQueries = allowMainThreadQueries,
1749 journalMode = journalMode.resolve(context),
1750 queryExecutor = requireNotNull(queryExecutor),
1751 transactionExecutor = requireNotNull(transactionExecutor),
1752 multiInstanceInvalidationServiceIntent = multiInstanceInvalidationIntent,
1753 requireMigration = requireMigration,
1754 allowDestructiveMigrationOnDowngrade = allowDestructiveMigrationOnDowngrade,
1755 migrationNotRequiredFrom = migrationsNotRequiredFrom,
1756 copyFromAssetPath = copyFromAssetPath,
1757 copyFromFile = copyFromFile,
1758 copyFromInputStream = copyFromInputStream,
1759 prepackagedDatabaseCallback = prepackagedDatabaseCallback,
1760 typeConverters = typeConverters,
1761 autoMigrationSpecs = autoMigrationSpecs,
1762 allowDestructiveMigrationForAllTables =
1763 allowDestructiveMigrationForAllTables,
1764 sqliteDriver = driver,
1765 queryCoroutineContext = queryCoroutineContext,
1766 )
1767 .apply { this.useTempTrackingTable = inMemoryTrackingTableMode }
1768 val db = factory?.invoke() ?: findAndInstantiateDatabaseImpl(klass.java)
1769 db.init(configuration)
1770 return db
1771 }
1772 }
1773
1774 /**
1775 * A container to hold migrations. It also allows querying its contents to find migrations
1776 * between two versions.
1777 */
1778 actual open class MigrationContainer {
1779 private val migrations = mutableMapOf<Int, TreeMap<Int, Migration>>()
1780
1781 /**
1782 * Adds the given migrations to the list of available migrations. If 2 migrations have the
1783 * same start-end versions, the latter migration overrides the previous one.
1784 *
1785 * @param migrations List of available migrations.
1786 */
1787 open fun addMigrations(vararg migrations: Migration) {
1788 migrations.forEach(::addMigration)
1789 }
1790
1791 /**
1792 * Adds the given migrations to the list of available migrations. If 2 migrations have the
1793 * same start-end versions, the latter migration overrides the previous one.
1794 *
1795 * @param migrations List of available migrations.
1796 */
1797 actual open fun addMigrations(migrations: List<Migration>) {
1798 migrations.forEach(::addMigration)
1799 }
1800
1801 /**
1802 * Add a [Migration] to the container. If the container already has a migration with the
1803 * same start-end versions then it will be overwritten.
1804 *
1805 * @param migration the migration to add.
1806 */
1807 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
1808 actual fun addMigration(migration: Migration) {
1809 val start = migration.startVersion
1810 val end = migration.endVersion
1811 val targetMap = migrations.getOrPut(start) { TreeMap<Int, Migration>() }
1812
1813 if (targetMap.contains(end)) {
1814 Log.w(LOG_TAG, "Overriding migration ${targetMap[end]} with $migration")
1815 }
1816 targetMap[end] = migration
1817 }
1818
1819 /**
1820 * Returns the map of available migrations where the key is the start version of the
1821 * migration, and the value is a map of (end version -> Migration).
1822 *
1823 * @return Map of migrations keyed by the start version
1824 */
1825 actual open fun getMigrations(): Map<Int, Map<Int, Migration>> {
1826 return migrations
1827 }
1828
1829 /**
1830 * Finds the list of migrations that should be run to move from `start` version to `end`
1831 * version.
1832 *
1833 * @param start The current database version
1834 * @param end The target database version
1835 * @return An ordered list of [Migration] objects that should be run to migrate between the
1836 * given versions. If a migration path cannot be found, returns `null`.
1837 */
1838 open fun findMigrationPath(start: Int, end: Int): List<Migration>? {
1839 return this.findMigrationPathExt(start, end)
1840 }
1841
1842 /**
1843 * Indicates if the given migration is contained within the [MigrationContainer] based on
1844 * its start-end versions.
1845 *
1846 * @param startVersion Start version of the migration.
1847 * @param endVersion End version of the migration
1848 * @return True if it contains a migration with the same start-end version, false otherwise.
1849 */
1850 actual fun contains(startVersion: Int, endVersion: Int): Boolean {
1851 return this.containsCommon(startVersion, endVersion)
1852 }
1853
1854 internal actual fun getSortedNodes(
1855 migrationStart: Int
1856 ): Pair<Map<Int, Migration>, Iterable<Int>>? {
1857 val targetNodes = migrations[migrationStart] ?: return null
1858 return targetNodes to targetNodes.keys
1859 }
1860
1861 internal actual fun getSortedDescendingNodes(
1862 migrationStart: Int
1863 ): Pair<Map<Int, Migration>, Iterable<Int>>? {
1864 val targetNodes = migrations[migrationStart] ?: return null
1865 return targetNodes to targetNodes.descendingKeySet()
1866 }
1867 }
1868
1869 /** Callback for [RoomDatabase]. */
1870 actual abstract class Callback {
1871 /**
1872 * Called when the database is created for the first time. This is called after all the
1873 * tables are created.
1874 *
1875 * This function is only called when Room is configured without a driver. If a driver is set
1876 * using [androidx.room.RoomDatabase.Builder.setDriver], then only the version that receives
1877 * a [SQLiteConnection] is called.
1878 *
1879 * @param db The database.
1880 */
1881 open fun onCreate(db: SupportSQLiteDatabase) {}
1882
1883 /**
1884 * Called when the database is created for the first time.
1885 *
1886 * This function called after all the tables are created.
1887 *
1888 * @param connection The database connection.
1889 */
1890 actual open fun onCreate(connection: SQLiteConnection) {
1891 if (connection is SupportSQLiteConnection) {
1892 onCreate(connection.db)
1893 }
1894 }
1895
1896 /**
1897 * Called after the database was destructively migrated
1898 *
1899 * This function is only called when Room is configured without a driver. If a driver is set
1900 * using [androidx.room.RoomDatabase.Builder.setDriver], then only the version that receives
1901 * a [SQLiteConnection] is called.
1902 *
1903 * @param db The database.
1904 */
1905 open fun onDestructiveMigration(db: SupportSQLiteDatabase) {}
1906
1907 /**
1908 * Called after the database was destructively migrated.
1909 *
1910 * @param connection The database connection.
1911 */
1912 actual open fun onDestructiveMigration(connection: SQLiteConnection) {
1913 if (connection is SupportSQLiteConnection) {
1914 onDestructiveMigration(connection.db)
1915 }
1916 }
1917
1918 /**
1919 * Called when the database has been opened.
1920 *
1921 * This function is only called when Room is configured without a driver. If a driver is set
1922 * using [androidx.room.RoomDatabase.Builder.setDriver], then only the version that receives
1923 * a [SQLiteConnection] is called.
1924 *
1925 * @param db The database.
1926 */
1927 open fun onOpen(db: SupportSQLiteDatabase) {}
1928
1929 /**
1930 * Called when the database has been opened.
1931 *
1932 * @param connection The database connection.
1933 */
1934 actual open fun onOpen(connection: SQLiteConnection) {
1935 if (connection is SupportSQLiteConnection) {
1936 onOpen(connection.db)
1937 }
1938 }
1939 }
1940
1941 /**
1942 * Callback for [Builder.createFromAsset], [Builder.createFromFile] and
1943 * [Builder.createFromInputStream]
1944 *
1945 * This callback will be invoked after the pre-package DB is copied but before Room had a chance
1946 * to open it and therefore before the [RoomDatabase.Callback] methods are invoked. This
1947 * callback can be useful for updating the pre-package DB schema to satisfy Room's schema
1948 * validation.
1949 */
1950 abstract class PrepackagedDatabaseCallback {
1951 /**
1952 * Called when the pre-packaged database has been copied.
1953 *
1954 * @param db The database.
1955 */
1956 open fun onOpenPrepackagedDatabase(db: SupportSQLiteDatabase) {}
1957 }
1958
1959 /**
1960 * Callback interface for when SQLite queries are executed.
1961 *
1962 * Can be set using [RoomDatabase.Builder.setQueryCallback].
1963 */
1964 fun interface QueryCallback {
1965 /**
1966 * Called when a SQL query is executed.
1967 *
1968 * @param sqlQuery The SQLite query statement.
1969 * @param bindArgs Arguments of the query if available, empty list otherwise.
1970 */
1971 fun onQuery(sqlQuery: String, bindArgs: List<Any?>)
1972 }
1973
1974 companion object {
1975 /**
1976 * Unfortunately, we cannot read this value so we are only setting it to the SQLite default.
1977 */
1978 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) // used in generated code
1979 const val MAX_BIND_PARAMETER_CNT = 999
1980 }
1981 }
1982
1983 /**
1984 * Calls the specified suspending [block] in a database transaction. The transaction will be marked
1985 * as successful unless an exception is thrown in the suspending [block] or the coroutine is
1986 * cancelled.
1987 *
1988 * Room will only perform at most one transaction at a time, additional transactions are queued and
1989 * executed on a first come, first serve order.
1990 *
1991 * Performing blocking database operations is not permitted in a coroutine scope other than the one
1992 * received by the suspending block. It is recommended that all [Dao] function invoked within the
1993 * [block] be suspending functions.
1994 *
1995 * The internal dispatcher used to execute the given [block] will block an utilize a thread from
1996 * Room's transaction executor until the [block] is complete.
1997 */
<lambda>null1998 suspend fun <R> RoomDatabase.withTransaction(block: suspend () -> R): R = withTransactionContext {
1999 @Suppress("DEPRECATION") beginTransaction()
2000 try {
2001 val result = block.invoke()
2002 @Suppress("DEPRECATION") setTransactionSuccessful()
2003 result
2004 } finally {
2005 @Suppress("DEPRECATION") endTransaction()
2006 }
2007 }
2008
2009 /** Calls the specified suspending [block] with Room's transaction context. */
withTransactionContextnull2010 internal suspend fun <R> RoomDatabase.withTransactionContext(block: suspend () -> R): R {
2011 val transactionBlock: suspend CoroutineScope.() -> R = transaction@{
2012 val transactionElement = coroutineContext[TransactionElement]!!
2013 transactionElement.acquire()
2014 try {
2015 return@transaction block.invoke()
2016 } finally {
2017 transactionElement.release()
2018 }
2019 }
2020 // Use inherited transaction context if available, this allows nested suspending transactions.
2021 val transactionDispatcher = coroutineContext[TransactionElement]?.transactionDispatcher
2022 return if (transactionDispatcher != null) {
2023 withContext(transactionDispatcher, transactionBlock)
2024 } else {
2025 startTransactionCoroutine(coroutineContext, transactionBlock)
2026 }
2027 }
2028
2029 /**
2030 * Suspend caller coroutine and start the transaction coroutine in a thread from the
2031 * [RoomDatabase.transactionExecutor], resuming the caller coroutine with the result once done. The
2032 * [context] will be a parent of the started coroutine to propagating cancellation and release the
2033 * thread when cancelled.
2034 */
startTransactionCoroutinenull2035 private suspend fun <R> RoomDatabase.startTransactionCoroutine(
2036 context: CoroutineContext,
2037 transactionBlock: suspend CoroutineScope.() -> R
2038 ): R = suspendCancellableCoroutine { continuation ->
2039 try {
2040 transactionExecutor.execute {
2041 try {
2042 // Thread acquired, start the transaction coroutine using the parent context.
2043 // The started coroutine will have an event loop dispatcher that we'll use for the
2044 // transaction context.
2045 runBlocking(context.minusKey(ContinuationInterceptor)) {
2046 val dispatcher = coroutineContext[ContinuationInterceptor]!!
2047 val transactionContext = createTransactionContext(dispatcher)
2048 continuation.resume(withContext(transactionContext, transactionBlock))
2049 }
2050 } catch (ex: Throwable) {
2051 // If anything goes wrong, propagate exception to the calling coroutine.
2052 continuation.cancel(ex)
2053 }
2054 }
2055 } catch (ex: RejectedExecutionException) {
2056 // Couldn't acquire a thread, cancel coroutine.
2057 continuation.cancel(
2058 IllegalStateException(
2059 "Unable to acquire a thread to perform the database transaction.",
2060 ex
2061 )
2062 )
2063 }
2064 }
2065
2066 /**
2067 * Creates a [CoroutineContext] for performing database operations within a coroutine transaction.
2068 *
2069 * The context is a combination of a dispatcher, a [TransactionElement] and a thread local element.
2070 * * The dispatcher will dispatch coroutines to a single thread that is taken over from the Room
2071 * transaction executor. If the coroutine context is switched, suspending DAO functions will be
2072 * able to dispatch to the transaction thread. In reality the dispatcher is the event loop of a
2073 * [runBlocking] started on the dedicated thread.
2074 * * The [TransactionElement] serves as an indicator for inherited context, meaning, if there is a
2075 * switch of context, suspending DAO methods will be able to use the indicator to dispatch the
2076 * database operation to the transaction thread.
2077 * * The thread local element serves as a second indicator and marks threads that are used to
2078 * execute coroutines within the coroutine transaction, more specifically it allows us to identify
2079 * if a blocking DAO method is invoked within the transaction coroutine. Never assign meaning to
2080 * this value, for now all we care is if its present or not.
2081 */
createTransactionContextnull2082 private fun RoomDatabase.createTransactionContext(
2083 dispatcher: ContinuationInterceptor
2084 ): CoroutineContext {
2085 val transactionElement = TransactionElement(dispatcher)
2086 val threadLocalElement =
2087 suspendingTransactionId.asContextElement(System.identityHashCode(transactionElement))
2088 return dispatcher + transactionElement + threadLocalElement
2089 }
2090
2091 /** A [CoroutineContext.Element] that indicates there is an on-going database transaction. */
2092 internal class TransactionElement(internal val transactionDispatcher: ContinuationInterceptor) :
2093 CoroutineContext.Element {
2094
2095 companion object Key : CoroutineContext.Key<TransactionElement>
2096
2097 override val key: CoroutineContext.Key<TransactionElement>
2098 get() = TransactionElement
2099
2100 /**
2101 * Number of transactions (including nested ones) started with this element. Call [acquire] to
2102 * increase the count and [release] to decrease it.
2103 */
2104 private val referenceCount = AtomicInteger(0)
2105
acquirenull2106 fun acquire() {
2107 referenceCount.incrementAndGet()
2108 }
2109
releasenull2110 fun release() {
2111 val count = referenceCount.decrementAndGet()
2112 if (count < 0) {
2113 throw IllegalStateException("Transaction was never started or was already released.")
2114 }
2115 }
2116 }
2117
2118 /**
2119 * Creates a [Flow] that listens for changes in the database via the [InvalidationTracker] and emits
2120 * sets of the tables that were invalidated.
2121 *
2122 * The Flow will emit at least one value, a set of all the tables registered for observation to
2123 * kick-start the stream unless [emitInitialState] is set to `false`.
2124 *
2125 * If one of the tables to observe does not exist in the database, this functions throws an
2126 * [IllegalArgumentException].
2127 *
2128 * The returned Flow can be used to create a stream that reacts to changes in the database:
2129 * ```
2130 * fun getArtistTours(from: Date, to: Date): Flow<Map<Artist, TourState>> {
2131 * return db.invalidationTrackerFlow("Artist").map { _ ->
2132 * val artists = artistsDao.getAllArtists()
2133 * val tours = tourService.fetchStates(artists.map { it.id })
2134 * associateTours(artists, tours, from, to)
2135 * }
2136 * }
2137 * ```
2138 *
2139 * @param tables The name of the tables or views to observe.
2140 * @param emitInitialState Set to `false` if no initial emission is desired. Default value is
2141 * `true`.
2142 */
2143 @Deprecated(
2144 message = "Replaced by equivalent API in InvalidationTracker.",
2145 replaceWith = ReplaceWith("this.invalidationTracker.createFlow(*tables)")
2146 )
invalidationTrackerFlownull2147 fun RoomDatabase.invalidationTrackerFlow(
2148 vararg tables: String,
2149 emitInitialState: Boolean = true
2150 ): Flow<Set<String>> = invalidationTracker.createFlow(*tables, emitInitialState = emitInitialState)
2151