Skip to content

Commit 6abd3f4

Browse files
committed
Update, commit, and rollback hook support
Should make it easy, e.g., to create an interface for a table view to register for updates. Signed-off-by: Stephen Celis <stephen@stephencelis.com>
1 parent 540df59 commit 6abd3f4

File tree

4 files changed

+174
-0
lines changed

4 files changed

+174
-0
lines changed

SQLite Tests/DatabaseTests.swift

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,64 @@ class DatabaseTests: SQLiteTestCase {
292292
XCTAssertEqual(true, db.foreignKeys)
293293
}
294294

295+
func test_updateHook_setsUpdateHook() {
296+
let updateHook = expectationWithDescription("updateHook")
297+
db.updateHook { operation, db, table, rowid in
298+
XCTAssertEqual(.Insert, operation)
299+
XCTAssertEqual("main", db)
300+
XCTAssertEqual("users", table)
301+
XCTAssertEqual(1, rowid)
302+
updateHook.fulfill()
303+
}
304+
executeAndWait {
305+
insertUser("alice")
306+
}
307+
}
308+
309+
func test_commitHook_setsCommitHook() {
310+
let commitHook = expectationWithDescription("commitHook")
311+
db.commitHook {
312+
commitHook.fulfill()
313+
return .Commit
314+
}
315+
executeAndWait {
316+
self.db.transaction { _ in
317+
self.insertUser("alice")
318+
return .Commit
319+
}
320+
XCTAssertEqual(1, self.db.scalar("SELECT count(*) FROM users") as! Int64)
321+
}
322+
}
323+
324+
func test_rollbackHook_setsRollbackHook() {
325+
let rollbackHook = expectationWithDescription("commitHook")
326+
db.rollbackHook {
327+
rollbackHook.fulfill()
328+
}
329+
executeAndWait {
330+
self.db.transaction { _ in
331+
self.insertUser("alice")
332+
return .Rollback
333+
}
334+
XCTAssertEqual(0, self.db.scalar("SELECT count(*) FROM users") as! Int64)
335+
}
336+
}
337+
338+
func test_commitHook_withRollback_rollsBack() {
339+
let rollbackHook = expectationWithDescription("commitHook")
340+
db.commitHook { .Rollback }
341+
db.rollbackHook {
342+
rollbackHook.fulfill()
343+
}
344+
executeAndWait {
345+
self.db.transaction { _ in
346+
self.insertUser("alice")
347+
return .Commit
348+
}
349+
XCTAssertEqual(0, self.db.scalar("SELECT count(*) FROM users") as! Int64)
350+
}
351+
}
352+
295353
func test_createFunction_withArrayArguments() {
296354
db.create(function: "hello") { $0[0].map { "Hello, \($0)!" } }
297355

@@ -320,4 +378,13 @@ class DatabaseTests: SQLiteTestCase {
320378
XCTAssertEqual(1, db.scalar("SELECT ? = ? COLLATE \"NO DIACRITIC\"", "cafe", "café") as! Int64)
321379
}
322380

381+
func executeAndWait(block: () -> ()) {
382+
dispatch_async(dispatch_get_main_queue(), block)
383+
waitForExpectationsWithTimeout(5) { error in
384+
if let error = error {
385+
fatalError(error.localizedDescription)
386+
}
387+
}
388+
}
389+
323390
}

SQLite/Database.swift

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,80 @@ public final class Database {
404404
}
405405
private var trace: SQLiteTraceCallback?
406406

