1 // Copyright © 2021 650 Industries. All rights reserved. 2 3 import Foundation 4 import sqlite3 5 6 internal enum UpdatesDatabaseMigrationError: Error { 7 case foreignKeysError 8 case transactionError 9 case migrationSQLError 10 } 11 12 internal final class TransactionExecutor { 13 let db: OpaquePointer 14 15 init(db: OpaquePointer) { 16 self.db = db 17 } 18 19 func safeExecOrRollback(sql: String) throws { 20 guard sqlite3_exec(db, String(sql.utf8), nil, nil, nil) == SQLITE_OK else { 21 sqlite3_exec(db, "ROLLBACK;", nil, nil, nil) 22 throw UpdatesDatabaseMigrationError.migrationSQLError 23 } 24 } 25 26 func safeExecOrRollback(sql: String, args: [Any?]) throws { 27 do { 28 _ = try UpdatesDatabaseUtils.execute(sql: sql, withArgs: args, onDatabase: db) 29 } catch { 30 sqlite3_exec(db, "ROLLBACK;", nil, nil, nil) 31 throw UpdatesDatabaseMigrationError.migrationSQLError 32 } 33 } 34 } 35 36 internal extension OpaquePointer { 37 func withForeignKeysOff<R>(_ body: () throws -> R) throws -> R { 38 // https://www.sqlite.org/lang_altertable.html#otheralter 39 guard sqlite3_exec(self, "PRAGMA foreign_keys=OFF;", nil, nil, nil) == SQLITE_OK else { 40 throw UpdatesDatabaseMigrationError.foreignKeysError 41 } 42 defer { 43 sqlite3_exec(self, "PRAGMA foreign_keys=ON;", nil, nil, nil) 44 } 45 46 return try body() 47 } 48 49 func withTransaction<R>(_ body: (TransactionExecutor) throws -> R) throws -> R { 50 guard sqlite3_exec(self, "BEGIN;", nil, nil, nil) == SQLITE_OK else { 51 throw UpdatesDatabaseMigrationError.transactionError 52 } 53 54 let result = try body(TransactionExecutor(db: self)) 55 56 guard sqlite3_exec(self, "COMMIT;", nil, nil, nil) == SQLITE_OK else { 57 throw UpdatesDatabaseMigrationError.transactionError 58 } 59 60 return result 61 } 62 } 63 64 internal protocol UpdatesDatabaseMigration { 65 var filename: String { get } 66 func runMigration(onDatabase db: OpaquePointer) throws 67 } 68