| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 7af0a96 commit bbb1226
11 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,42 @@ | |||
| 1 | + 'use strict'; | ||
| 2 | + const common = require('../common.js'); | ||
| 3 | + const sqlite = require('node:sqlite'); | ||
| 4 | + const dc = require('node:diagnostics_channel'); | ||
| 5 | + const assert = require('node:assert'); | ||
| 6 | + | ||
| 7 | + const bench = common.createBenchmark(main, { | ||
| 8 | + n: [1e5], | ||
| 9 | + mode: ['none', 'subscribed', 'unsubscribed'], | ||
| 10 | + }); | ||
| 11 | + | ||
| 12 | + function main(conf) { | ||
| 13 | + const { n, mode } = conf; | ||
| 14 | + | ||
| 15 | + const db = new sqlite.DatabaseSync(':memory:'); | ||
| 16 | + db.exec('CREATE TABLE t (x INTEGER)'); | ||
| 17 | + const insert = db.prepare('INSERT INTO t VALUES (?)'); | ||
| 18 | + | ||
| 19 | + let subscriber; | ||
| 20 | + if (mode === 'subscribed') { | ||
| 21 | + subscriber = () => {}; | ||
| 22 | + dc.subscribe('sqlite.db.query', subscriber); | ||
| 23 | + } else if (mode === 'unsubscribed') { | ||
| 24 | + subscriber = () => {}; | ||
| 25 | + dc.subscribe('sqlite.db.query', subscriber); | ||
| 26 | + dc.unsubscribe('sqlite.db.query', subscriber); | ||
| 27 | + } | ||
| 28 | + // mode === 'none': no subscription ever made | ||
| 29 | + | ||
| 30 | + let result; | ||
| 31 | + bench.start(); | ||
| 32 | + for (let i = 0; i < n; i++) { | ||
| 33 | + result = insert.run(i); | ||
| 34 | + } | ||
| 35 | + bench.end(n); | ||
| 36 | + | ||
| 37 | + if (mode === 'subscribed') { | ||
| 38 | + dc.unsubscribe('sqlite.db.query', subscriber); | ||
| 39 | + } | ||
| 40 | + | ||
| 41 | + assert.ok(result !== undefined); | ||
| 42 | + } | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1922,10 +1922,47 @@ added: v16.18.0 | |||
| 1922 | 1922 | ||
| 1923 | 1923 | Emitted when a new thread is created. | |
| 1924 | 1924 | ||
| 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 | + | ||
| 1925 | 1961 | [BoundedChannel Channels]: #boundedchannel-channels | |
| 1926 | 1962 | [TracingChannel Channels]: #tracingchannel-channels | |
| 1927 | 1963 | [`'uncaughtException'`]: process.md#event-uncaughtexception | |
| 1928 | 1964 | [`BoundedChannel`]: #class-boundedchannel | |
| 1965 | + [`DatabaseSync`]: sqlite.md#class-databasesync | ||
| 1929 | 1966 | [`TracingChannel`]: #class-tracingchannel | |
| 1930 | 1967 | [`asyncEnd` event]: #asyncendevent | |
| 1931 | 1968 | [`asyncStart` event]: #asyncstartevent | |
@@ -1936,6 +1973,7 @@ Emitted when a new thread is created. | |||
| 1936 | 1973 | [`channel.unsubscribe(onMessage)`]: #channelunsubscribeonmessage | |
| 1937 | 1974 | [`channel.withStoreScope(data)`]: #channelwithstorescopedata | |
| 1938 | 1975 | [`child_process.spawn()`]: child_process.md#child_processspawncommand-args-options | |
| 1976 | + [`database.close()`]: sqlite.md#databaseclose | ||
| 1939 | 1977 | [`diagnostics_channel.channel(name)`]: #diagnostics_channelchannelname | |
| 1940 | 1978 | [`diagnostics_channel.subscribe(name, onMessage)`]: #diagnostics_channelsubscribename-onmessage | |
| 1941 | 1979 | [`diagnostics_channel.tracingChannel()`]: #diagnostics_channeltracingchannelnameorchannels | |
@@ -1945,6 +1983,7 @@ Emitted when a new thread is created. | |||
| 1945 | 1983 | [`net.Server.listen()`]: net.md#serverlisten | |
| 1946 | 1984 | [`process.execve()`]: process.md#processexecvefile-args-env | |
| 1947 | 1985 | [`start` event]: #startevent | |
| 1986 | + [`statement.close()`]: sqlite.md#statementclose | ||
| 1948 | 1987 | [`worker_threads.locks`]: worker_threads.md#worker_threadslocks | |
| 1949 | 1988 | [context loss]: async_context.md#troubleshooting-context-loss | |
| 1950 | 1989 | [thenable object]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise#thenables | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -30,7 +30,9 @@ import sqlite from 'node:sqlite'; | |||
| 30 | 30 | const sqlite = require('node:sqlite'); | |
| 31 | 31 | ``` | |
| 32 | 32 | ||
| 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. | ||
| 34 | 36 | ||
| 35 | 37 | The following example shows the basic usage of the `node:sqlite` module to open | |
| 36 | 38 | an in-memory database, write data to the database, and then read the data back. | |
@@ -309,8 +311,8 @@ added: v22.5.0 | |||
| 309 | 311 | Closes the database connection. An exception is thrown if the database is not | |
| 310 | 312 | open. An [`ERR_INVALID_STATE`][] error is thrown if the method is called while | |
| 311 | 313 | 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()`][]. | ||
| 314 | 316 | ||
| 315 | 317 | ### `database.loadExtension(path[, entryPoint])` | |
| 316 | 318 | ||
@@ -1114,7 +1116,12 @@ added: REPLACEME | |||
| 1114 | 1116 | --> | |
| 1115 | 1117 | ||
| 1116 | 1118 | 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()`][]. | ||
| 1118 | 1125 | ||
| 1119 | 1126 | ### `statement.columns()` | |
| 1120 | 1127 | ||
@@ -1361,7 +1368,9 @@ added: REPLACEME | |||
| 1361 | 1368 | --> | |
| 1362 | 1369 | ||
| 1363 | 1370 | 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()`][]. | ||
| 1365 | 1374 | ||
| 1366 | 1375 | ### `statement.stat(counter)` | |
| 1367 | 1376 | ||
@@ -1882,6 +1891,7 @@ callback function to indicate what type of operation is being authorized. | |||
| 1882 | 1891 | [Run-Time Limits]: https://www.sqlite.org/c3ref/limit.html | |
| 1883 | 1892 | [SQL injection]: https://en.wikipedia.org/wiki/SQL_injection | |
| 1884 | 1893 | [Type conversion between JavaScript and SQLite]: #type-conversion-between-javascript-and-sqlite | |
| 1894 | + [`'sqlite.db.query'`]: diagnostics_channel.md#event-sqlitedbquery | ||
| 1885 | 1895 | [`ATTACH DATABASE`]: https://www.sqlite.org/lang_attach.html | |
| 1886 | 1896 | [`ERR_INVALID_STATE`]: errors.md#err_invalid_state | |
| 1887 | 1897 | [`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. | |||
| 1895 | 1905 | [`database.createTagStore()`]: #databasecreatetagstoremaxsize | |
| 1896 | 1906 | [`database.serialize()`]: #databaseserializedbname | |
| 1897 | 1907 | [`database.setAuthorizer()`]: #databasesetauthorizercallback | |
| 1908 | + [`diagnostics_channel`]: diagnostics_channel.md | ||
| 1898 | 1909 | [`sqlite3_backup_finish()`]: https://www.sqlite.org/c3ref/backup_finish.html#sqlite3backupfinish | |
| 1899 | 1910 | [`sqlite3_backup_init()`]: https://www.sqlite.org/c3ref/backup_finish.html#sqlite3backupinit | |
| 1900 | 1911 | [`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. | |||
| 1926 | 1937 | [`sqlite3session_create()`]: https://www.sqlite.org/session/sqlite3session_create.html | |
| 1927 | 1938 | [`sqlite3session_delete()`]: https://www.sqlite.org/session/sqlite3session_delete.html | |
| 1928 | 1939 | [`sqlite3session_patchset()`]: https://www.sqlite.org/session/sqlite3session_patchset.html | |
| 1940 | + [`statement.close()`]: #statementclose | ||
| 1929 | 1941 | [`statement.setAllowBareNamedParameters()`]: #statementsetallowbarenamedparametersenabled | |
| 1930 | 1942 | [`statement.setAllowUnknownNamedParameters()`]: #statementsetallowunknownnamedparametersenabled | |
| 1931 | 1943 | [`statement.stat()`]: #statementstatcounter | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -73,11 +73,19 @@ function markActive(channel) { | |||
| 73 | 73 | ObjectSetPrototypeOf(channel, ActiveChannel.prototype); | |
| 74 | 74 | channel._subscribers = []; | |
| 75 | 75 | channel._stores = new SafeMap(); | |
| 76 | + | ||
| 77 | + // Notify native modules that this channel just got its first subscriber. | ||
| 78 | + if (channel._index !== undefined) | ||
| 79 | + dc_binding.notifyChannelActive(channel._index); | ||
| 76 | 80 | } | |
| 77 | 81 | ||
| 78 | 82 | function maybeMarkInactive(channel) { | |
| 79 | 83 | // When there are no more active subscribers or bound, restore to fast prototype. | |
| 80 | 84 | 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 | + | ||
| 81 | 89 | // eslint-disable-next-line no-use-before-define | |
| 82 | 90 | ObjectSetPrototypeOf(channel, Channel.prototype); | |
| 83 | 91 | channel._subscribers = undefined; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -24,7 +24,8 @@ namespace node { | |||
| 24 | 24 | #define UNSERIALIZABLE_BINDING_TYPES(V) \ | |
| 25 | 25 | V(http2_binding_data, http2::BindingData) \ | |
| 26 | 26 | 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) | ||
| 28 | 29 | ||
| 29 | 30 | // List of (non-binding) BaseObjects that are serializable in the snapshot. | |
| 30 | 31 | // The first argument should match what the type passes to | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -143,6 +143,7 @@ | |||
| 143 | 143 | V(crypto_rsa_pss_string, "rsa-pss") \ | |
| 144 | 144 | V(cwd_string, "cwd") \ | |
| 145 | 145 | V(data_string, "data") \ | |
| 146 | + V(database_string, "database") \ | ||
| 146 | 147 | V(default_is_true_string, "defaultIsTrue") \ | |
| 147 | 148 | V(defensive_string, "defensive") \ | |
| 148 | 149 | V(deserialize_info_string, "deserializeInfo") \ | |
@@ -359,6 +360,7 @@ | |||
| 359 | 360 | V(source_map_url_string, "sourceMapURL") \ | |
| 360 | 361 | V(source_url_string, "sourceURL") \ | |
| 361 | 362 | V(specifier_string, "specifier") \ | |
| 363 | + V(sql_string, "sql") \ | ||
| 362 | 364 | V(stack_string, "stack") \ | |
| 363 | 365 | V(start_string, "start") \ | |
| 364 | 366 | V(state_string, "state") \ | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -127,10 +127,38 @@ void BindingData::Deserialize(Local<Context> context, | |||
| 127 | 127 | CHECK_NOT_NULL(binding); | |
| 128 | 128 | } | |
| 129 | 129 | ||
| 130 | + void BindingData::SetChannelStatusCallback(uint32_t index, | ||
| 131 | + ChannelStatusCallback cb) { | ||
| 132 | + channel_status_callbacks_[index] = std::move(cb); | ||
| 133 | + } | ||
| 134 | + | ||
| 135 | + void BindingData::NotifyChannelActive(const FunctionCallbackInfo<Value>& args) { | ||
| 136 | + Realm* realm = Realm::GetCurrent(args); | ||
| 137 | + BindingData* binding = realm->GetBindingData<BindingData>(); | ||
| 138 | + if (binding == nullptr) return; | ||
| 139 | + CHECK(args[0]->IsUint32()); | ||
| 140 | + uint32_t index = args[0].As<v8::Uint32>()->Value(); | ||
| 141 | + auto it = binding->channel_status_callbacks_.find(index); | ||
| 142 | + if (it != binding->channel_status_callbacks_.end()) it->second(true); | ||
| 143 | + } | ||
| 144 | + | ||
| 145 | + void BindingData::NotifyChannelInactive( | ||
| 146 | + const FunctionCallbackInfo<Value>& args) { | ||
| 147 | + Realm* realm = Realm::GetCurrent(args); | ||
| 148 | + BindingData* binding = realm->GetBindingData<BindingData>(); | ||
| 149 | + if (binding == nullptr) return; | ||
| 150 | + CHECK(args[0]->IsUint32()); | ||
| 151 | + uint32_t index = args[0].As<v8::Uint32>()->Value(); | ||
| 152 | + auto it = binding->channel_status_callbacks_.find(index); | ||
| 153 | + if (it != binding->channel_status_callbacks_.end()) it->second(false); | ||
| 154 | + } | ||
| 155 | + | ||
| 130 | 156 | void BindingData::CreatePerIsolateProperties(IsolateData* isolate_data, | |
| 131 | 157 | Local<ObjectTemplate> target) { | |
| 132 | 158 | Isolate* isolate = isolate_data->isolate(); | |
| 133 | 159 | SetMethod(isolate, target, "linkNativeChannel", LinkNativeChannel); | |
| 160 | + SetMethod(isolate, target, "notifyChannelActive", NotifyChannelActive); | ||
| 161 | + SetMethod(isolate, target, "notifyChannelInactive", NotifyChannelInactive); | ||
| 134 | 162 | } | |
| 135 | 163 | ||
| 136 | 164 | void BindingData::CreatePerContextProperties(Local<Object> target, | |
@@ -145,6 +173,8 @@ void BindingData::CreatePerContextProperties(Local<Object> target, | |||
| 145 | 173 | void BindingData::RegisterExternalReferences( | |
| 146 | 174 | ExternalReferenceRegistry* registry) { | |
| 147 | 175 | registry->Register(LinkNativeChannel); | |
| 176 | + registry->Register(NotifyChannelActive); | ||
| 177 | + registry->Register(NotifyChannelInactive); | ||
| 148 | 178 | } | |
| 149 | 179 | ||
| 150 | 180 | Channel::Channel(Environment* env, | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -4,6 +4,7 @@ | |||
| 4 | 4 | #if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS | |
| 5 | 5 | ||
| 6 | 6 | #include <cinttypes> | |
| 7 | + #include <functional> | ||
| 7 | 8 | #include <string> | |
| 8 | 9 | #include <unordered_map> | |
| 9 | 10 | #include <vector> | |
@@ -52,6 +53,14 @@ class BindingData : public SnapshotableObject { | |||
| 52 | 53 | static void LinkNativeChannel( | |
| 53 | 54 | const v8::FunctionCallbackInfo<v8::Value>& args); | |
| 54 | 55 | ||
| 56 | + using ChannelStatusCallback = std::function<void(bool is_active)>; | ||
| 57 | + void SetChannelStatusCallback(uint32_t index, ChannelStatusCallback cb); | ||
| 58 | + | ||
| 59 | + static void NotifyChannelActive( | ||
| 60 | + const v8::FunctionCallbackInfo<v8::Value>& args); | ||
| 61 | + static void NotifyChannelInactive( | ||
| 62 | + const v8::FunctionCallbackInfo<v8::Value>& args); | ||
| 63 | + | ||
| 55 | 64 | static void CreatePerIsolateProperties(IsolateData* isolate_data, | |
| 56 | 65 | v8::Local<v8::ObjectTemplate> target); | |
| 57 | 66 | static void CreatePerContextProperties(v8::Local<v8::Object> target, | |
@@ -62,6 +71,7 @@ class BindingData : public SnapshotableObject { | |||
| 62 | 71 | ||
| 63 | 72 | private: | |
| 64 | 73 | InternalFieldInfo* internal_field_info_ = nullptr; | |
| 74 | + std::unordered_map<uint32_t, ChannelStatusCallback> channel_status_callbacks_; | ||
| 65 | 75 | }; | |
| 66 | 76 | ||
| 67 | 77 | class Channel : public BaseObject { | |
| Back | FazBrowse Home | New Git URL |
0 commit comments