Skip to content

Commit df66614

Browse files
author
Erik Soehnel
committed
Add updateHook
A wrapper around sqlite3_update_hook. For now only as a low-level operation to Database. To be useful in projects it will probably need some wrapping in the worker but right now I have no idea yet how that should look.
1 parent 436a880 commit df66614

File tree

3 files changed

+125
-1
lines changed

3 files changed

+125
-1
lines changed

src/api.js

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,10 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() {
7171
var SQLITE_BLOB = 4;
7272
// var - Encodings, used for registering functions.
7373
var SQLITE_UTF8 = 1;
74+
// var - Authorizer Action Codes used to identify change types in updateHook
75+
var SQLITE_INSERT = 18;
76+
var SQLITE_UPDATE = 23;
77+
var SQLITE_DELETE = 9;
7478
// var - cwrap function
7579
var sqlite3_open = cwrap("sqlite3_open", "number", ["string", "number"]);
7680
var sqlite3_close_v2 = cwrap("sqlite3_close_v2", "number", ["number"]);
@@ -239,6 +243,12 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() {
239243
["number"]
240244
);
241245

246+
var sqlite3_update_hook = cwrap(
247+
"sqlite3_update_hook",
248+
"number",
249+
["number", "number", "number"]
250+
);
251+
242252
/**
243253
* @classdesc
244254
* Represents a prepared statement.
@@ -1383,6 +1393,76 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() {
13831393
return this;
13841394
};
13851395

1396+
/** Registers the update hook with SQLite
1397+
@param {function(operation, tableName, rowId)} callback
1398+
executed whenever a row in any rowid table is changed
1399+
1400+
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.
1403+
1404+
rowid is cast to a plain number, if it exceeds Number.MAX_SAFE_INTEGER
1405+
an error will be thrown.
1406+
1407+
The callback MUST NOT modify the database in any way.
1408+
1409+
Only a single callback can be registered. Unregister the callback by
1410+
passing an empty function.
1411+
1412+
Not called for some updates like ON REPLACE CONFLICT and TRUNCATE.
1413+
1414+
See sqlite docs on sqlite3_update_hook for more details.
1415+
*/
1416+
Database.prototype["updateHook"] = function updateHook(callback) {
1417+
this.updateHookCallback = callback;
1418+
1419+
// void(*)(void *,int ,char const *,char const *,sqlite3_int64)
1420+
function wrappedCallback(
1421+
ignored,
1422+
operationCode,
1423+
databaseNamePtr,
1424+
tableNamePtr,
1425+
rowIdBigInt
1426+
) {
1427+
var operation;
1428+
1429+
switch (operationCode) {
1430+
case SQLITE_INSERT:
1431+
operation = "insert";
1432+
break;
1433+
case SQLITE_UPDATE:
1434+
operation = "update";
1435+
break;
1436+
case SQLITE_DELETE:
1437+
operation = "delete";
1438+
break;
1439+
default:
1440+
throw "unknown operationCode in updateHook callback: "
1441+
+ operationCode;
1442+
}
1443+
1444+
var tableName = UTF8ToString(tableNamePtr);
1445+
1446+
if (rowIdBigInt > Number.MAX_SAFE_INTEGER) {
1447+
throw "rowId too big to fit inside a Number";
1448+
}
1449+
1450+
var rowId = Number(rowIdBigInt);
1451+
1452+
if (this.updateHookCallback) {
1453+
this.updateHookCallback(operation, tableName, rowId);
1454+
}
1455+
}
1456+
1457+
var funcPtr = addFunction(wrappedCallback.bind(this), "viiiij");
1458+
1459+
this.handleError(sqlite3_update_hook(
1460+
this.db,
1461+
funcPtr,
1462+
0 // passed as the first arg to wrappedCallback
1463+
));
1464+
};
1465+
13861466
// export Database to Module
13871467
Module.Database = Database;
13881468
};

src/exported_functions.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,5 +42,6 @@
4242
"_sqlite3_result_int64",
4343
"_sqlite3_result_error",
4444
"_sqlite3_aggregate_context",
45-
"_RegisterExtensionFunctions"
45+
"_RegisterExtensionFunctions",
46+
"_sqlite3_update_hook"
4647
]

test/test_update_hook.js

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
exports.test = function(SQL, assert){
2+
var db = new SQL.Database();
3+
4+
db.exec(
5+
"CREATE TABLE consoles (id INTEGER PRIMARY KEY, company TEXT, name TEXT);" +
6+
"INSERT INTO consoles VALUES (1, 'Sony', 'Playstation');" +
7+
"INSERT INTO consoles VALUES (2, 'Microsoft', 'Xbox');"
8+
);
9+
10+
// {operation: undefined, tableName: undefined, rowId: undefined};
11+
var updateHookCalls = []
12+
13+
db.updateHook(function(operation, tableName, rowId) {
14+
updateHookCalls.push({operation, tableName, rowId});
15+
});
16+
17+
// INSERT
18+
db.exec("INSERT INTO consoles VALUES (3, 'Sega', 'Saturn');");
19+
20+
assert.deepEqual(updateHookCalls, [{operation: 'insert', tableName: 'consoles', rowId: 3}], 'insert a single row');
21+
updateHookCalls = []
22+
23+
// UPDATE
24+
db.exec("UPDATE consoles SET name = 'Playstation 5' WHERE id = 1");
25+
26+
assert.deepEqual(updateHookCalls, [{operation: 'update', tableName: 'consoles', rowId: 1}], 'update a single row');
27+
updateHookCalls = []
28+
29+
// UPDATE (multiple rows)
30+
db.exec("UPDATE consoles SET name = name + ' [legacy]' WHERE id IN (2,3)");
31+
32+
assert.deepEqual(updateHookCalls, [
33+
{operation: 'update', tableName: 'consoles', rowId: 2},
34+
{operation: 'update', tableName: 'consoles', rowId: 3},
35+
], 'update two rows');
36+
updateHookCalls = []
37+
38+
// DELETE
39+
db.exec("DELETE FROM consoles WHERE company = 'Sega'");
40+
41+
assert.deepEqual(updateHookCalls, [{operation: 'delete', tableName: 'consoles', rowId: 3}], 'delete a single row');
42+
updateHookCalls = []
43+
}

0 commit comments

Comments
 (0)