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