I have taken the GRDB library into use in my mobile app. All current features are working nicely, and I have started implementing the migration from earlier databsase versions. For consistency with my implementation for other platforms, I have decided to use SQLite's user_version
instead of GRDB's own migration framework, which uses its own table.
With tables being altered, copied, created, and deleted, it happens that changes within one migration step (database version) depend on each other. As changes are committed at the end of a transaction, this leads to foreign key violations, and the upgrade fails.
A way to work around this is to prevent foreign key enforcement either by deferring (defer_foreign_keys
pragma) it or by temporarily disabling it for the transaction setting the foreign_keys
pragma. Unfortunately, I have not had much luck with either of the options. After some testing noticed, for instance, that attempting to turn off the foreign key checking with
config.prepareDatabase { db in
try db.execute(sql: "PRAGMA foreign_keys = OFF")
}
and reading the pragma using
try dbQueue.write { db in
print(try Bool.fetchOne(db, sql: "PRAGMA foreign_keys")! as Bool)
}
or by inspecting the database indicates that the foreign_keys
setting remained ON.
My migration steps look, slightly simplified, like this:
if try userVersion() < 2 {
try dbQueue.write { db in
try db.execute(sql: ...)
try db.execute(sql: ...)
...
try db.execute(sql: "PRAGMA user_version = 2")
}
}
if try userVersion() < 3 {
try dbQueue.write { db in
try db.execute(sql: ...)
try db.execute(sql: ...)
...
try db.execute(sql: "PRAGMA user_version = 3")
}
}
My initial GRDB setup is as follows:
var config = Configuration()
config.foreignKeysEnabled = true
let appSupportDirectory = NSSearchPathForDirectoriesInDomains(.applicationSupportDirectory, .userDomainMask, true).first!
dbPath = (appSupportDirectory as NSString).appendingPathComponent(dbName)
let fileManager = FileManager.default
if fileManager.fileExists(atPath: dbPath) {
// Just connect to database.
dbQueue = try DatabaseQueue(path: dbPath, configuration: config)
} else {
// Create new database.
dbQueue = try DatabaseQueue(path: dbPath, configuration: config)
// Create tables
...
}
// Database migration steps:
...
What would be the best approach for preventing those foreign key failures during database migration, and why are my pragma statements not working?