Skip to content

Commit

Permalink
Merge pull request #225 from davidisaaclee/commit-hook
Browse files Browse the repository at this point in the history
Expose commit hook
  • Loading branch information
rhashimoto authored Nov 6, 2024
2 parents ca1c304 + dc9b8a1 commit 605100a
Show file tree
Hide file tree
Showing 12 changed files with 215 additions and 5 deletions.
2 changes: 1 addition & 1 deletion dist/wa-sqlite-async.mjs

Large diffs are not rendered by default.

Binary file modified dist/wa-sqlite-async.wasm
Binary file not shown.
2 changes: 1 addition & 1 deletion dist/wa-sqlite-jspi.mjs

Large diffs are not rendered by default.

Binary file modified dist/wa-sqlite-jspi.wasm
Binary file not shown.
2 changes: 1 addition & 1 deletion dist/wa-sqlite.mjs

Large diffs are not rendered by default.

Binary file modified dist/wa-sqlite.wasm
Binary file not shown.
2 changes: 1 addition & 1 deletion src/libadapters.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Method names for these signatures must be in src/asyncify_imports.json.
const SIGNATURES = [
'ipp', // xProgress
'ipp', // xProgress, xCommitHook
'ippp', // xClose, xSectorSize, xDeviceCharacteristics
'vppp', // xShmBarrier, xFinal
'ipppj', // xTruncate
Expand Down
9 changes: 9 additions & 0 deletions src/libhook.c
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@
SIGNATURE##_async(KEY, __VA_ARGS__) : \
SIGNATURE(KEY, __VA_ARGS__))

static int libhook_xCommitHook(void* pApp) {
const int asyncFlags = pApp ? *(int *)pApp : 0;
return CALL_JS(ipp, pApp, pApp);
}

static void libhook_xUpdateHook(
void* pApp,
int iUpdateType,
Expand All @@ -22,6 +27,10 @@ static void libhook_xUpdateHook(
CALL_JS(vppippii, pApp, pApp, iUpdateType, dbName, tblName, lo32, hi32);
}

void EMSCRIPTEN_KEEPALIVE libhook_commit_hook(sqlite3* db, int xCommitHook, void* pApp) {
sqlite3_commit_hook(db, xCommitHook ? &libhook_xCommitHook : NULL, pApp);
}

void EMSCRIPTEN_KEEPALIVE libhook_update_hook(sqlite3* db, int xUpdateHook, void* pApp) {
sqlite3_update_hook(db, xUpdateHook ? &libhook_xUpdateHook : NULL, pApp);
}
29 changes: 28 additions & 1 deletion src/libhook.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,31 @@
});
}
};
})();
})();

(function() {
const AsyncFunction = Object.getPrototypeOf(async function(){}).constructor;
let pAsyncFlags = 0;

Module['commit_hook'] = function(db, xCommitHook) {
if (pAsyncFlags) {
Module['deleteCallback'](pAsyncFlags);
Module['_sqlite3_free'](pAsyncFlags);
pAsyncFlags = 0;
}

pAsyncFlags = Module['_sqlite3_malloc'](4);
setValue(pAsyncFlags, xCommitHook instanceof AsyncFunction ? 1 : 0, 'i32');

ccall(
'libhook_commit_hook',
'void',
['number', 'number', 'number'],
[db, xCommitHook ? 1 : 0, pAsyncFlags]);
if (xCommitHook) {
Module['setCallback'](pAsyncFlags, (_) => {
return xCommitHook();
});
}
};
})();
5 changes: 5 additions & 0 deletions src/sqlite-api.js
Original file line number Diff line number Diff line change
Expand Up @@ -735,6 +735,11 @@ export function Factory(Module) {
};
})();

sqlite3.commit_hook = function(db, xCommitHook) {
verifyDatabase(db);
Module.commit_hook(db, xCommitHook);
};

