FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

sqlite: add diagnostic channel · nodejs/node@bbb1226 · GitHub

/ node Public

Commit bbb1226

Browse files
authored andcommitted
sqlite: add diagnostic channel
Signed-off-by: Guilherme Araújo <arauujogui@gmail.com> PR-URL: #62241 Reviewed-By: Stephen Belanger <admin@stephenbelanger.com> Reviewed-By: René <contact.9a5d6388@renegade334.me.uk> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com>
1 parent 7af0a96 commit bbb1226

11 files changed

Lines changed: 579 additions & 6 deletions
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
'use strict';
2+
const common = require('../common.js');
3+
const sqlite = require('node:sqlite');
4+
const dc = require('node:diagnostics_channel');
5+
const assert = require('node:assert');
6+
7+
const bench = common.createBenchmark(main, {
8+
n: [1e5],
9+
mode: ['none', 'subscribed', 'unsubscribed'],
10+
});
11+
12+
function main(conf) {
13+
const { n, mode } = conf;
14+
15+
const db = new sqlite.DatabaseSync(':memory:');
16+
db.exec('CREATE TABLE t (x INTEGER)');
17+
const insert = db.prepare('INSERT INTO t VALUES (?)');
18+
19+
let subscriber;
20+
if (mode === 'subscribed') {
21+
subscriber = () => {};
22+
dc.subscribe('sqlite.db.query', subscriber);
23+
} else if (mode === 'unsubscribed') {
24+
subscriber = () => {};
25+
dc.subscribe('sqlite.db.query', subscriber);
26+
dc.unsubscribe('sqlite.db.query', subscriber);
27+
}
28+
// mode === 'none': no subscription ever made
29+
30+
let result;
31+
bench.start();
32+
for (let i = 0; i < n; i++) {
33+
result = insert.run(i);
34+
}
35+
bench.end(n);
36+
37+
if (mode === 'subscribed') {
38+
dc.unsubscribe('sqlite.db.query', subscriber);
39+
}
40+
41+
assert.ok(result !== undefined);
42+
}

‎doc/api/diagnostics_channel.md‎

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1922,10 +1922,47 @@ added: v16.18.0
19221922

19231923
Emitted when a new thread is created.
19241924

1925+
#### SQLite
1926+
1927+
<!-- YAML
1928+
added: REPLACEME
1929+
-->
1930+
1931+
> Stability: 1 - Experimental
1932+
1933+
##### Event: `'sqlite.db.query'`
1934+
1935+
* `sql` {string} The expanded SQL with bound parameter values substituted.
1936+
If expansion fails, the source SQL with unsubstituted placeholders is used
1937+
instead.
1938+
* `database` {DatabaseSync} The [`DatabaseSync`][] instance that executed the
1939+
statement.
1940+
* `duration` {number} SQLite's internal estimate of the statement run time in
1941+
nanoseconds. This reflects C-layer execution time only and does not include
1942+
JavaScript binding overhead such as argument marshaling or result-row
1943+
construction.
1944+
1945+
Emitted after a SQL statement finishes executing against a [`DatabaseSync`][]
1946+
instance. This is a **profiling** event: it fires once per statement upon
1947+
completion and reports an estimated duration from SQLite's internal profiler.
1948+
It is not a distributed-tracing span. There is no corresponding start event,
1949+
no async context propagation, and no parent-span linkage. If you need
1950+
OpenTelemetry-compatible spans or async context propagation, wrap your SQLite
1951+
calls with a [`TracingChannel`][] at the JavaScript layer instead.
1952+
1953+
Publishing is zero-overhead when there are no subscribers.
1954+
1955+
No event is emitted for a statement that is abandoned mid-iteration and later
1956+
finalized, either explicitly through [`statement.close()`][] or when the
1957+
statement is garbage collected. Subscribers must not close the database or the
1958+
statement, since both are still in use while the event is being delivered; see
1959+
[`database.close()`][] and [`statement.close()`][].
1960+
19251961
[BoundedChannel Channels]: #boundedchannel-channels
19261962
[TracingChannel Channels]: #tracingchannel-channels
19271963
[`'uncaughtException'`]: process.md#event-uncaughtexception
19281964
[`BoundedChannel`]: #class-boundedchannel
1965+
[`DatabaseSync`]: sqlite.md#class-databasesync
19291966
[`TracingChannel`]: #class-tracingchannel
19301967
[`asyncEnd` event]: #asyncendevent
19311968
[`asyncStart` event]: #asyncstartevent
@@ -1936,6 +1973,7 @@ Emitted when a new thread is created.
19361973
[`channel.unsubscribe(onMessage)`]: #channelunsubscribeonmessage
19371974
[`channel.withStoreScope(data)`]: #channelwithstorescopedata
19381975
[`child_process.spawn()`]: child_process.md#child_processspawncommand-args-options
1976+
[`database.close()`]: sqlite.md#databaseclose
19391977
[`diagnostics_channel.channel(name)`]: #diagnostics_channelchannelname
19401978
[`diagnostics_channel.subscribe(name, onMessage)`]: #diagnostics_channelsubscribename-onmessage
19411979
[`diagnostics_channel.tracingChannel()`]: #diagnostics_channeltracingchannelnameorchannels
@@ -1945,6 +1983,7 @@ Emitted when a new thread is created.
19451983
[`net.Server.listen()`]: net.md#serverlisten
19461984
[`process.execve()`]: process.md#processexecvefile-args-env
19471985
[`start` event]: #startevent
1986+
[`statement.close()`]: sqlite.md#statementclose
19481987
[`worker_threads.locks`]: worker_threads.md#worker_threadslocks
19491988
[context loss]: async_context.md#troubleshooting-context-loss
19501989
[thenable object]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise#thenables

