1 /*
2  * Copyright 2024 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 
17 package androidx.room
18 
19 import androidx.room.Transactor.SQLiteTransactionType
20 import androidx.sqlite.SQLiteConnection
21 import androidx.sqlite.SQLiteStatement
22 
23 /**
24  * A wrapper of [SQLiteConnection] that belongs to a connection pool and is safe to use in a
25  * coroutine.
26  */
27 interface PooledConnection {
28     /**
29      * Prepares a new SQL statement and use it within the code [block].
30      *
31      * Using the given [SQLiteStatement] after [block] completes is prohibited. The statement will
32      * also be thread confined, attempting to use it from another thread is an error.
33      *
34      * Using a statement locks the connection it belongs to, therefore try not to do long-running
35      * computations within the [block].
36      *
37      * @param sql The SQL statement to prepare
38      * @param block The code to use the statement
39      */
40     // TODO(b/319653917): Revisit shareable / caching APIs
usePreparednull41     suspend fun <R> usePrepared(sql: String, block: (SQLiteStatement) -> R): R
42 }
43 
44 /** Executes a single SQL statement that returns no values. */
45 suspend fun PooledConnection.execSQL(sql: String) {
46     usePrepared(sql) { it.step() }
47 }
48 
49 /** A [PooledConnection] that can perform transactions. */
50 interface Transactor : PooledConnection {
51 
52     /**
53      * Begins a transaction and runs the [block] within the transaction. If [block] fails to
54      * complete normally i.e., an exception is thrown, or [TransactionScope.rollback] is invoked
55      * then the transaction will be rollback, otherwise it is committed.
56      *
57      * If [inTransaction] returns `true` and this function is invoked it is the equivalent of
58      * starting a nested transaction as if [TransactionScope.withNestedTransaction] was invoked and
59      * the [type] of the transaction will be ignored since its type will be inherited from the
60      * parent transaction.
61      *
62      * See also [Transaction](https://www.sqlite.org/lang_transaction.html)
63      *
64      * @param type The type of transaction to begin.
65      * @param block The code that will execute within the transaction.
66      */
withTransactionnull67     suspend fun <R> withTransaction(
68         type: SQLiteTransactionType,
69         block: suspend TransactionScope<R>.() -> R
70     ): R
71 
72     /** Returns true if this connection has an active transaction, otherwise false. */
73     suspend fun inTransaction(): Boolean
74 
75     /**
76      * Transaction types.
77      *
78      * @see Transactor.withTransaction
79      */
80     enum class SQLiteTransactionType {
81         /**
82          * The transaction mode that does not start the actual transaction until the database is
83          * accessed, may it be a read or a write.
84          */
85         DEFERRED,
86         /** The transaction mode that immediately starts a write transaction. */
87         IMMEDIATE,
88         /**
89          * The transaction mode that immediately starts a write transaction and locks the database
90          * preventing others from accessing it.
91          */
92         EXCLUSIVE,
93     }
94 }
95 
96 /**
97  * A [PooledConnection] with an active transaction capable of performing nested transactions.
98  *
99  * @see Transactor
100  */
101 interface TransactionScope<T> : PooledConnection {
102 
103     /**
104      * Begins a nested transaction and runs the [block] within the transaction. If [block] fails to
105      * complete normally i.e., an exception is thrown, or [rollback] is invoked then the transaction
106      * will be rollback, otherwise it is committed.
107      *
108      * Note that a nested transaction is still governed by its parent transaction and it too must
109      * complete successfully for all its children transactions to be committed.
110      *
111      * See also [Savepoint](https://www.sqlite.org/lang_savepoint.html)
112      *
113      * @param block The code that will execute within the transaction.
114      */
withNestedTransactionnull115     suspend fun <R> withNestedTransaction(block: suspend TransactionScope<R>.() -> R): R
116 
117     /**
118      * Rollback the transaction, completing it and returning the [result].
119      *
120      * @see Transactor.withTransaction
121      * @see TransactionScope.withNestedTransaction
122      */
123     suspend fun rollback(result: T): Nothing
124 }
125 
126 /** Performs a [SQLiteTransactionType.DEFERRED] within the [block]. */
127 suspend fun <R> Transactor.deferredTransaction(block: suspend TransactionScope<R>.() -> R): R =
128     withTransaction(SQLiteTransactionType.DEFERRED, block)
129 
130 /** Performs a [SQLiteTransactionType.IMMEDIATE] within the [block]. */
131 suspend fun <R> Transactor.immediateTransaction(block: suspend TransactionScope<R>.() -> R): R =
132     withTransaction(SQLiteTransactionType.IMMEDIATE, block)
133 
134 /** Performs a [SQLiteTransactionType.EXCLUSIVE] within the [block]. */
135 suspend fun <R> Transactor.exclusiveTransaction(block: suspend TransactionScope<R>.() -> R): R =
136     withTransaction(SQLiteTransactionType.EXCLUSIVE, block)
137