Commit bbb1226

Browse files
araujoguiaduh95
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+
constcommon=require('../common.js');
3+
constsqlite=require('node:sqlite');
4+
constdc=require('node:diagnostics_channel');
5+
constassert=require('node:assert');
6+
7+
constbench=common.createBenchmark(main,{
8+
n: [1e5],
9+
mode: ['none','subscribed','unsubscribed'],
10+
});
11+
12+
functionmain(conf){
13+
const{ n, mode }=conf;
14+
15+
constdb=newsqlite.DatabaseSync(':memory:');
16+
db.exec('CREATE TABLE t (x INTEGER)');
17+
constinsert=db.prepare('INSERT INTO t VALUES (?)');
18+
19+
letsubscriber;
20+
if(mode==='subscribed'){
21+
subscriber=()=>{};
22+
dc.subscribe('sqlite.db.query',subscriber);
23+
}elseif(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+
letresult;
31+
bench.start();
32+
for(leti=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
constsqlite=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=newSafeMap();
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
functionmaybeMarkInactive(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
#defineUNSERIALIZABLE_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+
voidBindingData::SetChannelStatusCallback(uint32_t index,
131+
ChannelStatusCallback cb) {
132+
channel_status_callbacks_[index] = std::move(cb);
133+
}
134+
135+
voidBindingData::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+
voidBindingData::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
voidBindingData::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
voidBindingData::CreatePerContextProperties(Local<Object> target,
@@ -145,6 +173,8 @@ void BindingData::CreatePerContextProperties(Local<Object> target,
145173
voidBindingData::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
staticvoidLinkNativeChannel(
5354
const v8::FunctionCallbackInfo<v8::Value>& args);
5455

56+
using ChannelStatusCallback = std::function<void(bool is_active)>;
57+
voidSetChannelStatusCallback(uint32_t index, ChannelStatusCallback cb);
58+
59+
staticvoidNotifyChannelActive(
60+
const v8::FunctionCallbackInfo<v8::Value>& args);
61+
staticvoidNotifyChannelInactive(
62+
const v8::FunctionCallbackInfo<v8::Value>& args);
63+
5564
staticvoidCreatePerIsolateProperties(IsolateData* isolate_data,
5665
v8::Local<v8::ObjectTemplate> target);
5766
staticvoidCreatePerContextProperties(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
classChannel : publicBaseObject {

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Commit bbb1226

Browse files
araujoguiaduh95
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+
constcommon=require('../common.js');
3+
constsqlite=require('node:sqlite');
4+
constdc=require('node:diagnostics_channel');
5+
constassert=require('node:assert');
6+
7+
constbench=common.createBenchmark(main,{
8+
n: [1e5],
9+
mode: ['none','subscribed','unsubscribed'],
10+
});
11+
12+
functionmain(conf){
13+
const{ n, mode }=conf;
14+
15+
constdb=newsqlite.DatabaseSync(':memory:');
16+
db.exec('CREATE TABLE t (x INTEGER)');
17+
constinsert=db.prepare('INSERT INTO t VALUES (?)');
18+
19+
letsubscriber;
20+
if(mode==='subscribed'){
21+
subscriber=()=>{};
22+
dc.subscribe('sqlite.db.query',subscriber);
23+
}elseif(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+
letresult;
31+
bench.start();
32+
for(leti=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
constsqlite=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=newSafeMap();
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
functionmaybeMarkInactive(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
#defineUNSERIALIZABLE_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+
voidBindingData::SetChannelStatusCallback(uint32_t index,
131+
ChannelStatusCallback cb) {
132+
channel_status_callbacks_[index] = std::move(cb);
133+
}
134+
135+
voidBindingData::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+
voidBindingData::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
voidBindingData::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
voidBindingData::CreatePerContextProperties(Local<Object> target,
@@ -145,6 +173,8 @@ void BindingData::CreatePerContextProperties(Local<Object> target,
145173
voidBindingData::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
staticvoidLinkNativeChannel(
5354
const v8::FunctionCallbackInfo<v8::Value>& args);
5455

56+
using ChannelStatusCallback = std::function<void(bool is_active)>;
57+
voidSetChannelStatusCallback(uint32_t index, ChannelStatusCallback cb);
58+
59+
staticvoidNotifyChannelActive(
60+
const v8::FunctionCallbackInfo<v8::Value>& args);
61+
staticvoidNotifyChannelInactive(
62+
const v8::FunctionCallbackInfo<v8::Value>& args);
63+
5564
staticvoidCreatePerIsolateProperties(IsolateData* isolate_data,
5665
v8::Local<v8::ObjectTemplate> target);
5766
staticvoidCreatePerContextProperties(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
classChannel : publicBaseObject {

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Commit bbb1226

Browse files
araujoguiaduh95
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+
constcommon=require('../common.js');
3+
constsqlite=require('node:sqlite');
4+
constdc=require('node:diagnostics_channel');
5+
constassert=require('node:assert');
6+
7+
constbench=common.createBenchmark(main,{
8+
n: [1e5],
9+
mode: ['none','subscribed','unsubscribed'],
10+
});
11+
12+
functionmain(conf){
13+
const{ n, mode }=conf;
14+
15+
constdb=newsqlite.DatabaseSync(':memory:');
16+
db.exec('CREATE TABLE t (x INTEGER)');
17+
constinsert=db.prepare('INSERT INTO t VALUES (?)');
18+
19+
letsubscriber;
20+
if(mode==='subscribed'){
21+
subscriber=()=>{};
22+
dc.subscribe('sqlite.db.query',subscriber);
23+
}elseif(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+
letresult;
31+
bench.start();
32+
for(leti=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
constsqlite=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=newSafeMap();
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
functionmaybeMarkInactive(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
#defineUNSERIALIZABLE_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+
voidBindingData::SetChannelStatusCallback(uint32_t index,
131+
ChannelStatusCallback cb) {
132+
channel_status_callbacks_[index] = std::move(cb);
133+
}
134+
135+
voidBindingData::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+
voidBindingData::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
voidBindingData::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
voidBindingData::CreatePerContextProperties(Local<Object> target,
@@ -145,6 +173,8 @@ void BindingData::CreatePerContextProperties(Local<Object> target,
145173
voidBindingData::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
staticvoidLinkNativeChannel(
5354
const v8::FunctionCallbackInfo<v8::Value>& args);
5455

56+
using ChannelStatusCallback = std::function<void(bool is_active)>;
57+
voidSetChannelStatusCallback(uint32_t index, ChannelStatusCallback cb);
58+
59+
staticvoidNotifyChannelActive(
60+
const v8::FunctionCallbackInfo<v8::Value>& args);
61+
staticvoidNotifyChannelInactive(
62+
const v8::FunctionCallbackInfo<v8::Value>& args);
63+
5564
staticvoidCreatePerIsolateProperties(IsolateData* isolate_data,
5665
v8::Local<v8::ObjectTemplate> target);
5766
staticvoidCreatePerContextProperties(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
classChannel : publicBaseObject {

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Commit bbb1226

Browse files
araujoguiaduh95
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+
constcommon=require('../common.js');
3+
constsqlite=require('node:sqlite');
4+
constdc=require('node:diagnostics_channel');
5+
constassert=require('node:assert');
6+
7+
constbench=common.createBenchmark(main,{
8+
n: [1e5],
9+
mode: ['none','subscribed','unsubscribed'],
10+
});
11+
12+
functionmain(conf){
13+
const{ n, mode }=conf;
14+
15+
constdb=newsqlite.DatabaseSync(':memory:');
16+
db.exec('CREATE TABLE t (x INTEGER)');
17+
constinsert=db.prepare('INSERT INTO t VALUES (?)');
18+
19+
letsubscriber;
20+
if(mode==='subscribed'){
21+
subscriber=()=>{};
22+
dc.subscribe('sqlite.db.query',subscriber);
23+
}elseif(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+
letresult;
31+
bench.start();
32+
for(leti=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
constsqlite=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=newSafeMap();
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
functionmaybeMarkInactive(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
#defineUNSERIALIZABLE_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+
voidBindingData::SetChannelStatusCallback(uint32_t index,
131+
ChannelStatusCallback cb) {
132+
channel_status_callbacks_[index] = std::move(cb);
133+
}
134+
135+
voidBindingData::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+
voidBindingData::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
voidBindingData::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
voidBindingData::CreatePerContextProperties(Local<Object> target,
@@ -145,6 +173,8 @@ void BindingData::CreatePerContextProperties(Local<Object> target,
145173
voidBindingData::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
staticvoidLinkNativeChannel(
5354
const v8::FunctionCallbackInfo<v8::Value>& args);
5455

56+
using ChannelStatusCallback = std::function<void(bool is_active)>;
57+
voidSetChannelStatusCallback(uint32_t index, ChannelStatusCallback cb);
58+
59+
staticvoidNotifyChannelActive(
60+
const v8::FunctionCallbackInfo<v8::Value>& args);
61+
staticvoidNotifyChannelInactive(
62+
const v8::FunctionCallbackInfo<v8::Value>& args);
63+
5564
staticvoidCreatePerIsolateProperties(IsolateData* isolate_data,
5665
v8::Local<v8::ObjectTemplate> target);
5766
staticvoidCreatePerContextProperties(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
classChannel : publicBaseObject {

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Commit bbb1226

Browse files
araujoguiaduh95
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+
constcommon=require('../common.js');
3+
constsqlite=require('node:sqlite');
4+
constdc=require('node:diagnostics_channel');
5+
constassert=require('node:assert');
6+
7+
constbench=common.createBenchmark(main,{
8+
n: [1e5],
9+
mode: ['none','subscribed','unsubscribed'],
10+
});
11+
12+
functionmain(conf){
13+
const{ n, mode }=conf;
14+
15+
constdb=newsqlite.DatabaseSync(':memory:');
16+
db.exec('CREATE TABLE t (x INTEGER)');
17+
constinsert=db.prepare('INSERT INTO t VALUES (?)');
18+
19+
letsubscriber;
20+
if(mode==='subscribed'){
21+
subscriber=()=>{};
22+
dc.subscribe('sqlite.db.query',subscriber);
23+
}elseif(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+
letresult;
31+
bench.start();
32+
for(leti=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
constsqlite=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=newSafeMap();
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
functionmaybeMarkInactive(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
#defineUNSERIALIZABLE_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+
voidBindingData::SetChannelStatusCallback(uint32_t index,
131+
ChannelStatusCallback cb) {
132+
channel_status_callbacks_[index] = std::move(cb);
133+
}
134+
135+
voidBindingData::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+
voidBindingData::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
voidBindingData::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
voidBindingData::CreatePerContextProperties(Local<Object> target,
@@ -145,6 +173,8 @@ void BindingData::CreatePerContextProperties(Local<Object> target,
145173
voidBindingData::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
staticvoidLinkNativeChannel(
5354
const v8::FunctionCallbackInfo<v8::Value>& args);
5455

56+
using ChannelStatusCallback = std::function<void(bool is_active)>;
57+
voidSetChannelStatusCallback(uint32_t index, ChannelStatusCallback cb);
58+
59+
staticvoidNotifyChannelActive(
60+
const v8::FunctionCallbackInfo<v8::Value>& args);
61+
staticvoidNotifyChannelInactive(
62+
const v8::FunctionCallbackInfo<v8::Value>& args);
63+
5564
staticvoidCreatePerIsolateProperties(IsolateData* isolate_data,
5665
v8::Local<v8::ObjectTemplate> target);
5766
staticvoidCreatePerContextProperties(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
classChannel : publicBaseObject {

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Commit bbb1226

Browse files
araujoguiaduh95
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+
constcommon=require('../common.js');
3+
constsqlite=require('node:sqlite');
4+
constdc=require('node:diagnostics_channel');
5+
constassert=require('node:assert');
6+
7+
constbench=common.createBenchmark(main,{
8+
n: [1e5],
9+
mode: ['none','subscribed','unsubscribed'],
10+
});
11+
12+
functionmain(conf){
13+
const{ n, mode }=conf;
14+
15+
constdb=newsqlite.DatabaseSync(':memory:');
16+
db.exec('CREATE TABLE t (x INTEGER)');
17+
constinsert=db.prepare('INSERT INTO t VALUES (?)');
18+
19+
letsubscriber;
20+
if(mode==='subscribed'){
21+
subscriber=()=>{};
22+
dc.subscribe('sqlite.db.query',subscriber);
23+
}elseif(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+
letresult;
31+
bench.start();
32+
for(leti=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
constsqlite=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=newSafeMap();
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
functionmaybeMarkInactive(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
#defineUNSERIALIZABLE_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+
voidBindingData::SetChannelStatusCallback(uint32_t index,
131+
ChannelStatusCallback cb) {
132+
channel_status_callbacks_[index] = std::move(cb);
133+
}
134+
135+
voidBindingData::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+
voidBindingData::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
voidBindingData::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
voidBindingData::CreatePerContextProperties(Local<Object> target,
@@ -145,6 +173,8 @@ void BindingData::CreatePerContextProperties(Local<Object> target,
145173
voidBindingData::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
staticvoidLinkNativeChannel(
5354
const v8::FunctionCallbackInfo<v8::Value>& args);
5455

56+
using ChannelStatusCallback = std::function<void(bool is_active)>;
57+
voidSetChannelStatusCallback(uint32_t index, ChannelStatusCallback cb);
58+
59+
staticvoidNotifyChannelActive(
60+
const v8::FunctionCallbackInfo<v8::Value>& args);
61+
staticvoidNotifyChannelInactive(
62+
const v8::FunctionCallbackInfo<v8::Value>& args);
63+
5564
staticvoidCreatePerIsolateProperties(IsolateData* isolate_data,
5665
v8::Local<v8::ObjectTemplate> target);
5766
staticvoidCreatePerContextProperties(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
classChannel : publicBaseObject {

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Commit bbb1226

Browse files
araujoguiaduh95
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+
constcommon=require('../common.js');
3+
constsqlite=require('node:sqlite');
4+
constdc=require('node:diagnostics_channel');
5+
constassert=require('node:assert');
6+
7+
constbench=common.createBenchmark(main,{
8+
n: [1e5],
9+
mode: ['none','subscribed','unsubscribed'],
10+
});
11+
12+
functionmain(conf){
13+
const{ n, mode }=conf;
14+
15+
constdb=newsqlite.DatabaseSync(':memory:');
16+
db.exec('CREATE TABLE t (x INTEGER)');
17+
constinsert=db.prepare('INSERT INTO t VALUES (?)');
18+
19+
letsubscriber;
20+
if(mode==='subscribed'){
21+
subscriber=()=>{};
22+
dc.subscribe('sqlite.db.query',subscriber);
23+
}elseif(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+
letresult;
31+
bench.start();
32+
for(leti=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
constsqlite=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=newSafeMap();
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
functionmaybeMarkInactive(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
#defineUNSERIALIZABLE_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+
voidBindingData::SetChannelStatusCallback(uint32_t index,
131+
ChannelStatusCallback cb) {
132+
channel_status_callbacks_[index] = std::move(cb);
133+
}
134+
135+
voidBindingData::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+
voidBindingData::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
voidBindingData::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
voidBindingData::CreatePerContextProperties(Local<Object> target,
@@ -145,6 +173,8 @@ void BindingData::CreatePerContextProperties(Local<Object> target,
145173
voidBindingData::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
staticvoidLinkNativeChannel(
5354
const v8::FunctionCallbackInfo<v8::Value>& args);
5455

56+
using ChannelStatusCallback = std::function<void(bool is_active)>;
57+
voidSetChannelStatusCallback(uint32_t index, ChannelStatusCallback cb);
58+
59+
staticvoidNotifyChannelActive(
60+
const v8::FunctionCallbackInfo<v8::Value>& args);
61+
staticvoidNotifyChannelInactive(
62+
const v8::FunctionCallbackInfo<v8::Value>& args);
63+
5564
staticvoidCreatePerIsolateProperties(IsolateData* isolate_data,
5665
v8::Local<v8::ObjectTemplate> target);
5766
staticvoidCreatePerContextProperties(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
classChannel : publicBaseObject {

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Commit bbb1226

Browse files
araujoguiaduh95
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+
constcommon=require('../common.js');
3+
constsqlite=require('node:sqlite');
4+
constdc=require('node:diagnostics_channel');
5+
constassert=require('node:assert');
6+
7+
constbench=common.createBenchmark(main,{
8+
n: [1e5],
9+
mode: ['none','subscribed','unsubscribed'],
10+
});
11+
12+
functionmain(conf){
13+
const{ n, mode }=conf;
14+
15+
constdb=newsqlite.DatabaseSync(':memory:');
16+
db.exec('CREATE TABLE t (x INTEGER)');
17+
constinsert=db.prepare('INSERT INTO t VALUES (?)');
18+
19+
letsubscriber;
20+
if(mode==='subscribed'){
21+
subscriber=()=>{};
22+
dc.subscribe('sqlite.db.query',subscriber);
23+
}elseif(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+
letresult;
31+
bench.start();
32+
for(leti=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
constsqlite=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=newSafeMap();
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
functionmaybeMarkInactive(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
#defineUNSERIALIZABLE_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+
voidBindingData::SetChannelStatusCallback(uint32_t index,
131+
ChannelStatusCallback cb) {
132+
channel_status_callbacks_[index] = std::move(cb);
133+
}
134+
135+
voidBindingData::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+
voidBindingData::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
voidBindingData::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
voidBindingData::CreatePerContextProperties(Local<Object> target,
@@ -145,6 +173,8 @@ void BindingData::CreatePerContextProperties(Local<Object> target,
145173
voidBindingData::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
staticvoidLinkNativeChannel(
5354
const v8::FunctionCallbackInfo<v8::Value>& args);
5455

56+
using ChannelStatusCallback = std::function<void(bool is_active)>;
57+
voidSetChannelStatusCallback(uint32_t index, ChannelStatusCallback cb);
58+
59+
staticvoidNotifyChannelActive(
60+
const v8::FunctionCallbackInfo<v8::Value>& args);
61+
staticvoidNotifyChannelInactive(
62+
const v8::FunctionCallbackInfo<v8::Value>& args);
63+
5564
staticvoidCreatePerIsolateProperties(IsolateData* isolate_data,
5665
v8::Local<v8::ObjectTemplate> target);
5766
staticvoidCreatePerContextProperties(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
classChannel : publicBaseObject {

0 commit comments

Comments
 (0)