Skip to content

Commit 83627f5

Browse files
committed
sqlite: use diagnostic channel
1 parent 0d78adb commit 83627f5

File tree

4 files changed

+108
-139
lines changed

4 files changed

+108
-139
lines changed

doc/api/sqlite.md

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

375-
### `database.setSqlTraceHook(hook)`
376-
377-
<!-- YAML
378-
added: REPLACEME
379-
-->
380-
381-
* `hook` {Function|null} The trace function to set, or `null` to clear
382-
the current hook.
383-
384-
Sets a hook that SQLite invokes for every SQL statement executed against the
385-
database. The hook receives the expanded SQL string (with bound parameter
386-
values substituted) as its only argument. If expansion fails, the source SQL
387-
with unsubstituted placeholders is passed instead.
388-
389-
This method is a wrapper around [`sqlite3_trace_v2()`][].
390-
391-
```cjs
392-
const { DatabaseSync } = require('node:sqlite');
393-
const db = new DatabaseSync(':memory:');
394-
395-
db.setSqlTraceHook((sql) => console.log(sql));
396-
397-
db.exec('CREATE TABLE t (x INTEGER)');
398-
// Logs: CREATE TABLE t (x INTEGER)
399-
400-
const stmt = db.prepare('INSERT INTO t VALUES (?)');
401-
stmt.run(42);
402-
// Logs: INSERT INTO t VALUES (42.0)
403-
404-
// Clear the hook
405-
db.setSqlTraceHook(null);
406-
```
407-
408-
```mjs
409-
import { DatabaseSync } from 'node:sqlite';
410-
const db = new DatabaseSync(':memory:');
411-
412-
db.setSqlTraceHook((sql) => console.log(sql));
413-
414-
db.exec('CREATE TABLE t (x INTEGER)');
415-
// Logs: CREATE TABLE t (x INTEGER)
416-
417-
const stmt = db.prepare('INSERT INTO t VALUES (?)');
418-
stmt.run(42);
419-
// Logs: INSERT INTO t VALUES (42.0)
420-
421-
// Clear the hook
422-
db.setSqlTraceHook(null);
423-
```
424-
425375
### `database.setAuthorizer(callback)`
426376

427377
<!-- YAML
@@ -1331,6 +1281,66 @@ const totalPagesTransferred = await backup(sourceDb, 'backup.db', {
13311281
console.log('Backup completed', totalPagesTransferred);
13321282
```
13331283

1284+
## Diagnostics channel
1285+
1286+
<!-- YAML
1287+
added: REPLACEME
1288+
-->
1289+
1290+
The `node:sqlite` module publishes SQL trace events on the
1291+
[`diagnostics_channel`][] channel `sqlite.db.query`. This allows subscribers
1292+
to observe every SQL statement executed against any `DatabaseSync` instance
1293+
without modifying the database code itself. Tracing is zero-cost when there
1294+
are no subscribers.
1295+
1296+
### Channel `sqlite.db.query`
1297+
1298+
The message published to this channel is a {string} containing the expanded
1299+
SQL with bound parameter values substituted. If expansion fails, the source
1300+
SQL with unsubstituted placeholders is used instead.
1301+
1302+
```cjs
1303+
const dc = require('node:diagnostics_channel');
1304+
const { DatabaseSync } = require('node:sqlite');
1305+
1306+
function onQuery(sql) {
1307+
console.log(sql);
1308+
}
1309+
1310+
dc.subscribe('sqlite.db.query', onQuery);
1311+
1312+
const db = new DatabaseSync(':memory:');
1313+
db.exec('CREATE TABLE t (x INTEGER)');
1314+
// Logs: CREATE TABLE t (x INTEGER)
1315+
1316+
const stmt = db.prepare('INSERT INTO t VALUES (?)');
1317+
stmt.run(42);
1318+
// Logs: INSERT INTO t VALUES (42.0)
1319+
1320+
dc.unsubscribe('sqlite.db.query', onQuery);
1321+
```
1322+
1323+
```mjs
1324+
import dc from 'node:diagnostics_channel';
1325+
import { DatabaseSync } from 'node:sqlite';
1326+
1327+
function onQuery(sql) {
1328+
console.log(sql);
1329+
}
1330+
1331+
dc.subscribe('sqlite.db.query', onQuery);
1332+
1333+
const db = new DatabaseSync(':memory:');
1334+
db.exec('CREATE TABLE t (x INTEGER)');
1335+
// Logs: CREATE TABLE t (x INTEGER)
1336+
1337+
const stmt = db.prepare('INSERT INTO t VALUES (?)');
1338+
stmt.run(42);
1339+
// Logs: INSERT INTO t VALUES (42.0)
1340+
1341+
dc.unsubscribe('sqlite.db.query', onQuery);
1342+
```
1343+
13341344
## `sqlite.constants`
13351345

13361346
<!-- YAML
@@ -1596,6 +1606,7 @@ callback function to indicate what type of operation is being authorized.
15961606
[`database.applyChangeset()`]: #databaseapplychangesetchangeset-options
15971607
[`database.createTagStore()`]: #databasecreatetagstoremaxsize
15981608
[`database.setAuthorizer()`]: #databasesetauthorizercallback
1609+
[`diagnostics_channel`]: diagnostics_channel.md
15991610
[`sqlite3_backup_finish()`]: https://www.sqlite.org/c3ref/backup_finish.html#sqlite3backupfinish
16001611
[`sqlite3_backup_init()`]: https://www.sqlite.org/c3ref/backup_finish.html#sqlite3backupinit
16011612
[`sqlite3_backup_step()`]: https://www.sqlite.org/c3ref/backup_finish.html#sqlite3backupstep
@@ -1617,7 +1628,6 @@ callback function to indicate what type of operation is being authorized.
16171628
[`sqlite3_prepare_v2()`]: https://www.sqlite.org/c3ref/prepare.html
16181629
[`sqlite3_set_authorizer()`]: https://sqlite.org/c3ref/set_authorizer.html
16191630
[`sqlite3_sql()`]: https://www.sqlite.org/c3ref/expanded_sql.html
1620-
[`sqlite3_trace_v2()`]: https://www.sqlite.org/c3ref/trace_v2.html
16211631
[`sqlite3changeset_apply()`]: https://www.sqlite.org/session/sqlite3changeset_apply.html
16221632
[`sqlite3session_attach()`]: https://www.sqlite.org/session/sqlite3session_attach.html
16231633
[`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"
@@ -974,6 +975,8 @@ bool DatabaseSync::Open() {
974975
env()->isolate(), this, load_extension_ret, SQLITE_OK, false);
975976
}
976977

978+
sqlite3_trace_v2(connection_, SQLITE_TRACE_STMT, TraceCallback, this);
979+
977980
return true;
978981
}
979982