407+
/// A SQL operation passed to update callbacks.
408+
public enum Operation {
409+
410+
/// An INSERT operation.
411+
case Insert
412+
413+
/// An UPDATE operation.
414+
case Update
415+
416+
/// A DELETE operation.
417+
case Delete
418+
419+
private static func fromRawValue(rawValue: Int32) -> Operation {
420+
switch rawValue {
421+
case SQLITE_INSERT:
422+
return .Insert
423+
case SQLITE_UPDATE:
424+
return .Update
425+
case SQLITE_DELETE:
426+
return .Delete
427+
default:
428+
fatalError("unhandled operation code: \(rawValue)")
429+
}
430+
}
431+
432+
}
433+
434+
/// Registers a callback to be invoked whenever a row is inserted, updated,
435+
/// or deleted in a rowid table.
436+
///
437+
/// :param: callback A callback invoked with the `Operation` (one
438+
/// of `.Insert`, `.Update`, or `.Delete`), database name,
439+
/// table name, and rowid.
440+
public func updateHook(callback: ((operation: Operation, db: String, table: String, rowid: Int64) -> ())?) {
441+
if let callback = callback {
442+
updateHook = { operation, db, table, rowid in
443+
callback(
444+
operation: .fromRawValue(operation),
445+
db: String.fromCString(db)!,
446+
table: String.fromCString(table)!,
447+
rowid: rowid
448+
)
449+
}
450+
} else {
451+
updateHook = nil
452+
}
453+
SQLiteUpdateHook(handle, updateHook)
454+
}
455+
private var updateHook: SQLiteUpdateHookCallback?
456+
457+
/// Registers a callback to be invoked whenever a transaction is committed.
458+
///
459+
/// :param: callback A callback that must return `.Commit` or `.Rollback` to
460+
/// determine whether a transaction should be committed or
461+
/// not.
462+
public func commitHook(callback: (() -> TransactionResult)?) {
463+
if let callback = callback {
464+
commitHook = { callback() == .Commit ? 0 : 1 }
465+
} else {
466+
commitHook = nil
467+
}
468+
SQLiteCommitHook(handle, commitHook)
469+
}
470+
private var commitHook: SQLiteCommitHookCallback?
471+
472+
/// Registers a callback to be invoked whenever a transaction rolls back.
473+
///
474+
/// :param: callback A callback invoked when a transaction is rolled back.
475+
public func rollbackHook(callback: (() -> ())?) {
476+
rollbackHook = callback.map { $0 }
477+
SQLiteRollbackHook(handle, rollbackHook)
478+
}
479+
private var rollbackHook: SQLiteRollbackHookCallback?
480+
407481
/// Creates or redefines a custom SQL function.
408482
///
409483
/// :param: function The name of the function to create or redefine.

SQLite/SQLite-Bridging.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,15 @@ int SQLiteBusyHandler(sqlite3 * handle, SQLiteBusyHandlerCallback callback);
3131
typedef void (^SQLiteTraceCallback)(const char * SQL);
3232
void SQLiteTrace(sqlite3 * handle, SQLiteTraceCallback callback);
3333

34+
typedef void (^SQLiteUpdateHookCallback)(int operation, const char * db, const char * table, sqlite3_int64 rowid);
35+
void SQLiteUpdateHook(sqlite3 * handle, SQLiteUpdateHookCallback callback);
36+
37+
typedef int (^SQLiteCommitHookCallback)();
38+
void SQLiteCommitHook(sqlite3 * handle, SQLiteCommitHookCallback callback);
39+
40+
typedef void (^SQLiteRollbackHookCallback)();
41+
void SQLiteRollbackHook(sqlite3 * handle, SQLiteRollbackHookCallback callback);
42+
3443
typedef void (^SQLiteCreateFunctionCallback)(sqlite3_context * context, int argc, sqlite3_value ** argv);
3544
int SQLiteCreateFunction(sqlite3 * handle, const char * name, int argc, int deterministic, SQLiteCreateFunctionCallback callback);
3645

SQLite/SQLite-Bridging.m

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,30 @@ void SQLiteTrace(sqlite3 * handle, SQLiteTraceCallback callback) {
5050
}
5151
}
5252

53+
static void _SQLiteUpdateHook(void * context, int operation, const char * db, const char * table, sqlite3_int64 rowid) {
54+
((__bridge SQLiteUpdateHookCallback)context)(operation, db, table, rowid);
55+
}
56+
57+
void SQLiteUpdateHook(sqlite3 * handle, SQLiteUpdateHookCallback callback) {
58+
sqlite3_update_hook(handle, _SQLiteUpdateHook, (__bridge void *)callback);
59+
}
60+
61+
static int _SQLiteCommitHook(void * context) {
62+
return ((__bridge SQLiteCommitHookCallback)context)();
63+
}
64+
65+
void SQLiteCommitHook(sqlite3 * handle, SQLiteCommitHookCallback callback) {
66+
sqlite3_commit_hook(handle, _SQLiteCommitHook, (__bridge void *)callback);
67+
}
68+
69+
static void _SQLiteRollbackHook(void * context) {
70+
((__bridge SQLiteRollbackHookCallback)context)();
71+
}
72+
73+
void SQLiteRollbackHook(sqlite3 * handle, SQLiteRollbackHookCallback callback) {
74+
sqlite3_rollback_hook(handle, _SQLiteRollbackHook, (__bridge void *)callback);
75+
}
76+
5377
static void _SQLiteCreateFunction(sqlite3_context * context, int argc, sqlite3_value ** argv) {
5478
((__bridge SQLiteCreateFunctionCallback)sqlite3_user_data(context))(context, argc, argv);
5579
}

0 commit comments

Comments
 (0)