Skip to content

Commit 2b4439b

Browse files
committed
Expose commit hook
1 parent ca1c304 commit 2b4439b

File tree

5 files changed

+167
-2
lines changed

5 files changed

+167
-2
lines changed

src/libadapters.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// Method names for these signatures must be in src/asyncify_imports.json.
22
const SIGNATURES = [
3-
'ipp', // xProgress
3+
'ipp', // xProgress, xCommitHook
44
'ippp', // xClose, xSectorSize, xDeviceCharacteristics
55
'vppp', // xShmBarrier, xFinal
66
'ipppj', // xTruncate

src/libhook.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@
1010
SIGNATURE##_async(KEY, __VA_ARGS__) : \
1111
SIGNATURE(KEY, __VA_ARGS__))
1212

13+
static int libhook_xCommitHook(void* pApp) {
14+
const int asyncFlags = pApp ? *(int *)pApp : 0;
15+
return CALL_JS(ipp, pApp, pApp);
16+
}
17+
1318
static void libhook_xUpdateHook(
1419
void* pApp,
1520
int iUpdateType,
@@ -22,6 +27,10 @@ static void libhook_xUpdateHook(
2227
CALL_JS(vppippii, pApp, pApp, iUpdateType, dbName, tblName, lo32, hi32);
2328
}
2429

30+
void EMSCRIPTEN_KEEPALIVE libhook_commit_hook(sqlite3* db, int xCommitHook, void* pApp) {
31+
sqlite3_commit_hook(db, xCommitHook ? &libhook_xCommitHook : NULL, pApp);
32+
}
33+
2534
void EMSCRIPTEN_KEEPALIVE libhook_update_hook(sqlite3* db, int xUpdateHook, void* pApp) {
2635
sqlite3_update_hook(db, xUpdateHook ? &libhook_xUpdateHook : NULL, pApp);
2736
}

src/libhook.js

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,26 @@
2626
});
2727
}
2828
};
29-
})();
29+
30+
Module['commit_hook'] = function(db, xCommitHook) {
31+
if (pAsyncFlags) {
32+
Module['deleteCallback'](pAsyncFlags);
33+
Module['_sqlite3_free'](pAsyncFlags);
34+
pAsyncFlags = 0;
35+
}
36+
37+
pAsyncFlags = Module['_sqlite3_malloc'](4);
38+
setValue(pAsyncFlags, xCommitHook instanceof AsyncFunction ? 1 : 0, 'i32');
39+
40+
ccall(
41+
'libhook_commit_hook',
42+
'void',
43+
['number', 'number', 'number'],
44+
[db, xCommitHook ? 1 : 0, pAsyncFlags]);
45+
if (xCommitHook) {
46+
Module['setCallback'](pAsyncFlags, (_) => {
47+
return xCommitHook();
48+
});
49+
}
50+
};
51+
})();

src/sqlite-api.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -735,6 +735,11 @@ export function Factory(Module) {
735735
};
736736
})();
737737