@@ -2285,30 +2288,6 @@ void DatabaseSync::LoadExtension(const FunctionCallbackInfo<Value>& args) {
22852288
}
22862289
}
22872290

2288-
void DatabaseSync::SetSqlTraceHook(const FunctionCallbackInfo<Value>& args) {
2289-
DatabaseSync* db;
2290-
ASSIGN_OR_RETURN_UNWRAP(&db, args.This());
2291-
Environment* env = Environment::GetCurrent(args);
2292-
THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open");
2293-
Isolate* isolate = env->isolate();
2294-
2295-
if (args[0]->IsNull() || args[0]->IsUndefined()) {
2296-
sqlite3_trace_v2(db->connection_, 0, nullptr, nullptr);
2297-
db->object()->SetInternalField(kTraceCallback, Null(isolate));
2298-
return;
2299-
}
2300-
2301-
if (!args[0]->IsFunction()) {
2302-
THROW_ERR_INVALID_ARG_TYPE(isolate,
2303-
"The \"hook\" argument must be a function.");
2304-
return;
2305-
}
2306-
2307-
db->object()->SetInternalField(kTraceCallback, args[0].As<Function>());
2308-
sqlite3_trace_v2(
2309-
db->connection_, SQLITE_TRACE_STMT, DatabaseSync::TraceCallback, db);
2310-
}
2311-
23122291
void DatabaseSync::SetAuthorizer(const FunctionCallbackInfo<Value>& args) {
23132292
DatabaseSync* db;
23142293
ASSIGN_OR_RETURN_UNWRAP(&db, args.This());
@@ -2424,17 +2403,16 @@ int DatabaseSync::TraceCallback(unsigned int type,
24242403

24252404
DatabaseSync* db = static_cast<DatabaseSync*>(user_data);
24262405
Environment* env = db->env();
2427-
Isolate* isolate = env->isolate();
2428-
HandleScope handle_scope(isolate);
2429-
Local<Context> context = env->context();
24302406

2431-
Local<Value> cb =
2432-
db->object()->GetInternalField(kTraceCallback).template As<Value>();
2433-
2434-
if (!cb->IsFunction()) {
2407+
diagnostics_channel::Channel* ch =
2408+
diagnostics_channel::Channel::Get(env, "sqlite.db.query");
2409+
if (ch == nullptr || !ch->HasSubscribers()) {
24352410
return 0;
24362411
}
24372412

2413+
Isolate* isolate = env->isolate();
2414+
HandleScope handle_scope(isolate);
2415+
24382416
char* expanded = sqlite3_expanded_sql(static_cast<sqlite3_stmt*>(p));
24392417
Local<Value> sql_string;
24402418
if (expanded != nullptr) {
@@ -2452,13 +2430,7 @@ int DatabaseSync::TraceCallback(unsigned int type,
24522430
}
24532431
}
24542432

2455-
Local<Function> callback = cb.As<Function>();
2456-
MaybeLocal<Value> retval =
2457-
callback->Call(context, Undefined(isolate), 1, &sql_string);
2458-
2459-
if (retval.IsEmpty()) {
2460-
db->SetIgnoreNextSQLiteError(true);
2461-
}
2433+
ch->Publish(env, sql_string);
24622434

24632435
return 0;
24642436
}
@@ -3838,8 +3810,6 @@ static void Initialize(Local<Object> target,
38383810
isolate, db_tmpl, "enableDefensive", DatabaseSync::EnableDefensive);
38393811
SetProtoMethod(
38403812
isolate, db_tmpl, "loadExtension", DatabaseSync::LoadExtension);
3841-
SetProtoMethod(
3842-
isolate, db_tmpl, "setSqlTraceHook", DatabaseSync::SetSqlTraceHook);
38433813
SetProtoMethod(
38443814
isolate, db_tmpl, "setAuthorizer", DatabaseSync::SetAuthorizer);
38453815
SetSideEffectFreeGetter(isolate,

src/node_sqlite.h

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

@@ -203,7 +202,6 @@ class DatabaseSync : public BaseObject {
203202
const char* param2,
204203
const char* param3,
205204
const char* param4);
206-
static void SetSqlTraceHook(const v8::FunctionCallbackInfo<v8::Value>& args);
207205
static int TraceCallback(unsigned int type,
208206
void* user_data,
209207
void* p,

0 commit comments

Comments
 (0)