sqlite3.update_hook = function(db, xUpdateHook) {
verifyDatabase(db);

Expand Down
13 changes: 13 additions & 0 deletions src/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,19 @@ declare interface SQLiteAPI {
*/
column_type(stmt: number, i: number): number;

/**
* Register a commit hook
*
* @see https://www.sqlite.org/c3ref/commit_hook.html
*
* @param db database pointer
* @param callback If a non-zero value is returned, commit is converted into
* a rollback; disables callback when null
*/
commit_hook(
db: number,
callback: (() => number) | null): void;

/**
* Create or redefine SQL functions
*
Expand Down
156 changes: 156 additions & 0 deletions test/callbacks.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -422,4 +422,160 @@ for (const [key, factory] of FACTORIES) {
expect(calls).toEqual([[23, "main", "t", 1n]]);
});
});

describe(`${key} commit_hook`, function() {
let db;
beforeEach(async function() {
db = await sqlite3.open_v2(':memory:');
});

afterEach(async function() {
await sqlite3.close(db);
});

it('should call commit hook', async function() {
let rc;

let callsCount = 0;
const resetCallsCount = () => callsCount = 0;

sqlite3.commit_hook(db, () => {
callsCount++;
return 0;
});
expect(callsCount).toEqual(0);
resetCallsCount();

rc = await sqlite3.exec(db, `
CREATE TABLE t(i integer primary key, x);
`);
expect(rc).toEqual(SQLite.SQLITE_OK);
expect(callsCount).toEqual(1);
resetCallsCount();

rc = await sqlite3.exec(db, `
SELECT * FROM t;
`);
expect(callsCount).toEqual(0);
resetCallsCount();

rc = await sqlite3.exec(db, `
BEGIN TRANSACTION;
INSERT INTO t VALUES (1, 'foo');
ROLLBACK;
`);
expect(callsCount).toEqual(0);
resetCallsCount();

rc = await sqlite3.exec(db, `
BEGIN TRANSACTION;
INSERT INTO t VALUES (1, 'foo');
INSERT INTO t VALUES (2, 'bar');
COMMIT;
`);
expect(callsCount).toEqual(1);
resetCallsCount();
});

it('can change commit hook', async function() {
let rc;
rc = await sqlite3.exec(db, `
CREATE TABLE t(i integer primary key, x);
`);
expect(rc).toEqual(SQLite.SQLITE_OK);

let a = 0;
let b = 0;

// set hook to increment `a` on commit
sqlite3.commit_hook(db, () => {
a++;
return 0;
});
rc = await sqlite3.exec(db, `
INSERT INTO t VALUES (1, 'foo');
`);
expect(a).toEqual(1);
expect(b).toEqual(0);

// switch to increment `b`
sqlite3.commit_hook(db, () => {
b++;
return 0;
});

rc = await sqlite3.exec(db, `
INSERT INTO t VALUES (2, 'bar');
`);
expect(rc).toEqual(SQLite.SQLITE_OK);
expect(a).toEqual(1);
expect(b).toEqual(1);

// disable hook by passing null
sqlite3.commit_hook(db, null);

rc = await sqlite3.exec(db, `
INSERT INTO t VALUES (3, 'qux');
`);
expect(rc).toEqual(SQLite.SQLITE_OK);
expect(a).toEqual(1);
expect(b).toEqual(1);
});

it('can rollback based on return value', async function() {
let rc;
rc = await sqlite3.exec(db, `
CREATE TABLE t(i integer primary key, x);
`);
expect(rc).toEqual(SQLite.SQLITE_OK);

// accept commit by returning 0
sqlite3.commit_hook(db, () => 0);
rc = await sqlite3.exec(db, `
INSERT INTO t VALUES (1, 'foo');
`);
expect(rc).toEqual(SQLite.SQLITE_OK);

// reject commit by returning 1, causing rollback
sqlite3.commit_hook(db, () => 1);
await expectAsync(
sqlite3.exec(db, `INSERT INTO t VALUES (2, 'bar');`)
).toBeRejected();

// double-check that the insert was rolled back
let hasRow = false;
rc = await sqlite3.exec(db, `
SELECT * FROM t WHERE i = 2;
`, () => hasRow = true);
expect(rc).toEqual(SQLite.SQLITE_OK);
expect(hasRow).toBeFalse();
});

it('does not overwrite update_hook', async function() {
let rc;
rc = await sqlite3.exec(db, `
CREATE TABLE t(i integer primary key, x);
`);
expect(rc).toEqual(SQLite.SQLITE_OK);

let updateHookInvocationsCount = 0;
sqlite3.update_hook(db, (...args) => {
updateHookInvocationsCount++;
});

let commitHookInvocationsCount = 0;
sqlite3.commit_hook(db, () => {
commitHookInvocationsCount++;
return 0;
});

rc = await sqlite3.exec(db, `
INSERT INTO t VALUES (1, 'foo');
`);
expect(rc).toEqual(SQLite.SQLITE_OK);

expect(updateHookInvocationsCount).toEqual(1);
expect(commitHookInvocationsCount).toEqual(1);
});
});
}

0 comments on commit 605100a

Please sign in to comment.