738+
sqlite3.commit_hook = function(db, xCommitHook) {
739+
verifyDatabase(db);
740+
Module.commit_hook(db, xCommitHook);
741+
};
742+
738743
sqlite3.update_hook = function(db, xUpdateHook) {
739744
verifyDatabase(db);
740745

test/callbacks.test.js

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,4 +422,133 @@ for (const [key, factory] of FACTORIES) {
422422
expect(calls).toEqual([[23, "main", "t", 1n]]);
423423
});
424424
});
425+
426+
describe(`${key} commit_hook`, function() {
427+
let db;
428+
beforeEach(async function() {
429+
db = await sqlite3.open_v2(':memory:');
430+
});
431+
432+
afterEach(async function() {
433+
await sqlite3.close(db);
434+
});
435+
436+
it('should call commit hook', async function() {
437+
let rc;
438+
439+
let callsCount = 0;
440+
const resetCallsCount = () => callsCount = 0;
441+
442+
sqlite3.commit_hook(db, () => {
443+
callsCount++;
444+
return 0;
445+
});
446+
expect(callsCount).toEqual(0);
447+
resetCallsCount();
448+
449+
rc = await sqlite3.exec(db, `
450+
CREATE TABLE t(i integer primary key, x);
451+
`);
452+
expect(rc).toEqual(SQLite.SQLITE_OK);
453+
expect(callsCount).toEqual(1);
454+
resetCallsCount();
455+
456+
rc = await sqlite3.exec(db, `
457+
SELECT * FROM t;
458+
`);
459+
expect(callsCount).toEqual(0);
460+
resetCallsCount();
461+
462+
rc = await sqlite3.exec(db, `
463+
BEGIN TRANSACTION;
464+
INSERT INTO t VALUES (1, 'foo');
465+
ROLLBACK;
466+
`);
467+
expect(callsCount).toEqual(0);
468+
resetCallsCount();
469+
470+
rc = await sqlite3.exec(db, `
471+
BEGIN TRANSACTION;
472+
INSERT INTO t VALUES (1, 'foo');
473+
INSERT INTO t VALUES (2, 'bar');
474+
COMMIT;
475+
`);
476+
expect(callsCount).toEqual(1);
477+
resetCallsCount();
478+
});
479+
480+
it('can change commit hook', async function() {
481+
let rc;
482+
rc = await sqlite3.exec(db, `
483+
CREATE TABLE t(i integer primary key, x);
484+
`);
485+
expect(rc).toEqual(SQLite.SQLITE_OK);
486+
487+
let a = 0;
488+
let b = 0;
489+
490+
// set hook to increment `a` on commit
491+
sqlite3.commit_hook(db, () => {
492+
a++;
493+
return 0;
494+
});
495+
rc = await sqlite3.exec(db, `
496+
INSERT INTO t VALUES (1, 'foo');
497+
`);
498+
expect(a).toEqual(1);
499+
expect(b).toEqual(0);
500+
501+
// switch to increment `b`
502+
sqlite3.commit_hook(db, () => {
503+
b++;
504+
return 0;
505+
});
506+
507+
rc = await sqlite3.exec(db, `
508+
INSERT INTO t VALUES (2, 'bar');
509+
`);
510+
expect(rc).toEqual(SQLite.SQLITE_OK);
511+
expect(a).toEqual(1);
512+
expect(b).toEqual(1);
513+
514+
// disable hook by passing null
515+
sqlite3.commit_hook(db, null);
516+
517+
rc = await sqlite3.exec(db, `
518+
INSERT INTO t VALUES (3, 'qux');
519+
`);
520+
expect(rc).toEqual(SQLite.SQLITE_OK);
521+
expect(a).toEqual(1);
522+
expect(b).toEqual(1);
523+
});
524+
525+
it('can rollback based on return value', async function() {
526+
let rc;
527+
rc = await sqlite3.exec(db, `
528+
CREATE TABLE t(i integer primary key, x);
529+
`);
530+
expect(rc).toEqual(SQLite.SQLITE_OK);
531+
532+
// accept commit by returning 0
533+
sqlite3.commit_hook(db, () => 0);
534+
rc = await sqlite3.exec(db, `
535+
INSERT INTO t VALUES (1, 'foo');
536+
`);
537+
expect(rc).toEqual(SQLite.SQLITE_OK);
538+
539+
// reject commit by returning 1, causing rollback
540+
sqlite3.commit_hook(db, () => 1);
541+
await expectAsync(
542+
sqlite3.exec(db, `INSERT INTO t VALUES (2, 'bar');`)
543+
).toBeRejected();
544+
545+
// double-check that the insert was rolled back
546+
let hasRow = false;
547+
rc = await sqlite3.exec(db, `
548+
SELECT * FROM t WHERE i = 2;
549+
`, () => hasRow = true);
550+
expect(rc).toEqual(SQLite.SQLITE_OK);
551+
expect(hasRow).toBeFalse();
552+
});
553+
});
425554
}

0 commit comments

Comments
 (0)