‎doc/api/sqlite.md‎

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,9 @@ import sqlite from 'node:sqlite';
3030
const sqlite = require('node:sqlite');
3131
```
3232

33-
This module is only available under the `node:` scheme.
33+
This module is only available under the `node:` scheme. SQL trace events can
34+
be observed via the [`diagnostics_channel`][] module. See
35+
[`'sqlite.db.query'`][] for details.
3436

3537
The following example shows the basic usage of the `node:sqlite` module to open
3638
an in-memory database, write data to the database, and then read the data back.
@@ -309,8 +311,8 @@ added: v22.5.0
309311
Closes the database connection. An exception is thrown if the database is not
310312
open. An [`ERR_INVALID_STATE`][] error is thrown if the method is called while
311313
a statement is executing, such as inside a user-defined function, an aggregate
312-
function, or an authorizer callback. This method is a wrapper around
313-
[`sqlite3_close_v2()`][].
314+
function, an authorizer callback, or a [`'sqlite.db.query'`][] subscriber. This
315+
method is a wrapper around [`sqlite3_close_v2()`][].
314316

315317
### `database.loadExtension(path[, entryPoint])`
316318

@@ -1114,7 +1116,12 @@ added: REPLACEME
11141116
-->
11151117

11161118
Finalizes the prepared statement. An exception is thrown if the statement is
1117-
already finalized. This method is a wrapper around [`sqlite3_finalize()`][].
1119+
already finalized. An [`ERR_INVALID_STATE`][] error is thrown if this statement
1120+
is currently executing, which happens when the method is called from a callback
1121+
that the statement itself triggered, such as a user-defined function, an
1122+
aggregate function, or a [`'sqlite.db.query'`][] subscriber. Other statements
1123+
on the same connection can be finalized from such a callback. This method is a
1124+
wrapper around [`sqlite3_finalize()`][].
11181125

11191126
### `statement.columns()`
11201127

@@ -1361,7 +1368,9 @@ added: REPLACEME
13611368
-->
13621369

13631370
Finalizes the prepared statement. If the prepared statement is already
1364-
finalized, then this is a no-op.
1371+
finalized, then this is a no-op. An [`ERR_INVALID_STATE`][] error is thrown if
1372+
this statement is currently executing, under the same conditions as
1373+
[`statement.close()`][].
13651374

13661375
### `statement.stat(counter)`
13671376

@@ -1882,6 +1891,7 @@ callback function to indicate what type of operation is being authorized.
18821891
[Run-Time Limits]: https://www.sqlite.org/c3ref/limit.html
18831892
[SQL injection]: https://en.wikipedia.org/wiki/SQL_injection
18841893
[Type conversion between JavaScript and SQLite]: #type-conversion-between-javascript-and-sqlite
1894+
[`'sqlite.db.query'`]: diagnostics_channel.md#event-sqlitedbquery
18851895
[`ATTACH DATABASE`]: https://www.sqlite.org/lang_attach.html
18861896
[`ERR_INVALID_STATE`]: errors.md#err_invalid_state
18871897
[`PRAGMA foreign_keys`]: https://www.sqlite.org/pragma.html#pragma_foreign_keys
@@ -1895,6 +1905,7 @@ callback function to indicate what type of operation is being authorized.
18951905
[`database.createTagStore()`]: #databasecreatetagstoremaxsize
18961906
[`database.serialize()`]: #databaseserializedbname
18971907
[`database.setAuthorizer()`]: #databasesetauthorizercallback
1908+
[`diagnostics_channel`]: diagnostics_channel.md
18981909
[`sqlite3_backup_finish()`]: https://www.sqlite.org/c3ref/backup_finish.html#sqlite3backupfinish
18991910
[`sqlite3_backup_init()`]: https://www.sqlite.org/c3ref/backup_finish.html#sqlite3backupinit
19001911
[`sqlite3_backup_step()`]: https://www.sqlite.org/c3ref/backup_finish.html#sqlite3backupstep
@@ -1926,6 +1937,7 @@ callback function to indicate what type of operation is being authorized.
19261937
[`sqlite3session_create()`]: https://www.sqlite.org/session/sqlite3session_create.html
19271938
[`sqlite3session_delete()`]: https://www.sqlite.org/session/sqlite3session_delete.html
19281939
[`sqlite3session_patchset()`]: https://www.sqlite.org/session/sqlite3session_patchset.html
1940+
[`statement.close()`]: #statementclose
19291941
[`statement.setAllowBareNamedParameters()`]: #statementsetallowbarenamedparametersenabled
19301942
[`statement.setAllowUnknownNamedParameters()`]: #statementsetallowunknownnamedparametersenabled
19311943
[`statement.stat()`]: #statementstatcounter

‎lib/diagnostics_channel.js‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,11 +73,19 @@ function markActive(channel) {
7373
ObjectSetPrototypeOf(channel, ActiveChannel.prototype);
7474
channel._subscribers = [];
7575
channel._stores = new SafeMap();
76+
77+
// Notify native modules that this channel just got its first subscriber.
78+
if (channel._index !== undefined)
79+
dc_binding.notifyChannelActive(channel._index);
7680
}
7781

7882
function maybeMarkInactive(channel) {
7983
// When there are no more active subscribers or bound, restore to fast prototype.
8084
if (!channel._subscribers.length && !channel._stores.size) {
85+
// Notify native modules that this channel just lost its last subscriber.
86+
if (channel._index !== undefined)
87+
dc_binding.notifyChannelInactive(channel._index);
88+
8189
// eslint-disable-next-line no-use-before-define
8290
ObjectSetPrototypeOf(channel, Channel.prototype);
8391
channel._subscribers = undefined;

‎src/base_object_types.h‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ namespace node {
2424
#define UNSERIALIZABLE_BINDING_TYPES(V) \
2525
V(http2_binding_data, http2::BindingData) \
2626
V(http_parser_binding_data, http_parser::BindingData) \
27-
V(quic_binding_data, quic::BindingData)
27+
V(quic_binding_data, quic::BindingData) \
28+
V(sqlite_binding_data, sqlite::BindingData)
2829

2930
// List of (non-binding) BaseObjects that are serializable in the snapshot.
3031
// The first argument should match what the type passes to

‎src/env_properties.h‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@
143143
V(crypto_rsa_pss_string, "rsa-pss") \
144144
V(cwd_string, "cwd") \
145145
V(data_string, "data") \
146+
V(database_string, "database") \
146147
V(default_is_true_string, "defaultIsTrue") \
147148
V(defensive_string, "defensive") \
148149
V(deserialize_info_string, "deserializeInfo") \
@@ -359,6 +360,7 @@
359360
V(source_map_url_string, "sourceMapURL") \
360361
V(source_url_string, "sourceURL") \
361362
V(specifier_string, "specifier") \
363+
V(sql_string, "sql") \
362364
V(stack_string, "stack") \
363365
V(start_string, "start") \
364366
V(state_string, "state") \

‎src/node_diagnostics_channel.cc‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,10 +127,38 @@ void BindingData::Deserialize(Local<Context> context,
127127
CHECK_NOT_NULL(binding);
128128
}
129129

130+
void BindingData::SetChannelStatusCallback(uint32_t index,
131+
ChannelStatusCallback cb) {
132+
channel_status_callbacks_[index] = std::move(cb);
133+
}
134+
135+
void BindingData::NotifyChannelActive(const FunctionCallbackInfo<Value>& args) {
136+
Realm* realm = Realm::GetCurrent(args);
137+
BindingData* binding = realm->GetBindingData<BindingData>();
138+
if (binding == nullptr) return;
139+
CHECK(args[0]->IsUint32());
140+
uint32_t index = args[0].As<v8::Uint32>()->Value();
141+
auto it = binding->channel_status_callbacks_.find(index);
142+
if (it != binding->channel_status_callbacks_.end()) it->second(true);
143+
}
144+
145+
void BindingData::NotifyChannelInactive(
146+
const FunctionCallbackInfo<Value>& args) {
147+
Realm* realm = Realm::GetCurrent(args);
148+
BindingData* binding = realm->GetBindingData<BindingData>();
149+
if (binding == nullptr) return;
150+
CHECK(args[0]->IsUint32());
151+
uint32_t index = args[0].As<v8::Uint32>()->Value();
152+
auto it = binding->channel_status_callbacks_.find(index);
153+
if (it != binding->channel_status_callbacks_.end()) it->second(false);
154+
}
155+
130156
void BindingData::CreatePerIsolateProperties(IsolateData* isolate_data,
131157
Local<ObjectTemplate> target) {
132158
Isolate* isolate = isolate_data->isolate();
133159
SetMethod(isolate, target, "linkNativeChannel", LinkNativeChannel);
160+
SetMethod(isolate, target, "notifyChannelActive", NotifyChannelActive);
161+
SetMethod(isolate, target, "notifyChannelInactive", NotifyChannelInactive);
134162
}
135163

136164
void BindingData::CreatePerContextProperties(Local<Object> target,
@@ -145,6 +173,8 @@ void BindingData::CreatePerContextProperties(Local<Object> target,
145173
void BindingData::RegisterExternalReferences(
146174
ExternalReferenceRegistry* registry) {
147175
registry->Register(LinkNativeChannel);
176+
registry->Register(NotifyChannelActive);
177+
registry->Register(NotifyChannelInactive);
148178
}
149179

150180
Channel::Channel(Environment* env,

‎src/node_diagnostics_channel.h‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
55

66
#include <cinttypes>
7+
#include <functional>
78
#include <string>
89
#include <unordered_map>
910
#include <vector>
@@ -52,6 +53,14 @@ class BindingData : public SnapshotableObject {
5253
static void LinkNativeChannel(
5354
const v8::FunctionCallbackInfo<v8::Value>& args);
5455

56+
using ChannelStatusCallback = std::function<void(bool is_active)>;
57+
void SetChannelStatusCallback(uint32_t index, ChannelStatusCallback cb);
58+
59+
static void NotifyChannelActive(
60+
const v8::FunctionCallbackInfo<v8::Value>& args);
61+
static void NotifyChannelInactive(
62+
const v8::FunctionCallbackInfo<v8::Value>& args);
63+
5564
static void CreatePerIsolateProperties(IsolateData* isolate_data,
5665
v8::Local<v8::ObjectTemplate> target);
5766
static void CreatePerContextProperties(v8::Local<v8::Object> target,
@@ -62,6 +71,7 @@ class BindingData : public SnapshotableObject {
6271

6372
private:
6473
InternalFieldInfo* internal_field_info_ = nullptr;
74+
std::unordered_map<uint32_t, ChannelStatusCallback> channel_status_callbacks_;
6575
};
6676

6777
class Channel : public BaseObject {

0 commit comments

Comments
 (0)

Back | FazBrowse Home | New Git URL