Skip to content

Commit 342afcd

Browse files
committed
sqlite: use diagnostic channel
1 parent 215c658 commit 342afcd

4 files changed

Lines changed: 108 additions & 139 deletions

File tree

doc/api/sqlite.md

Lines changed: 61 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -395,56 +395,6 @@ added:
395395
This method is used to create SQLite user-defined functions. This method is a
396396
wrapper around [`sqlite3_create_function_v2()`][].
397397

398-
### `database.setSqlTraceHook(hook)`
399-
400-
<!-- YAML
401-
added: REPLACEME
402-
-->
403-
404-
* `hook` {Function|null} The trace function to set, or `null` to clear
405-
the current hook.
406-
407-
Sets a hook that SQLite invokes for every SQL statement executed against the
408-
database. The hook receives the expanded SQL string (with bound parameter
409-
values substituted) as its only argument. If expansion fails, the source SQL
410-
with unsubstituted placeholders is passed instead.
411-
412-
This method is a wrapper around [`sqlite3_trace_v2()`][].
413-
414-
```cjs
415-
const { DatabaseSync } = require('node:sqlite');
416-
const db = new DatabaseSync(':memory:');
417-
418-
db.setSqlTraceHook((sql) => console.log(sql));
419-
420-
db.exec('CREATE TABLE t (x INTEGER)');
421-
// Logs: CREATE TABLE t (x INTEGER)
422-
423-
const stmt = db.prepare('INSERT INTO t VALUES (?)');
424-
stmt.run(42);
425-
// Logs: INSERT INTO t VALUES (42.0)
426-
427-
// Clear the hook
428-
db.setSqlTraceHook(null);
429-
```
430-
431-
```mjs
432-
import { DatabaseSync } from 'node:sqlite';
433-
const db = new DatabaseSync(':memory:');
434-
435-
db.setSqlTraceHook((sql) => console.log(sql));
436-
437-
db.exec('CREATE TABLE t (x INTEGER)');
438-
// Logs: CREATE TABLE t (x INTEGER)
439-
440-
const stmt = db.prepare('INSERT INTO t VALUES (?)');
441-
stmt.run(42);
442-
// Logs: INSERT INTO t VALUES (42.0)
443-
444-
// Clear the hook
445-
db.setSqlTraceHook(null);
446-
```
447-
448398
### `database.setAuthorizer(callback)`
449399

