Files

87 lines
3.0 KiB
Swift

/// An internal struct that defines a migration.
struct Migration {
enum ForeignKeyChecks {
case deferred
case immediate
case disabled
}
let identifier: String
var foreignKeyChecks: ForeignKeyChecks
let migrate: (Database) throws -> Void
func run(_ db: Database) throws {
if try Bool.fetchOne(db, sql: "PRAGMA foreign_keys") ?? false {
switch foreignKeyChecks {
case .deferred:
try runWithDeferredForeignKeysChecks(db)
case .immediate:
try runWithImmediateForeignKeysChecks(db)
case .disabled:
try runWithDisabledForeignKeysChecks(db)
}
} else {
try runWithImmediateForeignKeysChecks(db)
}
}
private func runWithImmediateForeignKeysChecks(_ db: Database) throws {
try db.inTransaction(.immediate) {
try migrate(db)
try insertAppliedIdentifier(db)
return .commit
}
}
private func runWithDisabledForeignKeysChecks(_ db: Database) throws {
try db.execute(sql: "PRAGMA foreign_keys = OFF")
try throwingFirstError(
execute: {
try db.inTransaction(.immediate) {
try migrate(db)
try insertAppliedIdentifier(db)
return .commit
}
},
finally: {
try db.execute(sql: "PRAGMA foreign_keys = ON")
})
}
private func runWithDeferredForeignKeysChecks(_ db: Database) throws {
// Support for database alterations described at
// https://www.sqlite.org/lang_altertable.html#otheralter
//
// > 1. If foreign key constraints are enabled, disable them using
// > PRAGMA foreign_keys=OFF.
try db.execute(sql: "PRAGMA foreign_keys = OFF")
try throwingFirstError(
execute: {
// > 2. Start a transaction.
try db.inTransaction(.immediate) {
try migrate(db)
try insertAppliedIdentifier(db)
// > 10. If foreign key constraints were originally enabled
// > then run PRAGMA foreign_key_check to verify that the
// > schema change did not break any foreign
// > key constraints.
try db.checkForeignKeys()
// > 11. Commit the transaction started in step 2.
return .commit
}
},
finally: {
// > 12. If foreign keys constraints were originally enabled,
// > reenable them now.
try db.execute(sql: "PRAGMA foreign_keys = ON")
})
}
private func insertAppliedIdentifier(_ db: Database) throws {
try db.execute(sql: "INSERT INTO grdb_migrations (identifier) VALUES (?)", arguments: [identifier])
}
}