Skip to content

Commit c40646d

Browse files
committed
sqlite: use diagnostic channel
1 parent 8bfc4fd commit c40646d

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
@@ -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
@@ -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

@@ -2286,30 +2289,6 @@ void DatabaseSync::LoadExtension(const FunctionCallbackInfo<Value>& args) {
22862289
}
22872290
}
22882291

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

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

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

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

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

24642436
return 0;
24652437
}
@@ -3839,8 +3811,6 @@ static void Initialize(Local<Object> target,
38393811
isolate, db_tmpl, "enableDefensive", DatabaseSync::EnableDefensive);
38403812
SetProtoMethod(
38413813
isolate, db_tmpl, "loadExtension", DatabaseSync::LoadExtension);
3842-
SetProtoMethod(
3843-
isolate, db_tmpl, "setSqlTraceHook", DatabaseSync::SetSqlTraceHook);
38443814
SetProtoMethod(
38453815
isolate, db_tmpl, "setAuthorizer", DatabaseSync::SetAuthorizer);
38463816
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)