450400
<!-- YAML
@@ -1414,6 +1364,66 @@ const totalPagesTransferred = await backup(sourceDb, 'backup.db', {
14141364
console.log('Backup completed', totalPagesTransferred);
14151365
```
14161366

1367+
## Diagnostics channel
1368+
1369+
<!-- YAML
1370+
added: REPLACEME
1371+
-->
1372+
1373+
The `node:sqlite` module publishes SQL trace events on the
1374+
[`diagnostics_channel`][] channel `sqlite.db.query`. This allows subscribers
1375+
to observe every SQL statement executed against any `DatabaseSync` instance
1376+
without modifying the database code itself. Tracing is zero-cost when there
1377+
are no subscribers.
1378+
1379+
### Channel `sqlite.db.query`
1380+
1381+
The message published to this channel is a {string} containing the expanded
1382+
SQL with bound parameter values substituted. If expansion fails, the source
1383+
SQL with unsubstituted placeholders is used instead.
1384+
1385+
```cjs
1386+
const dc = require('node:diagnostics_channel');
1387+
const { DatabaseSync } = require('node:sqlite');
1388+
1389+
function onQuery(sql) {
1390+
console.log(sql);
1391+
}
1392+
1393+
dc.subscribe('sqlite.db.query', onQuery);
1394+
1395+
const db = new DatabaseSync(':memory:');
1396+
db.exec('CREATE TABLE t (x INTEGER)');
1397+
// Logs: CREATE TABLE t (x INTEGER)
1398+
1399+
const stmt = db.prepare('INSERT INTO t VALUES (?)');
1400+
stmt.run(42);
1401+
// Logs: INSERT INTO t VALUES (42.0)
1402+
1403+
dc.unsubscribe('sqlite.db.query', onQuery);
1404+
```
1405+
1406+
```mjs
1407+
import dc from 'node:diagnostics_channel';
1408+
import { DatabaseSync } from 'node:sqlite';
1409+
1410+
function onQuery(sql) {
1411+
console.log(sql);
1412+
}
1413+
1414+
dc.subscribe('sqlite.db.query', onQuery);
1415+
1416+
const db = new DatabaseSync(':memory:');
1417+
db.exec('CREATE TABLE t (x INTEGER)');
1418+
// Logs: CREATE TABLE t (x INTEGER)
1419+
1420+
const stmt = db.prepare('INSERT INTO t VALUES (?)');
1421+
stmt.run(42);
1422+
// Logs: INSERT INTO t VALUES (42.0)
1423+
1424+
dc.unsubscribe('sqlite.db.query', onQuery);
1425+
```
1426+
14171427
## `sqlite.constants`
14181428

14191429
<!-- YAML
@@ -1680,6 +1690,7 @@ callback function to indicate what type of operation is being authorized.
16801690
[`database.createTagStore()`]: #databasecreatetagstoremaxsize
16811691
[`database.serialize()`]: #databaseserializedbname
16821692
[`database.setAuthorizer()`]: #databasesetauthorizercallback
1693+
[`diagnostics_channel`]: diagnostics_channel.md
16831694
[`sqlite3_backup_finish()`]: https://www.sqlite.org/c3ref/backup_finish.html#sqlite3backupfinish
16841695
[`sqlite3_backup_init()`]: https://www.sqlite.org/c3ref/backup_finish.html#sqlite3backupinit
16851696
[`sqlite3_backup_step()`]: https://www.sqlite.org/c3ref/backup_finish.html#sqlite3backupstep
@@ -1703,7 +1714,6 @@ callback function to indicate what type of operation is being authorized.
17031714
[`sqlite3_serialize()`]: https://sqlite.org/c3ref/serialize.html
17041715
[`sqlite3_set_authorizer()`]: https://sqlite.org/c3ref/set_authorizer.html
17051716
[`sqlite3_sql()`]: https://www.sqlite.org/c3ref/expanded_sql.html
1706-
[`sqlite3_trace_v2()`]: https://www.sqlite.org/c3ref/trace_v2.html
17071717
[`sqlite3changeset_apply()`]: https://www.sqlite.org/session/sqlite3changeset_apply.html
17081718
[`sqlite3session_attach()`]: https://www.sqlite.org/session/sqlite3session_attach.html
17091719
[`sqlite3session_changeset()`]: https://www.sqlite.org/session/sqlite3session_changeset.html

src/node_sqlite.cc

Lines changed: 10 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include "env-inl.h"
66
#include "memory_tracker-inl.h"
77
#include "node.h"
8+
#include "node_diagnostics_channel.h"
89
#include "node_errors.h"
910
#include "node_mem-inl.h"
1011
#include "node_url.h"
@@ -993,6 +994,8 @@ bool DatabaseSync::Open() {
993994
env()->isolate(), this, load_extension_ret, SQLITE_OK, false);
994995
}
995996

997+
sqlite3_trace_v2(connection_, SQLITE_TRACE_STMT, TraceCallback, this);
998+
996999
return true;
9971000
}
9981001

@@ -2435,30 +2438,6 @@ void DatabaseSync::LoadExtension(const FunctionCallbackInfo<Value>& args) {
24352438
}
24362439
}
24372440

2438-
void DatabaseSync::SetSqlTraceHook(const FunctionCallbackInfo<Value>& args) {
2439-
DatabaseSync* db;
2440-
ASSIGN_OR_RETURN_UNWRAP(&db, args.This());
2441-
Environment* env = Environment::GetCurrent(args);
2442-
THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open");
2443-
Isolate* isolate = env->isolate();
2444-
2445-
if (args[0]->IsNull() || args[0]->IsUndefined()) {
2446-
sqlite3_trace_v2(db->connection_, 0, nullptr, nullptr);
2447-
db->object()->SetInternalField(kTraceCallback, Null(isolate));
2448-
return;
2449-
}
2450-
2451-
if (!args[0]->IsFunction()) {
2452-
THROW_ERR_INVALID_ARG_TYPE(isolate,
2453-
"The \"hook\" argument must be a function.");
2454-
return;
2455-
}
2456-
2457-
db->object()->SetInternalField(kTraceCallback, args[0].As<Function>());
2458-
sqlite3_trace_v2(
2459-
db->connection_, SQLITE_TRACE_STMT, DatabaseSync::TraceCallback, db);
2460-
}
2461-
24622441
void DatabaseSync::SetAuthorizer(const FunctionCallbackInfo<Value>& args) {
24632442
DatabaseSync* db;
24642443
ASSIGN_OR_RETURN_UNWRAP(&db, args.This());
@@ -2574,17 +2553,16 @@ int DatabaseSync::TraceCallback(unsigned int type,
25742553

25752554
DatabaseSync* db = static_cast<DatabaseSync*>(user_data);
25762555
Environment* env = db->env();
2577-
Isolate* isolate = env->isolate();
2578-
HandleScope handle_scope(isolate);
2579-
Local<Context> context = env->context();
25802556

2581-
Local<Value> cb =
2582-
db->object()->GetInternalField(kTraceCallback).template As<Value>();
2583-
2584-
if (!cb->IsFunction()) {
2557+
diagnostics_channel::Channel* ch =
2558+
diagnostics_channel::Channel::Get(env, "sqlite.db.query");
2559+
if (ch == nullptr || !ch->HasSubscribers()) {
25852560
return 0;
25862561
}
25872562

2563+
Isolate* isolate = env->isolate();
2564+
HandleScope handle_scope(isolate);
2565+
25882566
char* expanded = sqlite3_expanded_sql(static_cast<sqlite3_stmt*>(p));
25892567
Local<Value> sql_string;
25902568
if (expanded != nullptr) {
@@ -2602,13 +2580,7 @@ int DatabaseSync::TraceCallback(unsigned int type,
26022580
}
26032581
}
26042582

2605-
Local<Function> callback = cb.As<Function>();
2606-
MaybeLocal<Value> retval =
2607-
callback->Call(context, Undefined(isolate), 1, &sql_string);
2608-
2609-
if (retval.IsEmpty()) {
2610-
db->SetIgnoreNextSQLiteError(true);
2611-
}
2583+
ch->Publish(env, sql_string);
26122584

26132585
return 0;
26142586
}
@@ -4029,8 +4001,6 @@ static void Initialize(Local<Object> target,
40294001
isolate, db_tmpl, "loadExtension", DatabaseSync::LoadExtension);
40304002
SetProtoMethod(isolate, db_tmpl, "serialize", DatabaseSync::Serialize);
40314003
SetProtoMethod(isolate, db_tmpl, "deserialize", DatabaseSync::Deserialize);
4032-
SetProtoMethod(
4033-
isolate, db_tmpl, "setSqlTraceHook", DatabaseSync::SetSqlTraceHook);
40344004
SetProtoMethod(
40354005
isolate, db_tmpl, "setAuthorizer", DatabaseSync::SetAuthorizer);
40364006
SetSideEffectFreeGetter(isolate,

src/node_sqlite.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,6 @@ class DatabaseSync : public BaseObject {
166166
enum InternalFields {
167167
kAuthorizerCallback = BaseObject::kInternalFieldCount,
168168
kLimitsObject,
169-
kTraceCallback,
170169
kInternalFieldCount
171170
};
172171

@@ -206,7 +205,6 @@ class DatabaseSync : public BaseObject {
206205
const char* param2,
207206
const char* param3,
208207
const char* param4);
209-
static void SetSqlTraceHook(const v8::FunctionCallbackInfo<v8::Value>& args);
210208
static int TraceCallback(unsigned int type,
211209
void* user_data,
212210
void* p,

0 commit comments

Comments
 (0)