Skip to content

Commit c246e6e

Browse files
authored
Only run certain PRAGMAs on writer connections (#127)
- foreign_key and synchronous only affect writes, so they aren't needed on reader connections - Also prevent use of connections before schema creation/migration
1 parent 29987c3 commit c246e6e

File tree

3 files changed

+106
-65
lines changed

3 files changed

+106
-65
lines changed

library/src/commonMain/kotlin/com/eygraber/sqldelight/androidx/driver/AndroidxSqliteDriver.kt

Lines changed: 70 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public class AndroidxSqliteDriver(
3737
connectionFactory: AndroidxSqliteConnectionFactory,
3838
databaseType: AndroidxSqliteDatabaseType,
3939
private val schema: SqlSchema<QueryResult.Value<Unit>>,
40-
private val configuration: AndroidxSqliteConfiguration = AndroidxSqliteConfiguration(),
40+
configuration: AndroidxSqliteConfiguration = AndroidxSqliteConfiguration(),
4141
private val migrateEmptySchema: Boolean = false,
4242
/**
4343
* A callback to configure the database connection when it's first opened.
@@ -124,6 +124,8 @@ public class AndroidxSqliteDriver(
124124
@Suppress("NonBooleanPropertyPrefixedWithIs")
125125
private val isFirstInteraction = atomic(true)
126126

127+
private val configuration get() = connectionPool.configuration
128+
127129
private val connectionPool by lazy {
128130
val nameProvider = when(databaseType) {
129131
is AndroidxSqliteDatabaseType.File -> databaseType::databaseFilePath
@@ -196,39 +198,52 @@ public class AndroidxSqliteDriver(
196198
private val migrationCallbacks = migrationCallbacks
197199

198200
/**
199-
* True if foreign key constraints are enabled.
201+
* Journal mode to use.
202+
*
203+
* This function will block until pending schema creation/migration is completed,
204+
* and all created connections have been updated.
205+
*
206+
* Note that this means that this [SqliteJournalMode] **will not** be used for schema creation/migration.
200207
*
201-
* This function will block until all created connections have been updated.
208+
* Please use [AndroidxSqliteConfiguration] or [onConfigure] if a specific [SqliteJournalMode] is needed
209+
* during schema creation/migration.
202210
*
203211
* An exception will be thrown if this is called from within a transaction.
204212
*/
205-
public fun setForeignKeyConstraintsEnabled(isForeignKeyConstraintsEnabled: Boolean) {
213+
public fun setJournalMode(journalMode: SqliteJournalMode) {
206214
check(currentTransaction() == null) {
207-
"setForeignKeyConstraintsEnabled cannot be called from within a transaction"
215+
"setJournalMode cannot be called from within a transaction"
208216
}
209217

210-
connectionPool.setForeignKeyConstraintsEnabled(isForeignKeyConstraintsEnabled)
218+
// run creation or migration if needed before setting the journal mode
219+
createOrMigrateIfNeeded()
220+
221+
connectionPool.setJournalMode(journalMode)
211222
}
212223

213224
/**
214-
* Journal mode to use.
215-
*
216-
* This function will block until all created connections have been updated.
225+
* This function will block until executed on the writer connection.
217226
*
218227
* An exception will be thrown if this is called from within a transaction.
219228
*/
220-
public fun setJournalMode(journalMode: SqliteJournalMode) {
229+
public fun setForeignKeyConstraintsEnabled(isForeignKeyConstraintsEnabled: Boolean) {
221230
check(currentTransaction() == null) {
222-
"setJournalMode cannot be called from within a transaction"
231+
"setForeignKeyConstraintsEnabled cannot be called from within a transaction"
223232
}
224233

225-
connectionPool.setJournalMode(journalMode)
234+
connectionPool.updateForeignKeyConstraintsEnabled(isForeignKeyConstraintsEnabled)
235+
236+
val foreignKeys = if(isForeignKeyConstraintsEnabled) "ON" else "OFF"
237+
execute(
238+
identifier = null,
239+
sql = "PRAGMA foreign_keys = $foreignKeys;",
240+
parameters = 0,
241+
binders = null,
242+
)
226243
}
227244

228245
/**
229-
* Synchronous mode to use.
230-
*
231-
* This function will block until all created connections have been updated.
246+
* This function will block until executed on the writer connection.
232247
*
233248
* An exception will be thrown if this is called from within a transaction.
234249
*/
@@ -237,7 +252,14 @@ public class AndroidxSqliteDriver(
237252
"setSync cannot be called from within a transaction"
238253
}
239254

240-
connectionPool.setSync(sync)
255+
connectionPool.updateSync(sync)
256+
257+
execute(
258+
identifier = null,
259+
sql = "PRAGMA synchronous = ${sync.value};",
260+
parameters = 0,
261+
binders = null,
262+
)
241263
}
242264

243265
override fun addListener(vararg queryKeys: String, listener: Query.Listener) {
@@ -407,8 +429,15 @@ public class AndroidxSqliteDriver(
407429
): QueryResult.Value<R> {
408430
createOrMigrateIfNeeded()
409431

432+
// PRAGMA foreign_keys and synchronous should always be queried from the writer connection
433+
// since these are per-connection settings and only the writer connection has them set
434+
val shouldUseWriterConnection = sql.trim().run {
435+
startsWith("PRAGMA foreign_keys", ignoreCase = true) ||
436+
startsWith("PRAGMA synchronous", ignoreCase = true)
437+
}
438+
410439
val transaction = currentTransaction()
411-
if(transaction == null) {
440+
if(transaction == null && !shouldUseWriterConnection) {
412441
val connection = connectionPool.acquireReaderConnection()
413442
try {
414443
return execute(
@@ -428,20 +457,30 @@ public class AndroidxSqliteDriver(
428457
connectionPool.releaseReaderConnection(connection)
429458
}
430459
} else {
431-
val connection = (transaction as Transaction).connection
432-
return execute(
433-
identifier = identifier,
434-
connection = connection,
435-
createStatement = { c ->
436-
AndroidxQuery(
437-
sql = sql,
438-
statement = c.prepare(sql),
439-
argCount = parameters,
440-
)
441-
},
442-
binders = binders,
443-
result = { executeQuery(mapper) },
444-
)
460+
val connection = when(transaction) {
461+
null -> connectionPool.acquireWriterConnection()
462+
else -> (transaction as Transaction).connection
463+
}
464+
465+
try {
466+
return execute(
467+
identifier = identifier,
468+
connection = connection,
469+
createStatement = { c ->
470+
AndroidxQuery(
471+
sql = sql,
472+
statement = c.prepare(sql),
473+
argCount = parameters,
474+
)
475+
},
476+
binders = binders,
477+
result = { executeQuery(mapper) },
478+
)
479+
} finally {
480+
if(transaction == null) {
481+
connectionPool.releaseWriterConnection()
482+
}
483+
}
445484
}
446485
}
447486

library/src/commonMain/kotlin/com/eygraber/sqldelight/androidx/driver/ConnectionPool.kt

Lines changed: 31 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,15 @@ import kotlinx.coroutines.sync.Mutex
99
import kotlinx.coroutines.sync.withLock
1010

1111
public interface ConnectionPool : AutoCloseable {
12+
public val configuration: AndroidxSqliteConfiguration
13+
1214
public fun acquireWriterConnection(): SQLiteConnection
1315
public fun releaseWriterConnection()
1416
public fun acquireReaderConnection(): SQLiteConnection
1517
public fun releaseReaderConnection(connection: SQLiteConnection)
16-
public fun setForeignKeyConstraintsEnabled(isForeignKeyConstraintsEnabled: Boolean)
1718
public fun setJournalMode(journalMode: SqliteJournalMode)
18-
public fun setSync(sync: SqliteSync)
19+
public fun updateForeignKeyConstraintsEnabled(isForeignKeyConstraintsEnabled: Boolean)
20+
public fun updateSync(sync: SqliteSync)
1921
}
2022

2123
internal class AndroidxDriverConnectionPool(
@@ -29,14 +31,14 @@ internal class AndroidxDriverConnectionPool(
2931
val connection: Lazy<SQLiteConnection>,
3032
)
3133

32-
private var configuration by atomic(configuration)
34+
override var configuration by atomic(configuration)
3335

3436
private val name by lazy { nameProvider() }
3537

3638
private val writerConnection: SQLiteConnection by lazy {
3739
connectionFactory
3840
.createConnection(name)
39-
.withConfiguration(configuration)
41+
.withWriterConfiguration(configuration)
4042
}
4143

4244
private val writerMutex = Mutex()
@@ -56,7 +58,7 @@ internal class AndroidxDriverConnectionPool(
5658
lazy {
5759
connectionFactory
5860
.createConnection(name)
59-
.withConfiguration(configuration)
61+
.withReaderConfiguration(configuration)
6062
},
6163
),
6264
)
@@ -108,32 +110,27 @@ internal class AndroidxDriverConnectionPool(
108110
}
109111
}
110112

111-
override fun setForeignKeyConstraintsEnabled(isForeignKeyConstraintsEnabled: Boolean) {
113+
override fun setJournalMode(journalMode: SqliteJournalMode) {
112114
configuration = configuration.copy(
113-
isForeignKeyConstraintsEnabled = isForeignKeyConstraintsEnabled,
115+
journalMode = journalMode,
114116
)
115117

116-
val foreignKeys = if(isForeignKeyConstraintsEnabled) "ON" else "OFF"
117-
runPragmaOnAllConnections("PRAGMA foreign_keys = $foreignKeys;")
118+
runPragmaOnAllCreatedConnections("PRAGMA journal_mode = ${configuration.journalMode.value};")
118119
}
119120

120-
override fun setJournalMode(journalMode: SqliteJournalMode) {
121+
override fun updateForeignKeyConstraintsEnabled(isForeignKeyConstraintsEnabled: Boolean) {
121122
configuration = configuration.copy(
122-
journalMode = journalMode,
123+
isForeignKeyConstraintsEnabled = isForeignKeyConstraintsEnabled,
123124
)
124-
125-
runPragmaOnAllConnections("PRAGMA journal_mode = ${configuration.journalMode.value};")
126125
}
127126

128-
override fun setSync(sync: SqliteSync) {
127+
override fun updateSync(sync: SqliteSync) {
129128
configuration = configuration.copy(
130129
sync = sync,
131130
)
132-
133-
runPragmaOnAllConnections("PRAGMA synchronous = ${configuration.sync.value};")
134131
}
135132

136-
private fun runPragmaOnAllConnections(sql: String) {
133+
private fun runPragmaOnAllCreatedConnections(sql: String) {
137134
val writer = acquireWriterConnection()
138135
try {
139136
writer.execSQL(sql)
@@ -180,12 +177,12 @@ internal class PassthroughConnectionPool(
180177
nameProvider: () -> String,
181178
configuration: AndroidxSqliteConfiguration,
182179
) : ConnectionPool {
183-
private var configuration by atomic(configuration)
180+
override var configuration by atomic(configuration)
184181

185182
private val name by lazy { nameProvider() }
186183

187184
private val delegatedConnection: SQLiteConnection by lazy {
188-
connectionFactory.createConnection(name).withConfiguration(configuration)
185+
connectionFactory.createConnection(name).withWriterConfiguration(configuration)
189186
}
190187

191188
override fun acquireWriterConnection() = delegatedConnection
@@ -196,15 +193,6 @@ internal class PassthroughConnectionPool(
196193

197194
override fun releaseReaderConnection(connection: SQLiteConnection) {}
198195

199-
override fun setForeignKeyConstraintsEnabled(isForeignKeyConstraintsEnabled: Boolean) {
200-
configuration = configuration.copy(
201-
isForeignKeyConstraintsEnabled = isForeignKeyConstraintsEnabled,
202-
)
203-
204-
val foreignKeys = if(isForeignKeyConstraintsEnabled) "ON" else "OFF"
205-
delegatedConnection.execSQL("PRAGMA foreign_keys = $foreignKeys;")
206-
}
207-
208196
override fun setJournalMode(journalMode: SqliteJournalMode) {
209197
configuration = configuration.copy(
210198
journalMode = journalMode,
@@ -217,20 +205,24 @@ internal class PassthroughConnectionPool(
217205
delegatedConnection.execSQL("PRAGMA foreign_keys = $foreignKeys;")
218206
}
219207

220-
override fun setSync(sync: SqliteSync) {
208+
override fun updateForeignKeyConstraintsEnabled(isForeignKeyConstraintsEnabled: Boolean) {
221209
configuration = configuration.copy(
222-
sync = sync,
210+
isForeignKeyConstraintsEnabled = isForeignKeyConstraintsEnabled,
223211
)
212+
}
224213

225-
delegatedConnection.execSQL("PRAGMA synchronous = ${configuration.sync.value};")
214+
override fun updateSync(sync: SqliteSync) {
215+
configuration = configuration.copy(
216+
sync = sync,
217+
)
226218
}
227219

228220
override fun close() {
229221
delegatedConnection.close()
230222
}
231223
}
232224

233-
private fun SQLiteConnection.withConfiguration(
225+
private fun SQLiteConnection.withWriterConfiguration(
234226
configuration: AndroidxSqliteConfiguration,
235227
): SQLiteConnection = this.apply {
236228
// copy the configuration for thread safety
@@ -243,3 +235,10 @@ private fun SQLiteConnection.withConfiguration(
243235
execSQL("PRAGMA foreign_keys = $foreignKeys;")
244236
}
245237
}
238+
239+
private fun SQLiteConnection.withReaderConfiguration(
240+
configuration: AndroidxSqliteConfiguration,
241+
): SQLiteConnection = this.apply {
242+
// copy the configuration for thread safety
243+
execSQL("PRAGMA journal_mode = ${configuration.copy().journalMode.value};")
244+
}

library/src/commonTest/kotlin/com/eygraber/sqldelight/androidx/driver/AndroidxSqliteTransacterTest.kt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -341,11 +341,14 @@ private class FirstTransactionsFailConnectionPool : ConnectionPool {
341341
override fun close() {
342342
firstTransactionFailConnection.close()
343343
}
344+
345+
override val configuration = AndroidxSqliteConfiguration()
346+
344347
override fun acquireWriterConnection() = firstTransactionFailConnection
345348
override fun releaseWriterConnection() {}
346349
override fun acquireReaderConnection() = firstTransactionFailConnection
347350
override fun releaseReaderConnection(connection: SQLiteConnection) {}
348-
override fun setForeignKeyConstraintsEnabled(isForeignKeyConstraintsEnabled: Boolean) {}
349351
override fun setJournalMode(journalMode: SqliteJournalMode) {}
350-
override fun setSync(sync: SqliteSync) {}
352+
override fun updateForeignKeyConstraintsEnabled(isForeignKeyConstraintsEnabled: Boolean) {}
353+
override fun updateSync(sync: SqliteSync) {}
351354
}

0 commit comments

Comments
 (0)