【问题标题】:Disabling or Deferring Foreign Key Enforcement with GRDB for Database Migration使用 GRDB 禁用或推迟执行外键以进行数据库迁移
【发布时间】:2021-06-02 07:18:53
【问题描述】:

我已在我的移动应用程序中使用了 GRDB 库。所有当前功能都运行良好,我已经开始实施从早期数据库版本的迁移。为了与我在其他平台上的实现保持一致,我决定使用 SQLite 的 user_version 而不是 GRDB 自己的迁移框架,它使用自己的表。

随着表的更改、复制、创建和删除,一个迁移步骤(数据库版本)中的更改碰巧相互依赖。由于在事务结束时提交更改,这会导致外键违规,升级失败。

解决此问题的一种方法是通过推迟 (defer_foreign_keys pragma) 或暂时禁用它以设置 foreign_keys pragma 来防止外键强制执行。不幸的是,我对这两种选择都不太满意。例如,经过一些测试后发现,尝试关闭外键检查

config.prepareDatabase { db in
    try db.execute(sql: "PRAGMA foreign_keys = OFF")
}

并使用

阅读编译指示
    try dbQueue.write { db in
        print(try Bool.fetchOne(db, sql: "PRAGMA foreign_keys")! as Bool)
    }

或通过检查数据库表明foreign_keys 设置保持开启。

我的迁移步骤看起来稍微简化了,如下所示:

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")
    }
}

我的初始 GRDB 设置如下:

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:
...

在数据库迁移期间防止这些外键失败的最佳方法是什么?为什么我的 pragma 语句不起作用?

【问题讨论】:

    标签: ios swift sqlite grdb


    【解决方案1】:

    引用https://www.sqlite.org/pragma.html#pragma_foreign_keys:

    此编译指示 [foreign_keys] 是事务中的空操作;只有在没有待处理的 BEGIN 或 SAVEPOINT 时,才能启用或禁用外键约束强制执行。

    GRDB DatabaseQueue.write 方法打开一个事务。所以当你想禁用外键时,你必须执行manual transaction handling

    // Add the `writeWithDeferredForeignKeys` method to
    // DatabaseQueue and DatabasePool.
    extension DatabaseWriter {
        func writeWithDeferredForeignKeys(_ updates: (Database) throws -> Void) throws {
            try writeWithoutTransaction { db in
                // Disable foreign keys
                try db.execute(sql: "PRAGMA foreign_keys = OFF");
                
                do {
                    // Perform updates in a transaction
                    try db.inTransaction {
                        try updates(db)
                        
                        // Check foreign keys before commit
                        if try Row.fetchOne(db, sql: "PRAGMA foreign_key_check") != nil {
                            throw DatabaseError(resultCode: .SQLITE_CONSTRAINT_FOREIGNKEY)
                        }
                        
                        return .commit
                    }
                    
                    // Re-enable foreign keys
                    try db.execute(sql: "PRAGMA foreign_keys = ON");
                } catch {
                    // Re-enable foreign keys and rethrow
                    try db.execute(sql: "PRAGMA foreign_keys = ON");
                    throw error
                }
            }
        }
    }
    
    try dbQueue.writeWithDeferredForeignKeys { db in
        try db.execute(sql: ...)
        try db.execute(sql: ...)
    }
    

    我对@9​​87654326@ 不够熟悉,但上面的示例代码应该可以帮助您收敛到您所追求的解决方案。

    【讨论】:

      猜你喜欢
      • 2016-11-01
      • 2018-11-26
      • 2012-04-11
      • 2017-02-10
      • 2019-12-02
      • 1970-01-01
      • 2020-04-21
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多