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