Skip to content

Commit 20ac522

Browse files
committed
Add functions for backing up the database.
1 parent df3aa4e commit 20ac522

2 files changed

Lines changed: 143 additions & 0 deletions

File tree

Sources/SQLiteKit/SQLQueue.swift

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ public class SQLQueue {
2424
self.database = database
2525
}
2626

27+
// MARK: - Run
28+
2729
/// Runs database actions asynchronously.
2830
public func run(actions: @escaping (SQLDatabase) throws -> Void) {
2931
run(actions: actions, completion: nil)
@@ -59,6 +61,8 @@ public class SQLQueue {
5961
}
6062
}
6163

64+
// MARK: - Transaction
65+
6266
/// Runs a database transaction asynchronously.
6367
public func transaction(actions: @escaping (SQLDatabase) throws -> SQLTransactionResult) {
6468
transaction(actions: actions, completion: nil)
@@ -97,4 +101,54 @@ public class SQLQueue {
97101
}
98102
return result
99103
}
104+
105+
// MARK: - Backup
106+
107+
public func storeBackupSynchronously(to path: String, vacuum: Bool = false) throws {
108+
try serialQueue.sync { [database] in
109+
try database.storeBackup(to: path, vacuum: vacuum)
110+
}
111+
}
112+
113+
public func storeBackup(to path: String, vacuum: Bool = false, completion: ((Result<(), SQLError>) -> Void)?) {
114+
serialQueue.async { [weak self] in
115+
guard let database = self?.database else {
116+
return
117+
}
118+
do {
119+
try database.storeBackup(to: path, vacuum: vacuum)
120+
} catch let error as SQLError {
121+
completion?(.failure(error))
122+
return
123+
} catch {
124+
fatalError("SQLQueue run failed from unknown error.")
125+
}
126+
127+
completion?(.success(Void()))
128+
}
129+
}
130+
131+
public func restoreBackupSynchronously(from path: String) throws {
132+
try serialQueue.sync { [database] in
133+
try database.restoreBackup(from: path)
134+
}
135+
}
136+
137+
public func restoreBackup(from path: String, completion: ((Result<(), SQLError>) -> Void)?) {
138+
serialQueue.async { [weak self] in
139+
guard let database = self?.database else {
140+
return
141+
}
142+
do {
143+
try database.restoreBackup(from: path)
144+
} catch let error as SQLError {
145+
completion?(.failure(error))
146+
return
147+
} catch {
148+
fatalError("SQLQueue run failed from unknown error.")
149+
}
150+
151+
completion?(.success(Void()))
152+
}
153+
}
100154
}

Sources/SQLiteKit/SQLiteDatabase.swift

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,13 +188,102 @@ internal class SQLiteDatabase: SQLDatabase {
188188
}
189189
}
190190

191+
// MARK: - Backups
192+
193+
extension SQLiteDatabase {
194+
195+
/// Store a backup of the contents of the current database to a new SQLite DB file at path.
196+
func storeBackup(to path: String, vacuum: Bool) throws {
197+
198+
if vacuum {
199+
// This is slower than the backup API, but results in smaller file.
200+
if FileManager.default.fileExists(atPath: path) {
201+
// File must be removed first if it exists.
202+
try FileManager.default.removeItem(atPath: path)
203+
}
204+
try execute(sql: "VACUUM main INTO ?;", values: .text(path))
205+
} else {
206+
// Use backup API. This is faster, but the file is bigger.
207+
var backupDB: SQLDatabaseID? = nil
208+
209+
let result = sqlite3_open(path, &backupDB)
210+
211+
guard result == SQLITE_OK, let backupDB else {
212+
var message: String = "Failed to open backup database at \(path)"
213+
if let backupDB {
214+
message = String(cString: sqlite3_errmsg(backupDB))
215+
sqlite3_close(backupDB)
216+
}
217+
throw SQLError.failedToOpenDatabase(code: result, message: message)
218+
}
219+
220+
let backup = sqlite3_backup_init(backupDB, "main", db, "main")
221+
if backup != nil {
222+
sqlite3_backup_step(backup, -1)
223+
sqlite3_backup_finish(backup)
224+
225+
}
226+
let backupResult = sqlite3_errcode(backupDB)
227+
guard backupResult == SQLITE_OK else {
228+
var message: String = "Failed to make backup"
229+
if let cMessage = sqlite3_errmsg(backupDB) {
230+
message = String(cString: cMessage)
231+
}
232+
sqlite3_close(backupDB)
233+
throw SQLError.failedToOpenDatabase(code: backupResult, message: message)
234+
}
235+
236+
sqlite3_close(backupDB)
237+
}
238+
}
239+
240+
func restoreBackup(from path: String) throws {
241+
var backupDB: SQLDatabaseID? = nil
242+
243+
let result = sqlite3_open(path, &backupDB)
244+
245+
guard result == SQLITE_OK, let backupDB else {
246+
var message: String = "Failed to open backup database at \(path)"
247+
if let backupDB {
248+
if let cMessage = sqlite3_errmsg(backupDB) {
249+
message = String(cString: cMessage)
250+
}
251+
sqlite3_close(backupDB)
252+
}
253+
throw SQLError.failedToOpenDatabase(code: result, message: message)
254+
}
255+
256+
let backup = sqlite3_backup_init(db, "main", backupDB, "main")
257+
if backup != nil {
258+
sqlite3_backup_step(backup, -1)
259+
sqlite3_backup_finish(backup)
260+
261+
}
262+
let backupResult = sqlite3_errcode(db)
263+
guard backupResult == SQLITE_OK else {
264+
var message: String = "Failed to make backup"
265+
if let cMessage = sqlite3_errmsg(db) {
266+
message = String(cString: cMessage)
267+
}
268+
sqlite3_close(backupDB)
269+
throw SQLError.failedToOpenDatabase(code: backupResult, message: message)
270+
}
271+
272+
sqlite3_close(backupDB)
273+
}
274+
}
275+
276+
// MARK: - SQLErrorMessage
277+
191278
extension SQLiteDatabase: SQLErrorMessage {
192279

193280
var current: String {
194281
return errorMessage
195282
}
196283
}
197284

285+
// MARK: - UpdateHook
286+
198287
private class UpdateHook {
199288
let hook: (SQLUpdateType, _ table: String?, _ rowID: Int64) -> Void
200289

0 commit comments

Comments
 (0)