1 /*
<lambda>null2 * Copyright (C) 2018 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 @file:JvmMultifileClass
18 @file:JvmName("DBUtil")
19
20 package androidx.room.util
21
22 import android.database.AbstractWindowedCursor
23 import android.database.Cursor
24 import android.os.Build
25 import android.os.CancellationSignal
26 import androidx.annotation.RestrictTo
27 import androidx.room.RoomDatabase
28 import androidx.room.TransactionElement
29 import androidx.room.coroutines.RawConnectionAccessor
30 import androidx.room.coroutines.runBlockingUninterruptible
31 import androidx.room.driver.SupportSQLiteConnection
32 import androidx.room.withTransactionContext
33 import androidx.sqlite.SQLiteConnection
34 import androidx.sqlite.db.SupportSQLiteDatabase
35 import androidx.sqlite.db.SupportSQLiteQuery
36 import java.io.File
37 import java.io.FileInputStream
38 import java.io.IOException
39 import java.nio.ByteBuffer
40 import kotlin.coroutines.CoroutineContext
41 import kotlin.coroutines.coroutineContext
42 import kotlinx.coroutines.withContext
43
44 /** Performs a database operation. */
45 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) // used in generated code
46 actual suspend fun <R> performSuspending(
47 db: RoomDatabase,
48 isReadOnly: Boolean,
49 inTransaction: Boolean,
50 block: (SQLiteConnection) -> R
51 ): R =
52 db.compatCoroutineExecute(inTransaction) {
53 db.internalPerform(isReadOnly, inTransaction) { connection ->
54 val rawConnection = (connection as RawConnectionAccessor).rawConnection
55 block.invoke(rawConnection)
56 }
57 }
58
59 /** Blocking version of [performSuspending] */
60 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) // used in generated code
performBlockingnull61 fun <R> performBlocking(
62 db: RoomDatabase,
63 isReadOnly: Boolean,
64 inTransaction: Boolean,
65 block: (SQLiteConnection) -> R
66 ): R {
67 db.assertNotMainThread()
68 db.assertNotSuspendingTransaction()
69 return runBlockingUninterruptible {
70 // If in compatibility mode and the database is already in a transaction, then do not
71 // start a nested transaction to avoid the overhead and because the SupportSQLite APIs
72 // do not support real SAVEPOINT-based nested transactions.
73 val inTransaction = !(db.inCompatibilityMode() && db.inTransaction()) && inTransaction
74 db.internalPerform(isReadOnly, inTransaction) { connection ->
75 val rawConnection = (connection as RawConnectionAccessor).rawConnection
76 block.invoke(rawConnection)
77 }
78 }
79 }
80
81 /**
82 * Utility function to wrap a suspend block in Room's transaction coroutine.
83 *
84 * This function should only be invoked from generated code and is needed to support `@Transaction`
85 * delegates in Java and Kotlin. It is preferred to use the other 'perform' functions.
86 */
87 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) // used in generated code
performInTransactionSuspendingnull88 actual suspend fun <R> performInTransactionSuspending(db: RoomDatabase, block: suspend () -> R): R =
89 if (db.inCompatibilityMode()) {
90 db.withTransactionContext {
91 db.internalPerform(isReadOnly = false, inTransaction = true) { block.invoke() }
92 }
93 } else {
<lambda>null94 withContext(db.getCoroutineScope().coroutineContext) {
95 db.internalPerform(isReadOnly = false, inTransaction = true) { block.invoke() }
96 }
97 }
98
99 /**
100 * Compatibility suspend function execution with driver usage. This will maintain the dispatcher
101 * behaviour in [androidx.room.CoroutinesRoom.execute] when Room is in compatibility mode executing
102 * driver codegen utility functions.
103 */
compatCoroutineExecutenull104 private suspend inline fun <R> RoomDatabase.compatCoroutineExecute(
105 inTransaction: Boolean,
106 crossinline block: suspend () -> R
107 ): R {
108 if (inCompatibilityMode() && isOpenInternal && inTransaction()) {
109 return block.invoke()
110 }
111 return withContext(getCoroutineContext(inTransaction)) { block.invoke() }
112 }
113
114 /**
115 * Gets the database [CoroutineContext] to perform database operation on utility functions. Prefer
116 * using this function over directly accessing [RoomDatabase.getCoroutineScope] as it has platform
117 * compatibility behaviour.
118 */
getCoroutineContextnull119 internal actual suspend fun RoomDatabase.getCoroutineContext(
120 inTransaction: Boolean
121 ): CoroutineContext {
122 return if (inCompatibilityMode()) {
123 // If in compatibility mode check if we are on a transaction coroutine, if so combine
124 // it with the database context, otherwise use the database dispatchers.
125 coroutineContext[TransactionElement]?.transactionDispatcher?.let { getQueryContext() + it }
126 ?: if (inTransaction) getTransactionContext() else getQueryContext()
127 } else {
128 getCoroutineScope().coroutineContext
129 }
130 }
131
132 /**
133 * Performs the SQLiteQuery on the given database.
134 *
135 * This util method encapsulates copying the cursor if the `maybeCopy` parameter is `true` and
136 * either the api level is below a certain threshold or the full result of the query does not fit in
137 * a single window.
138 *
139 * @param db The database to perform the query on.
140 * @param sqLiteQuery The query to perform.
141 * @param maybeCopy True if the result cursor should maybe be copied, false otherwise.
142 * @return Result of the query.
143 */
144 @Deprecated("This is only used in the generated code and shouldn't be called directly.")
145 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) // used in generated code
querynull146 fun query(db: RoomDatabase, sqLiteQuery: SupportSQLiteQuery, maybeCopy: Boolean): Cursor {
147 return query(db, sqLiteQuery, maybeCopy, null)
148 }
149
150 /**
151 * Performs the SQLiteQuery on the given database.
152 *
153 * This util method encapsulates copying the cursor if the `maybeCopy` parameter is `true` and
154 * either the api level is below a certain threshold or the full result of the query does not fit in
155 * a single window.
156 *
157 * @param db The database to perform the query on.
158 * @param sqLiteQuery The query to perform.
159 * @param maybeCopy True if the result cursor should maybe be copied, false otherwise.
160 * @param signal The cancellation signal to be attached to the query.
161 * @return Result of the query.
162 */
163 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) // used in generated code
querynull164 fun query(
165 db: RoomDatabase,
166 sqLiteQuery: SupportSQLiteQuery,
167 maybeCopy: Boolean,
168 signal: CancellationSignal?
169 ): Cursor {
170 val cursor = db.query(sqLiteQuery, signal)
171 if (maybeCopy && cursor is AbstractWindowedCursor) {
172 val rowsInCursor = cursor.count // Should fill the window.
173 val rowsInWindow =
174 if (cursor.hasWindow()) {
175 cursor.window.numRows
176 } else {
177 rowsInCursor
178 }
179 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || rowsInWindow < rowsInCursor) {
180 return copyAndClose(cursor)
181 }
182 }
183 return cursor
184 }
185
186 /**
187 * Drops all FTS content sync triggers created by Room.
188 *
189 * FTS content sync triggers created by Room are those that are found in the sqlite_master table
190 * who's names start with 'room_fts_content_sync_'.
191 *
192 * @param db The database.
193 */
194 @Deprecated("Replaced by dropFtsSyncTriggers(connection: SQLiteConnection)")
195 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) // used in generated code
dropFtsSyncTriggersnull196 fun dropFtsSyncTriggers(db: SupportSQLiteDatabase) {
197 dropFtsSyncTriggers(SupportSQLiteConnection(db))
198 }
199
200 /** Checks for foreign key violations by executing a PRAGMA foreign_key_check. */
201 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) // used in generated code
foreignKeyChecknull202 fun foreignKeyCheck(db: SupportSQLiteDatabase, tableName: String) {
203 foreignKeyCheck(SupportSQLiteConnection(db), tableName)
204 }
205
206 /**
207 * Reads the user version number out of the database header from the given file.
208 *
209 * @param databaseFile the database file.
210 * @return the database version
211 * @throws IOException if something goes wrong reading the file, such as bad database header or
212 * missing permissions.
213 * @see [User Version Number](https://www.sqlite.org/fileformat.html.user_version_number).
214 */
215 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) // used in generated code
216 @Throws(IOException::class)
readVersionnull217 fun readVersion(databaseFile: File): Int {
218 FileInputStream(databaseFile).channel.use { input ->
219 val buffer = ByteBuffer.allocate(4)
220 input.tryLock(60, 4, true)
221 input.position(60)
222 val read = input.read(buffer)
223 if (read != 4) {
224 throw IOException("Bad database header, unable to read 4 bytes at offset 60")
225 }
226 buffer.rewind()
227 return buffer.int // ByteBuffer is big-endian by default
228 }
229 }
230
231 /**
232 * This function will create a new instance of [CancellationSignal].
233 *
234 * @return A new instance of CancellationSignal.
235 */
236 @Deprecated("Use constructor", ReplaceWith("CancellationSignal()", "android.os.CancellationSignal"))
237 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) // used in generated code
createCancellationSignalnull238 fun createCancellationSignal(): CancellationSignal {
239 return CancellationSignal()
240 }
241
242 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) // used in generated code
toSQLiteConnectionnull243 fun toSQLiteConnection(db: SupportSQLiteDatabase): SQLiteConnection {
244 return SupportSQLiteConnection(db)
245 }
246