Skip to content

Commit f3cff2b

Browse files
author
Erik Soehnel
committed
Allow removing the updateHook callback
Also release the callback function when the callback is removed or the database is closed. Include the previously omitted database name in the callback args as the sqlite callback does.
1 parent 4c9602a commit f3cff2b

File tree

2 files changed

+71
-25
lines changed

2 files changed

+71
-25
lines changed

src/api.js

+30-13
Original file line numberDiff line numberDiff line change
@@ -1124,6 +1124,12 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() {
11241124
});
11251125
Object.values(this.functions).forEach(removeFunction);
11261126
this.functions = {};
1127+
1128+
if (this.updateHookFunctionPtr) {
1129+
removeFunction(this.updateHookFunctionPtr);
1130+
this.updateHookFunctionPtr = undefined;
1131+
}
1132+
11271133
this.handleError(sqlite3_close_v2(this.db));
11281134
FS.unlink("/" + this.filename);
11291135
this.db = null;
@@ -1394,27 +1400,39 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() {
13941400
};
13951401

13961402
/** Registers the update hook with SQLite
1397-
@param {function(operation, tableName, rowId)} callback
1403+
@param {function(operation, database, table, rowId) | null} callback
13981404
executed whenever a row in any rowid table is changed
13991405
14001406
For each changed row, the callback is called once with the change
1401-
('insert', 'update' or 'delete'), the table name where the change
1402-
happened and the rowid of the row that has been changed.
1407+
('insert', 'update' or 'delete'), the database name and table name
1408+
where the change happened and the rowid of the row that has been
1409+
changed.
14031410
14041411
rowid is cast to a plain number, if it exceeds Number.MAX_SAFE_INTEGER
14051412
an error will be thrown.
14061413
14071414
The callback MUST NOT modify the database in any way.
14081415
14091416
Only a single callback can be registered. Unregister the callback by
1410-
passing an empty function.
1417+
passing null.
14111418
1412-
Not called for some updates like ON REPLACE CONFLICT and TRUNCATE.
1419+
Not called for some updates like ON REPLACE CONFLICT and TRUNCATE (a
1420+
DELETE FROM without a WHERE clause).
14131421
14141422
See sqlite docs on sqlite3_update_hook for more details.
14151423
*/
14161424
Database.prototype["updateHook"] = function updateHook(callback) {
1417-
this.updateHookCallback = callback;
1425+
if (this.updateHookFunctionPtr) {
1426+
// unregister and cleanup a previously registered update hook
1427+
sqlite3_update_hook(this.db, 0, 0);
1428+
removeFunction(this.updateHookFunctionPtr);
1429+
this.updateHookFunctionPtr = undefined;
1430+
}
1431+
1432+
if (!callback) {
1433+
// no new callback to register
1434+
return;
1435+
}
14181436

14191437
// void(*)(void *,int ,char const *,char const *,sqlite3_int64)
14201438
function wrappedCallback(
@@ -1441,6 +1459,7 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() {
14411459
+ operationCode;
14421460
}
14431461

1462+
var databaseName = UTF8ToString(databaseNamePtr);
14441463
var tableName = UTF8ToString(tableNamePtr);
14451464

14461465
if (rowIdBigInt > Number.MAX_SAFE_INTEGER) {
@@ -1449,18 +1468,16 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() {
14491468

14501469
var rowId = Number(rowIdBigInt);
14511470

1452-
if (this.updateHookCallback) {
1453-
this.updateHookCallback(operation, tableName, rowId);
1454-
}
1471+
callback(operation, databaseName, tableName, rowId);
14551472
}
14561473

1457-
var funcPtr = addFunction(wrappedCallback.bind(this), "viiiij");
1474+
this.updateHookFunctionPtr = addFunction(wrappedCallback, "viiiij");
14581475

1459-
this.handleError(sqlite3_update_hook(
1476+
sqlite3_update_hook(
14601477
this.db,
1461-
funcPtr,
1478+
this.updateHookFunctionPtr,
14621479
0 // passed as the first arg to wrappedCallback
1463-
));
1480+
);
14641481
};
14651482

14661483
// export Database to Module

test/test_update_hook.js

+41-12
Original file line numberDiff line numberDiff line change
@@ -7,37 +7,66 @@ exports.test = function(SQL, assert){
77
"INSERT INTO consoles VALUES (2, 'Microsoft', 'Xbox');"
88
);
99

10-
// {operation: undefined, tableName: undefined, rowId: undefined};
10+
// {operation: undefined, databaseName: undefined, tableName: undefined, rowId: undefined};
1111
var updateHookCalls = []
1212

13-
db.updateHook(function(operation, tableName, rowId) {
14-
updateHookCalls.push({operation, tableName, rowId});
13+
db.updateHook(function(operation, databaseName, tableName, rowId) {
14+
updateHookCalls.push({operation, databaseName, tableName, rowId});
1515
});
1616

1717
// INSERT
1818
db.exec("INSERT INTO consoles VALUES (3, 'Sega', 'Saturn');");
1919

20-
assert.deepEqual(updateHookCalls, [{operation: 'insert', tableName: 'consoles', rowId: 3}], 'insert a single row');
21-
updateHookCalls = []
20+
assert.deepEqual(updateHookCalls, [
21+
{operation: "insert", databaseName: "main", tableName: "consoles", rowId: 3}
22+
], "insert a single row");
2223

2324
// UPDATE
25+
updateHookCalls = []
2426
db.exec("UPDATE consoles SET name = 'Playstation 5' WHERE id = 1");
2527

26-
assert.deepEqual(updateHookCalls, [{operation: 'update', tableName: 'consoles', rowId: 1}], 'update a single row');
27-
updateHookCalls = []
28+
assert.deepEqual(updateHookCalls, [
29+
{operation: "update", databaseName: "main", tableName: "consoles", rowId: 1}
30+
], "update a single row");
2831

2932
// UPDATE (multiple rows)
33+
updateHookCalls = []
3034
db.exec("UPDATE consoles SET name = name + ' [legacy]' WHERE id IN (2,3)");
3135

3236
assert.deepEqual(updateHookCalls, [
33-
{operation: 'update', tableName: 'consoles', rowId: 2},
34-
{operation: 'update', tableName: 'consoles', rowId: 3},
35-
], 'update two rows');
36-
updateHookCalls = []
37+
{operation: "update", databaseName: "main", tableName: "consoles", rowId: 2},
38+
{operation: "update", databaseName: "main", tableName: "consoles", rowId: 3},
39+
], "update two rows");
3740

3841
// DELETE
42+
updateHookCalls = []
3943
db.exec("DELETE FROM consoles WHERE company = 'Sega'");
4044

41-
assert.deepEqual(updateHookCalls, [{operation: 'delete', tableName: 'consoles', rowId: 3}], 'delete a single row');
45+
assert.deepEqual(updateHookCalls, [
46+
{operation: "delete", databaseName: "main", tableName: "consoles", rowId: 3}
47+
], "delete a single row");
48+
49+
// UNREGISTER
4250
updateHookCalls = []
51+
52+
db.updateHook(null);
53+
54+
db.exec("DELETE FROM consoles WHERE company = 'Microsoft'");
55+
56+
assert.deepEqual(updateHookCalls, [], "unregister the update hook");
57+
58+
// REGISTER AGAIN
59+
updateHookCalls = []
60+
61+
db.updateHook(function(operation, databaseName, tableName, rowId) {
62+
updateHookCalls.push({operation, databaseName, tableName, rowId});
63+
});
64+
65+
// need a where clause, just running "DELETE FROM consoles" would result in
66+
// a TRUNCATE and not yield any update hook callbacks
67+
db.exec("DELETE FROM consoles WHERE id > 0");
68+
69+
assert.deepEqual(updateHookCalls, [
70+
{operation: 'delete', databaseName: 'main', tableName: 'consoles', rowId: 1}
71+
], "register the update hook again");
4372
}

0 commit comments

Comments
 (0)