1 /*
<lambda>null2  * Copyright 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package androidx.room
18 
19 import androidx.room.coroutines.AndroidSQLiteDriverConnectionPool
20 import androidx.room.coroutines.ConnectionPool
21 import androidx.room.coroutines.newConnectionPool
22 import androidx.room.coroutines.newSingleConnectionPool
23 import androidx.room.driver.SupportSQLiteConnection
24 import androidx.room.driver.SupportSQLiteConnectionPool
25 import androidx.room.driver.SupportSQLiteDriver
26 import androidx.sqlite.SQLiteConnection
27 import androidx.sqlite.db.SupportSQLiteDatabase
28 import androidx.sqlite.db.SupportSQLiteOpenHelper
29 import androidx.sqlite.driver.AndroidSQLiteDriver
30 
31 /**
32  * An Android platform specific [RoomConnectionManager] with backwards compatibility with
33  * [androidx.sqlite.db] APIs (SupportSQLite*).
34  */
35 internal actual class RoomConnectionManager : BaseRoomConnectionManager {
36 
37     override val configuration: DatabaseConfiguration
38     override val openDelegate: RoomOpenDelegate
39     override val callbacks: List<RoomDatabase.Callback>
40 
41     private val connectionPool: ConnectionPool
42 
43     internal val supportOpenHelper: SupportSQLiteOpenHelper?
44         get() = (connectionPool as? SupportSQLiteConnectionPool)?.supportDriver?.openHelper
45 
46     private var supportDatabase: SupportSQLiteDatabase? = null
47 
48     constructor(config: DatabaseConfiguration, openDelegate: RoomOpenDelegate) {
49         this.configuration = config
50         this.openDelegate = openDelegate
51         this.callbacks = config.callbacks ?: emptyList()
52         if (config.sqliteDriver == null) {
53             // Compatibility mode due to no driver provided, instead a driver (SupportSQLiteDriver)
54             // is created that wraps SupportSQLite* APIs. The underlying SupportSQLiteDatabase will
55             // be migrated through the SupportOpenHelperCallback or through old gen code using
56             // RoomOpenHelper. A ConnectionPool is also created that skips common opening
57             // procedure and has no real connection management logic.
58             requireNotNull(config.sqliteOpenHelperFactory) {
59                 "SQLiteManager was constructed with both null driver and open helper factory!"
60             }
61             val openHelperConfig =
62                 SupportSQLiteOpenHelper.Configuration.builder(config.context)
63                     .name(config.name)
64                     .callback(SupportOpenHelperCallback(openDelegate.version))
65                     .build()
66             this.connectionPool =
67                 SupportSQLiteConnectionPool(
68                     SupportSQLiteDriver(config.sqliteOpenHelperFactory.create(openHelperConfig))
69                 )
70         } else {
71             this.connectionPool =
72                 if (config.sqliteDriver is AndroidSQLiteDriver) {
73                     // Special-case the Android driver and use a pass-through pool since the Android
74                     // bindings internally already have a thread-confined connection pool.
75                     AndroidSQLiteDriverConnectionPool(
76                         driver = DriverWrapper(config.sqliteDriver),
77                         fileName = config.name ?: ":memory:"
78                     )
79                 } else if (config.name == null) {
80                     // An in-memory database must use a single connection pool.
81                     newSingleConnectionPool(
82                         driver = DriverWrapper(config.sqliteDriver),
83                         fileName = ":memory:"
84                     )
85                 } else {
86                     newConnectionPool(
87                         driver = DriverWrapper(config.sqliteDriver),
88                         fileName = config.name,
89                         maxNumOfReaders = config.journalMode.getMaxNumberOfReaders(),
90                         maxNumOfWriters = config.journalMode.getMaxNumberOfWriters()
91                     )
92                 }
93         }
94         init()
95     }
96 
97     constructor(
98         config: DatabaseConfiguration,
99         supportOpenHelperFactory: (DatabaseConfiguration) -> SupportSQLiteOpenHelper
100     ) {
101         this.configuration = config
102         this.openDelegate = NoOpOpenDelegate()
103         this.callbacks = config.callbacks ?: emptyList()
104         // Compatibility mode due to no driver provided, the SupportSQLiteDriver and
105         // SupportConnectionPool are created. A Room onOpen callback is installed so that the
106         // SupportSQLiteDatabase is extracted out of the RoomOpenHelper installed.
107         val configWithCompatibilityCallback =
108             config.installOnOpenCallback { db -> supportDatabase = db }
109         this.connectionPool =
110             SupportSQLiteConnectionPool(
111                 SupportSQLiteDriver(
112                     supportOpenHelperFactory.invoke(configWithCompatibilityCallback)
113                 )
114             )
115         init()
116     }
117 
118     private fun init() {
119         val wal = configuration.journalMode == RoomDatabase.JournalMode.WRITE_AHEAD_LOGGING
120         supportOpenHelper?.setWriteAheadLoggingEnabled(wal)
121     }
122 
123     override suspend fun <R> useConnection(
124         isReadOnly: Boolean,
125         block: suspend (Transactor) -> R
126     ): R = connectionPool.useConnection(isReadOnly, block)
127 
128     override fun resolveFileName(fileName: String): String =
129         if (fileName != ":memory:") {
130             // Get database path from context, if the database name is not an absolute path, then
131             // the app's database directory will be used, otherwise the given path is used.
132             configuration.context.getDatabasePath(fileName).absolutePath
133         } else {
134             fileName
135         }
136 
137     fun close() {
138         connectionPool.close()
139     }
140 
141     // TODO(b/316944352): Figure out auto-close with driver APIs
142     fun isSupportDatabaseOpen() = supportDatabase?.isOpen ?: false
143 
144     /** An implementation of [SupportSQLiteOpenHelper.Callback] used in compatibility mode. */
145     inner class SupportOpenHelperCallback(version: Int) :
146         SupportSQLiteOpenHelper.Callback(version) {
147         override fun onCreate(db: SupportSQLiteDatabase) {
148             this@RoomConnectionManager.onCreate(SupportSQLiteConnection(db))
149         }
150 
151         override fun onUpgrade(db: SupportSQLiteDatabase, oldVersion: Int, newVersion: Int) {
152             this@RoomConnectionManager.onMigrate(
153                 SupportSQLiteConnection(db),
154                 oldVersion,
155                 newVersion
156             )
157         }
158 
159         override fun onDowngrade(db: SupportSQLiteDatabase, oldVersion: Int, newVersion: Int) {
160             this.onUpgrade(db, oldVersion, newVersion)
161         }
162 
163         override fun onOpen(db: SupportSQLiteDatabase) {
164             this@RoomConnectionManager.onOpen(SupportSQLiteConnection(db))
165             supportDatabase = db
166         }
167     }
168 
169     /**
170      * A no op implementation of [RoomOpenDelegate] used in compatibility mode with old gen code
171      * that relies on [RoomOpenHelper].
172      */
173     private class NoOpOpenDelegate : RoomOpenDelegate(-1, "", "") {
174         override fun onCreate(connection: SQLiteConnection) {
175             error("NOP delegate should never be called")
176         }
177 
178         override fun onPreMigrate(connection: SQLiteConnection) {
179             error("NOP delegate should never be called")
180         }
181 
182         override fun onValidateSchema(connection: SQLiteConnection): ValidationResult {
183             error("NOP delegate should never be called")
184         }
185 
186         override fun onPostMigrate(connection: SQLiteConnection) {
187             error("NOP delegate should never be called")
188         }
189 
190         override fun onOpen(connection: SQLiteConnection) {
191             error("NOP delegate should never be called")
192         }
193 
194         override fun createAllTables(connection: SQLiteConnection) {
195             error("NOP delegate should never be called")
196         }
197 
198         override fun dropAllTables(connection: SQLiteConnection) {
199             error("NOP delegate should never be called")
200         }
201     }
202 
203     private fun DatabaseConfiguration.installOnOpenCallback(
204         onOpen: (SupportSQLiteDatabase) -> Unit
205     ): DatabaseConfiguration {
206         val newCallbacks =
207             (this.callbacks ?: emptyList()) +
208                 object : RoomDatabase.Callback() {
209                     override fun onOpen(db: SupportSQLiteDatabase) {
210                         onOpen.invoke(db)
211                     }
212                 }
213         return this.copy(callbacks = newCallbacks)
214     }
215 }
216