mssql 12.3.1 → 12.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +98 -0
- package/lib/base/connection-pool.js +61 -10
- package/lib/base/index.js +2 -0
- package/lib/base/prepared-statement.js +62 -14
- package/lib/base/request.js +137 -65
- package/lib/base/transaction.js +32 -1
- package/lib/diagnostics.js +153 -0
- package/lib/msnodesqlv8/connection-pool.js +15 -2
- package/lib/tedious/connection-pool.js +13 -0
- package/lib/tedious/transaction.js +5 -0
- package/lib/udt.js +25 -23
- package/lib/utils.js +10 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -185,6 +185,7 @@ const config = {
|
|
|
185
185
|
* [Metadata](#metadata)
|
|
186
186
|
* [Data Types](#data-types)
|
|
187
187
|
* [SQL injection](#sql-injection)
|
|
188
|
+
* [Diagnostics Channel](#diagnostics-channel)
|
|
188
189
|
* [Contributing](https://github.com/tediousjs/node-mssql/wiki/Contributing)
|
|
189
190
|
* [11.x to 12.x changes](#11x-to-12x-changes)
|
|
190
191
|
* [10.x to 11.x changes](#10x-to-11x-changes)
|
|
@@ -2220,6 +2221,103 @@ request.query('select @myval as myval', (err, result) => {
|
|
|
2220
2221
|
})
|
|
2221
2222
|
```
|
|
2222
2223
|
|
|
2224
|
+
## Diagnostics Channel
|
|
2225
|
+
|
|
2226
|
+
node-mssql publishes telemetry through Node.js [`diagnostics_channel`](https://nodejs.org/api/diagnostics_channel.html), enabling APM tools and custom instrumentation to observe queries, connections, and internal events without modifying application code. When no subscribers are active, overhead is near-zero.
|
|
2227
|
+
|
|
2228
|
+
All channel name constants are exported from the package:
|
|
2229
|
+
|
|
2230
|
+
```js
|
|
2231
|
+
const { CHANNELS } = require('mssql')
|
|
2232
|
+
```
|
|
2233
|
+
|
|
2234
|
+
### TracingChannels (async lifecycle)
|
|
2235
|
+
|
|
2236
|
+
These use [`TracingChannel`](https://nodejs.org/api/diagnostics_channel.html#class-tracingchannel) to wrap async operations, emitting `start`, `end`, `asyncStart`, `asyncEnd`, and `error` sub-events. Subscribe via `tracing:<name>:<event>`:
|
|
2237
|
+
|
|
2238
|
+
```js
|
|
2239
|
+
const dc = require('node:diagnostics_channel')
|
|
2240
|
+
const { CHANNELS } = require('mssql')
|
|
2241
|
+
|
|
2242
|
+
dc.subscribe(`tracing:${CHANNELS.TRACE_QUERY}:start`, ({ command, requestId }) => {
|
|
2243
|
+
console.log(`[${requestId}] Query: ${command}`)
|
|
2244
|
+
})
|
|
2245
|
+
|
|
2246
|
+
dc.subscribe(`tracing:${CHANNELS.TRACE_QUERY}:error`, ({ requestId, error }) => {
|
|
2247
|
+
console.error(`[${requestId}] Failed:`, error.message)
|
|
2248
|
+
})
|
|
2249
|
+
```
|
|
2250
|
+
|
|
2251
|
+
| Constant | Channel name | Wraps |
|
|
2252
|
+
|---|---|---|
|
|
2253
|
+
| `TRACE_QUERY` | `mssql:query` | `request.query()` |
|
|
2254
|
+
| `TRACE_BATCH` | `mssql:batch` | `request.batch()` |
|
|
2255
|
+
| `TRACE_EXECUTE` | `mssql:execute` | `request.execute()` |
|
|
2256
|
+
| `TRACE_BULK` | `mssql:bulk` | `request.bulk()` |
|
|
2257
|
+
| `TRACE_CONNECT` | `mssql:connect` | `pool.connect()` |
|
|
2258
|
+
| `TRACE_POOL_ACQUIRE` | `mssql:pool:acquire` | Pool connection acquire (wait time) |
|
|
2259
|
+
| `TRACE_PREPARED_STATEMENT_PREPARE` | `mssql:prepared-statement:prepare` | `ps.prepare()` |
|
|
2260
|
+
| `TRACE_PREPARED_STATEMENT_EXECUTE` | `mssql:prepared-statement:execute` | `ps.execute()` |
|
|
2261
|
+
|
|
2262
|
+
TracingChannel contexts include identifiers (`requestId`, `poolId`), operation details (SQL text, procedure name, parameter names), and — on completion — `result` or `error`. Parameter **values** are never included (only their names).
|
|
2263
|
+
|
|
2264
|
+
> **Note on SQL text in trace contexts:** `command` / `procedure` / `statement` fields contain the SQL text as sent to the server, to support OTel `db.query.text` conventions. Because node-mssql is parameterised-query-first, user-supplied values flow through `parameters` and do not appear in the SQL text. Avoid hard-coding credentials, tokens, or PII as inline SQL literals — anything hard-coded into a raw query will appear verbatim in trace contexts.
|
|
2265
|
+
|
|
2266
|
+
> **Note on identifiers:** `connectionId`, `poolId`, `requestId`, `transactionId`, and `preparedStatementId` are monotonically increasing integers scoped to the current node process. They are not stable across restarts and cannot be used to correlate activity across processes.
|
|
2267
|
+
|
|
2268
|
+
> **Note:** TracingChannel instrumentation fires for both the promise and callback APIs. The callback API is traced via Node's `TracingChannel#traceCallback`, which emits the same `start` / `end` / `asyncStart` / `asyncEnd` / `error` sub-events as the promise path, so subscribers do not need to branch by API style. Point-event channels (connection, transaction, pool lifecycle) likewise fire regardless of API style.
|
|
2269
|
+
|
|
2270
|
+
### Point-event channels
|
|
2271
|
+
|
|
2272
|
+
These emit single events at state transitions via `dc.subscribe()`:
|
|
2273
|
+
|
|
2274
|
+
```js
|
|
2275
|
+
dc.subscribe(CHANNELS.CONNECTION_RELEASE, ({ connectionId, poolId }) => {
|
|
2276
|
+
console.log(`Pool ${poolId}: connection ${connectionId} released`)
|
|
2277
|
+
})
|
|
2278
|
+
```
|
|
2279
|
+
|
|
2280
|
+
| Constant | Channel name | Description |
|
|
2281
|
+
|---|---|---|
|
|
2282
|
+
| `CONNECTION_ACQUIRE` | `mssql:connection:acquire` | Connection borrowed from pool |
|
|
2283
|
+
| `CONNECTION_RELEASE` | `mssql:connection:release` | Connection returned to pool |
|
|
2284
|
+
| `CONNECTION_CREATE` | `mssql:connection:create` | New connection created in pool |
|
|
2285
|
+
| `CONNECTION_DESTROY` | `mssql:connection:destroy` | Connection destroyed |
|
|
2286
|
+
| `POOL_CLOSE` | `mssql:pool:close` | Pool closed (includes `reason`: `'closed'` or `'error'`; `error` on failure) |
|
|
2287
|
+
| `TRANSACTION_BEGIN` | `mssql:transaction:begin` | Transaction begun (includes numeric `isolationLevel` and `isolationLevelName`) |
|
|
2288
|
+
| `TRANSACTION_COMMIT` | `mssql:transaction:commit` | Transaction committed |
|
|
2289
|
+
| `TRANSACTION_ROLLBACK` | `mssql:transaction:rollback` | Transaction rolled back (includes `aborted` flag) |
|
|
2290
|
+
| `REQUEST_CANCEL` | `mssql:request:cancel` | Request cancelled |
|
|
2291
|
+
| `PREPARED_STATEMENT_UNPREPARE` | `mssql:prepared-statement:unprepare` | Prepared statement released |
|
|
2292
|
+
|
|
2293
|
+
### Example: OpenTelemetry Spans
|
|
2294
|
+
|
|
2295
|
+
```js
|
|
2296
|
+
const dc = require('node:diagnostics_channel')
|
|
2297
|
+
const { trace, SpanKind, SpanStatusCode } = require('@opentelemetry/api')
|
|
2298
|
+
const { CHANNELS } = require('mssql')
|
|
2299
|
+
|
|
2300
|
+
const tracer = trace.getTracer('mssql')
|
|
2301
|
+
const queryTC = dc.tracingChannel(CHANNELS.TRACE_QUERY)
|
|
2302
|
+
|
|
2303
|
+
queryTC.subscribe({
|
|
2304
|
+
start (ctx) {
|
|
2305
|
+
ctx.span = tracer.startSpan('mssql.query', {
|
|
2306
|
+
kind: SpanKind.CLIENT,
|
|
2307
|
+
attributes: { 'db.system': 'mssql', 'db.query.text': ctx.command },
|
|
2308
|
+
})
|
|
2309
|
+
},
|
|
2310
|
+
asyncEnd (ctx) { ctx.span?.end() },
|
|
2311
|
+
error (ctx) {
|
|
2312
|
+
if (ctx.span) {
|
|
2313
|
+
ctx.span.recordException(ctx.error)
|
|
2314
|
+
ctx.span.setStatus({ code: SpanStatusCode.ERROR })
|
|
2315
|
+
ctx.span.end()
|
|
2316
|
+
}
|
|
2317
|
+
},
|
|
2318
|
+
})
|
|
2319
|
+
```
|
|
2320
|
+
|
|
2223
2321
|
## 11.x to 12.x changes
|
|
2224
2322
|
|
|
2225
2323
|
- Config objects are no longer cloned by the library. Mutating a config object after passing it to a `ConnectionPool` results in undefined behaviour.
|
|
@@ -8,6 +8,7 @@ const { IDS } = require('../utils')
|
|
|
8
8
|
const ConnectionError = require('../error/connection-error')
|
|
9
9
|
const shared = require('../shared')
|
|
10
10
|
const { MSSQLError } = require('../error')
|
|
11
|
+
const { CHANNELS, tracePromise, traceCallback, publish } = require('../diagnostics')
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* Class ConnectionPool.
|
|
@@ -371,10 +372,26 @@ class ConnectionPool extends EventEmitter {
|
|
|
371
372
|
*/
|
|
372
373
|
|
|
373
374
|
acquire (requester, callback) {
|
|
374
|
-
const
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
375
|
+
const requestId = IDS.get(requester)
|
|
376
|
+
const poolId = IDS.get(this)
|
|
377
|
+
|
|
378
|
+
const acquirePromise = tracePromise(CHANNELS.TRACE_POOL_ACQUIRE, () => {
|
|
379
|
+
return shared.Promise.resolve(this._acquire()).catch(err => {
|
|
380
|
+
this.emit('error', err)
|
|
381
|
+
throw err
|
|
382
|
+
}).then(connection => {
|
|
383
|
+
publish(CHANNELS.CONNECTION_ACQUIRE, () => ({
|
|
384
|
+
connectionId: IDS.get(connection),
|
|
385
|
+
requestId,
|
|
386
|
+
poolId
|
|
387
|
+
}))
|
|
388
|
+
return connection
|
|
389
|
+
})
|
|
390
|
+
}, () => ({
|
|
391
|
+
poolId,
|
|
392
|
+
requestId
|
|
393
|
+
}))
|
|
394
|
+
|
|
378
395
|
if (typeof callback === 'function') {
|
|
379
396
|
acquirePromise.then(connection => callback(null, connection, this.config)).catch(callback)
|
|
380
397
|
return this
|
|
@@ -403,6 +420,11 @@ class ConnectionPool extends EventEmitter {
|
|
|
403
420
|
release (connection) {
|
|
404
421
|
debug('connection(%d): released', IDS.get(connection))
|
|
405
422
|
|
|
423
|
+
publish(CHANNELS.CONNECTION_RELEASE, () => ({
|
|
424
|
+
connectionId: IDS.get(connection),
|
|
425
|
+
poolId: IDS.get(this)
|
|
426
|
+
}))
|
|
427
|
+
|
|
406
428
|
if (this.pool) {
|
|
407
429
|
this.pool.release(connection)
|
|
408
430
|
}
|
|
@@ -418,16 +440,36 @@ class ConnectionPool extends EventEmitter {
|
|
|
418
440
|
|
|
419
441
|
connect (callback) {
|
|
420
442
|
if (typeof callback === 'function') {
|
|
421
|
-
this._connect(
|
|
443
|
+
traceCallback(CHANNELS.TRACE_CONNECT, this._connect, 0, () => ({
|
|
444
|
+
server: this.config.server,
|
|
445
|
+
port: this.config.port,
|
|
446
|
+
database: this.config.database,
|
|
447
|
+
poolId: IDS.get(this),
|
|
448
|
+
poolConfig: {
|
|
449
|
+
min: (this.config.pool && this.config.pool.min) || 0,
|
|
450
|
+
max: (this.config.pool && this.config.pool.max) || 10
|
|
451
|
+
}
|
|
452
|
+
}), this, [callback])
|
|
422
453
|
return this
|
|
423
454
|
}
|
|
424
455
|
|
|
425
|
-
return
|
|
426
|
-
return
|
|
427
|
-
|
|
428
|
-
|
|
456
|
+
return tracePromise(CHANNELS.TRACE_CONNECT, () => {
|
|
457
|
+
return new shared.Promise((resolve, reject) => {
|
|
458
|
+
return this._connect(err => {
|
|
459
|
+
if (err) return reject(err)
|
|
460
|
+
resolve(this)
|
|
461
|
+
})
|
|
429
462
|
})
|
|
430
|
-
})
|
|
463
|
+
}, () => ({
|
|
464
|
+
server: this.config.server,
|
|
465
|
+
port: this.config.port,
|
|
466
|
+
database: this.config.database,
|
|
467
|
+
poolId: IDS.get(this),
|
|
468
|
+
poolConfig: {
|
|
469
|
+
min: (this.config.pool && this.config.pool.min) || 0,
|
|
470
|
+
max: (this.config.pool && this.config.pool.max) || 10
|
|
471
|
+
}
|
|
472
|
+
}))
|
|
431
473
|
}
|
|
432
474
|
|
|
433
475
|
/**
|
|
@@ -559,11 +601,20 @@ class ConnectionPool extends EventEmitter {
|
|
|
559
601
|
|
|
560
602
|
this.pool.destroy().then(() => {
|
|
561
603
|
debug('pool(%d): pool closed, removing pool reference and executing close callbacks', IDS.get(this))
|
|
604
|
+
publish(CHANNELS.POOL_CLOSE, () => ({
|
|
605
|
+
poolId: IDS.get(this),
|
|
606
|
+
reason: 'closed'
|
|
607
|
+
}))
|
|
562
608
|
this.pool = null
|
|
563
609
|
this._closeStack.forEach(cb => {
|
|
564
610
|
setImmediate(cb, null)
|
|
565
611
|
})
|
|
566
612
|
}).catch(err => {
|
|
613
|
+
publish(CHANNELS.POOL_CLOSE, () => ({
|
|
614
|
+
poolId: IDS.get(this),
|
|
615
|
+
reason: 'error',
|
|
616
|
+
error: err
|
|
617
|
+
}))
|
|
567
618
|
this.pool = null
|
|
568
619
|
this._closeStack.forEach(cb => {
|
|
569
620
|
setImmediate(cb, err)
|
package/lib/base/index.js
CHANGED
|
@@ -10,6 +10,7 @@ const Table = require('../table')
|
|
|
10
10
|
const ISOLATION_LEVEL = require('../isolationlevel')
|
|
11
11
|
const { TYPES } = require('../datatypes')
|
|
12
12
|
const { connect, close, on, off, removeListener, query, batch } = require('../global-connection')
|
|
13
|
+
const { CHANNELS } = require('../diagnostics')
|
|
13
14
|
|
|
14
15
|
module.exports = {
|
|
15
16
|
ConnectionPool,
|
|
@@ -31,6 +32,7 @@ module.exports = {
|
|
|
31
32
|
Table,
|
|
32
33
|
ISOLATION_LEVEL,
|
|
33
34
|
TYPES,
|
|
35
|
+
CHANNELS,
|
|
34
36
|
MAX: 65535, // (1 << 16) - 1
|
|
35
37
|
map: shared.map,
|
|
36
38
|
getTypeByValue: shared.getTypeByValue,
|
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
const debug = require('debug')('mssql:base')
|
|
4
4
|
const { EventEmitter } = require('node:events')
|
|
5
|
-
const { IDS, objectHasProperty } = require('../utils')
|
|
5
|
+
const { IDS, objectHasProperty, getPoolId } = require('../utils')
|
|
6
6
|
const globalConnection = require('../global-connection')
|
|
7
7
|
const { TransactionError, PreparedStatementError } = require('../error')
|
|
8
8
|
const shared = require('../shared')
|
|
9
9
|
const { TYPES, declare } = require('../datatypes')
|
|
10
|
+
const { CHANNELS, tracePromise, traceCallback, publish } = require('../diagnostics')
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Class PreparedStatement.
|
|
@@ -194,16 +195,28 @@ class PreparedStatement extends EventEmitter {
|
|
|
194
195
|
|
|
195
196
|
prepare (statement, callback) {
|
|
196
197
|
if (typeof callback === 'function') {
|
|
197
|
-
this._prepare
|
|
198
|
+
traceCallback(CHANNELS.TRACE_PREPARED_STATEMENT_PREPARE, this._prepare, 1, () => ({
|
|
199
|
+
statement: statement || this.statement,
|
|
200
|
+
parameters: Object.keys(this.parameters),
|
|
201
|
+
preparedStatementId: IDS.get(this),
|
|
202
|
+
poolId: getPoolId(this)
|
|
203
|
+
}), this, [statement, callback])
|
|
198
204
|
return this
|
|
199
205
|
}
|
|
200
206
|
|
|
201
|
-
return
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
207
|
+
return tracePromise(CHANNELS.TRACE_PREPARED_STATEMENT_PREPARE, () => {
|
|
208
|
+
return new shared.Promise((resolve, reject) => {
|
|
209
|
+
this._prepare(statement, err => {
|
|
210
|
+
if (err) return reject(err)
|
|
211
|
+
resolve(this)
|
|
212
|
+
})
|
|
205
213
|
})
|
|
206
|
-
})
|
|
214
|
+
}, () => ({
|
|
215
|
+
statement: statement || this.statement,
|
|
216
|
+
parameters: Object.keys(this.parameters),
|
|
217
|
+
preparedStatementId: IDS.get(this),
|
|
218
|
+
poolId: getPoolId(this)
|
|
219
|
+
}))
|
|
207
220
|
}
|
|
208
221
|
|
|
209
222
|
/**
|
|
@@ -233,6 +246,7 @@ class PreparedStatement extends EventEmitter {
|
|
|
233
246
|
this._acquiredConfig = config
|
|
234
247
|
|
|
235
248
|
const req = new shared.driver.Request(this)
|
|
249
|
+
req._internal = true
|
|
236
250
|
req.stream = false
|
|
237
251
|
req.output('handle', TYPES.Int)
|
|
238
252
|
req.input('params', TYPES.NVarChar, ((() => {
|
|
@@ -276,15 +290,35 @@ class PreparedStatement extends EventEmitter {
|
|
|
276
290
|
|
|
277
291
|
execute (values, callback) {
|
|
278
292
|
if (this.stream || (typeof callback === 'function')) {
|
|
279
|
-
|
|
293
|
+
if (typeof callback !== 'function') {
|
|
294
|
+
// Stream mode without a callback: no async boundary for traceCallback
|
|
295
|
+
// to hook — fall through to the untraced call. Subscribers interested
|
|
296
|
+
// in streaming completion should listen to Request events.
|
|
297
|
+
return this._execute(values, callback)
|
|
298
|
+
}
|
|
299
|
+
return traceCallback(CHANNELS.TRACE_PREPARED_STATEMENT_EXECUTE, this._execute, 1, () => ({
|
|
300
|
+
statement: this.statement,
|
|
301
|
+
parameters: Object.keys(this.parameters),
|
|
302
|
+
handle: this._handle,
|
|
303
|
+
preparedStatementId: IDS.get(this),
|
|
304
|
+
poolId: getPoolId(this)
|
|
305
|
+
}), this, [values, callback])
|
|
280
306
|
}
|
|
281
307
|
|
|
282
|
-
return
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
308
|
+
return tracePromise(CHANNELS.TRACE_PREPARED_STATEMENT_EXECUTE, () => {
|
|
309
|
+
return new shared.Promise((resolve, reject) => {
|
|
310
|
+
this._execute(values, (err, recordset) => {
|
|
311
|
+
if (err) return reject(err)
|
|
312
|
+
resolve(recordset)
|
|
313
|
+
})
|
|
286
314
|
})
|
|
287
|
-
})
|
|
315
|
+
}, () => ({
|
|
316
|
+
statement: this.statement,
|
|
317
|
+
parameters: Object.keys(this.parameters),
|
|
318
|
+
handle: this._handle,
|
|
319
|
+
preparedStatementId: IDS.get(this),
|
|
320
|
+
poolId: getPoolId(this)
|
|
321
|
+
}))
|
|
288
322
|
}
|
|
289
323
|
|
|
290
324
|
/**
|
|
@@ -295,6 +329,7 @@ class PreparedStatement extends EventEmitter {
|
|
|
295
329
|
|
|
296
330
|
_execute (values, callback) {
|
|
297
331
|
const req = new shared.driver.Request(this)
|
|
332
|
+
req._internal = true
|
|
298
333
|
req.stream = this.stream
|
|
299
334
|
req.arrayRowMode = this.arrayRowMode
|
|
300
335
|
req.input('handle', TYPES.Int, this._handle)
|
|
@@ -334,13 +369,25 @@ class PreparedStatement extends EventEmitter {
|
|
|
334
369
|
|
|
335
370
|
unprepare (callback) {
|
|
336
371
|
if (typeof callback === 'function') {
|
|
337
|
-
this._unprepare(
|
|
372
|
+
this._unprepare(err => {
|
|
373
|
+
if (!err) {
|
|
374
|
+
publish(CHANNELS.PREPARED_STATEMENT_UNPREPARE, () => ({
|
|
375
|
+
preparedStatementId: IDS.get(this),
|
|
376
|
+
poolId: getPoolId(this)
|
|
377
|
+
}))
|
|
378
|
+
}
|
|
379
|
+
callback(err)
|
|
380
|
+
})
|
|
338
381
|
return this
|
|
339
382
|
}
|
|
340
383
|
|
|
341
384
|
return new shared.Promise((resolve, reject) => {
|
|
342
385
|
this._unprepare(err => {
|
|
343
386
|
if (err) return reject(err)
|
|
387
|
+
publish(CHANNELS.PREPARED_STATEMENT_UNPREPARE, () => ({
|
|
388
|
+
preparedStatementId: IDS.get(this),
|
|
389
|
+
poolId: getPoolId(this)
|
|
390
|
+
}))
|
|
344
391
|
resolve()
|
|
345
392
|
})
|
|
346
393
|
})
|
|
@@ -363,6 +410,7 @@ class PreparedStatement extends EventEmitter {
|
|
|
363
410
|
}
|
|
364
411
|
|
|
365
412
|
const req = new shared.driver.Request(this)
|
|
413
|
+
req._internal = true
|
|
366
414
|
req.stream = false
|
|
367
415
|
req.input('handle', TYPES.Int, this._handle)
|
|
368
416
|
req.execute('sp_unprepare', err => {
|
package/lib/base/request.js
CHANGED
|
@@ -3,11 +3,12 @@
|
|
|
3
3
|
const debug = require('debug')('mssql:base')
|
|
4
4
|
const { EventEmitter } = require('node:events')
|
|
5
5
|
const { Readable } = require('node:stream')
|
|
6
|
-
const { IDS, objectHasProperty } = require('../utils')
|
|
6
|
+
const { IDS, objectHasProperty, getPoolId } = require('../utils')
|
|
7
7
|
const globalConnection = require('../global-connection')
|
|
8
8
|
const { RequestError, ConnectionError } = require('../error')
|
|
9
9
|
const { TYPES } = require('../datatypes')
|
|
10
10
|
const shared = require('../shared')
|
|
11
|
+
const { CHANNELS, tracePromise, traceCallback, publish } = require('../diagnostics')
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* Class Request.
|
|
@@ -37,6 +38,7 @@ class Request extends EventEmitter {
|
|
|
37
38
|
|
|
38
39
|
this.canceled = false
|
|
39
40
|
this._paused = false
|
|
41
|
+
this._internal = false
|
|
40
42
|
this.parent = parent || globalConnection.pool
|
|
41
43
|
this.parameters = {}
|
|
42
44
|
this.stream = null
|
|
@@ -218,6 +220,24 @@ class Request extends EventEmitter {
|
|
|
218
220
|
return this.output(name, type, value)
|
|
219
221
|
}
|
|
220
222
|
|
|
223
|
+
_getParameterNames () {
|
|
224
|
+
return Object.keys(this.parameters)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
_tracedPromise (channel, contextFactory, fn) {
|
|
228
|
+
if (this._internal) {
|
|
229
|
+
return fn()
|
|
230
|
+
}
|
|
231
|
+
return tracePromise(channel, fn, contextFactory)
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
_tracedCallback (channel, contextFactory, fn, position, args) {
|
|
235
|
+
if (this._internal) {
|
|
236
|
+
return fn.apply(this, args)
|
|
237
|
+
}
|
|
238
|
+
return traceCallback(channel, fn, position, contextFactory, this, args)
|
|
239
|
+
}
|
|
240
|
+
|
|
221
241
|
/**
|
|
222
242
|
* Execute the SQL batch.
|
|
223
243
|
*
|
|
@@ -232,7 +252,11 @@ class Request extends EventEmitter {
|
|
|
232
252
|
this.rowsAffected = 0
|
|
233
253
|
|
|
234
254
|
if (typeof callback === 'function') {
|
|
235
|
-
this.
|
|
255
|
+
this._tracedCallback(CHANNELS.TRACE_BATCH, () => ({
|
|
256
|
+
command: batch,
|
|
257
|
+
requestId: IDS.get(this),
|
|
258
|
+
poolId: getPoolId(this)
|
|
259
|
+
}), this._batch, 1, [batch, (err, recordsets, output, rowsAffected) => {
|
|
236
260
|
if (this.stream) {
|
|
237
261
|
if (err) this.emit('error', err)
|
|
238
262
|
err = null
|
|
@@ -250,7 +274,7 @@ class Request extends EventEmitter {
|
|
|
250
274
|
output,
|
|
251
275
|
rowsAffected
|
|
252
276
|
})
|
|
253
|
-
})
|
|
277
|
+
}])
|
|
254
278
|
return this
|
|
255
279
|
}
|
|
256
280
|
|
|
@@ -261,24 +285,31 @@ class Request extends EventEmitter {
|
|
|
261
285
|
batch = this._template(strings, values)
|
|
262
286
|
}
|
|
263
287
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
288
|
+
const batchCommand = batch
|
|
289
|
+
return this._tracedPromise(CHANNELS.TRACE_BATCH, () => ({
|
|
290
|
+
command: batchCommand,
|
|
291
|
+
requestId: IDS.get(this),
|
|
292
|
+
poolId: getPoolId(this)
|
|
293
|
+
}), () => {
|
|
294
|
+
return new shared.Promise((resolve, reject) => {
|
|
295
|
+
this._batch(batchCommand, (err, recordsets, output, rowsAffected) => {
|
|
296
|
+
if (this.stream) {
|
|
297
|
+
if (err) this.emit('error', err)
|
|
298
|
+
err = null
|
|
299
|
+
|
|
300
|
+
this.emit('done', {
|
|
301
|
+
output,
|
|
302
|
+
rowsAffected
|
|
303
|
+
})
|
|
304
|
+
}
|
|
269
305
|
|
|
270
|
-
|
|
306
|
+
if (err) return reject(err)
|
|
307
|
+
resolve({
|
|
308
|
+
recordsets,
|
|
309
|
+
recordset: recordsets && recordsets[0],
|
|
271
310
|
output,
|
|
272
311
|
rowsAffected
|
|
273
312
|
})
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
if (err) return reject(err)
|
|
277
|
-
resolve({
|
|
278
|
-
recordsets,
|
|
279
|
-
recordset: recordsets && recordsets[0],
|
|
280
|
-
output,
|
|
281
|
-
rowsAffected
|
|
282
313
|
})
|
|
283
314
|
})
|
|
284
315
|
})
|
|
@@ -324,7 +355,12 @@ class Request extends EventEmitter {
|
|
|
324
355
|
if (this.arrayRowMode === null && this.parent) this.arrayRowMode = this.parent.config.arrayRowMode
|
|
325
356
|
|
|
326
357
|
if (this.stream || typeof callback === 'function') {
|
|
327
|
-
this.
|
|
358
|
+
this._tracedCallback(CHANNELS.TRACE_BULK, () => ({
|
|
359
|
+
table: table.path || table.name,
|
|
360
|
+
rowCount: table.rows ? table.rows.length : 0,
|
|
361
|
+
requestId: IDS.get(this),
|
|
362
|
+
poolId: getPoolId(this)
|
|
363
|
+
}), this._bulk, 2, [table, options, (err, rowsAffected) => {
|
|
328
364
|
if (this.stream) {
|
|
329
365
|
if (err) this.emit('error', err)
|
|
330
366
|
return this.emit('done', {
|
|
@@ -336,15 +372,22 @@ class Request extends EventEmitter {
|
|
|
336
372
|
callback(null, {
|
|
337
373
|
rowsAffected
|
|
338
374
|
})
|
|
339
|
-
})
|
|
375
|
+
}])
|
|
340
376
|
return this
|
|
341
377
|
}
|
|
342
378
|
|
|
343
|
-
return
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
379
|
+
return this._tracedPromise(CHANNELS.TRACE_BULK, () => ({
|
|
380
|
+
table: table.path || table.name,
|
|
381
|
+
rowCount: table.rows ? table.rows.length : 0,
|
|
382
|
+
requestId: IDS.get(this),
|
|
383
|
+
poolId: getPoolId(this)
|
|
384
|
+
}), () => {
|
|
385
|
+
return new shared.Promise((resolve, reject) => {
|
|
386
|
+
this._bulk(table, options, (err, rowsAffected) => {
|
|
387
|
+
if (err) return reject(err)
|
|
388
|
+
resolve({
|
|
389
|
+
rowsAffected
|
|
390
|
+
})
|
|
348
391
|
})
|
|
349
392
|
})
|
|
350
393
|
})
|
|
@@ -429,7 +472,12 @@ class Request extends EventEmitter {
|
|
|
429
472
|
this.rowsAffected = 0
|
|
430
473
|
|
|
431
474
|
if (typeof callback === 'function') {
|
|
432
|
-
this.
|
|
475
|
+
this._tracedCallback(CHANNELS.TRACE_QUERY, () => ({
|
|
476
|
+
command,
|
|
477
|
+
parameters: this._getParameterNames(),
|
|
478
|
+
requestId: IDS.get(this),
|
|
479
|
+
poolId: getPoolId(this)
|
|
480
|
+
}), this._query, 1, [command, (err, recordsets, output, rowsAffected, columns) => {
|
|
433
481
|
if (this.stream) {
|
|
434
482
|
if (err) this.emit('error', err)
|
|
435
483
|
err = null
|
|
@@ -449,7 +497,7 @@ class Request extends EventEmitter {
|
|
|
449
497
|
}
|
|
450
498
|
if (this.arrayRowMode) result.columns = columns
|
|
451
499
|
callback(null, result)
|
|
452
|
-
})
|
|
500
|
+
}])
|
|
453
501
|
return this
|
|
454
502
|
}
|
|
455
503
|
|
|
@@ -460,27 +508,34 @@ class Request extends EventEmitter {
|
|
|
460
508
|
command = this._template(strings, values)
|
|
461
509
|
}
|
|
462
510
|
|
|
463
|
-
return
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
511
|
+
return this._tracedPromise(CHANNELS.TRACE_QUERY, () => ({
|
|
512
|
+
command,
|
|
513
|
+
parameters: this._getParameterNames(),
|
|
514
|
+
requestId: IDS.get(this),
|
|
515
|
+
poolId: getPoolId(this)
|
|
516
|
+
}), () => {
|
|
517
|
+
return new shared.Promise((resolve, reject) => {
|
|
518
|
+
this._query(command, (err, recordsets, output, rowsAffected, columns) => {
|
|
519
|
+
if (this.stream) {
|
|
520
|
+
if (err) this.emit('error', err)
|
|
521
|
+
err = null
|
|
522
|
+
|
|
523
|
+
this.emit('done', {
|
|
524
|
+
output,
|
|
525
|
+
rowsAffected
|
|
526
|
+
})
|
|
527
|
+
}
|
|
468
528
|
|
|
469
|
-
|
|
529
|
+
if (err) return reject(err)
|
|
530
|
+
const result = {
|
|
531
|
+
recordsets,
|
|
532
|
+
recordset: recordsets && recordsets[0],
|
|
470
533
|
output,
|
|
471
534
|
rowsAffected
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
const result = {
|
|
477
|
-
recordsets,
|
|
478
|
-
recordset: recordsets && recordsets[0],
|
|
479
|
-
output,
|
|
480
|
-
rowsAffected
|
|
481
|
-
}
|
|
482
|
-
if (this.arrayRowMode) result.columns = columns
|
|
483
|
-
resolve(result)
|
|
535
|
+
}
|
|
536
|
+
if (this.arrayRowMode) result.columns = columns
|
|
537
|
+
resolve(result)
|
|
538
|
+
})
|
|
484
539
|
})
|
|
485
540
|
})
|
|
486
541
|
}
|
|
@@ -518,7 +573,12 @@ class Request extends EventEmitter {
|
|
|
518
573
|
this.rowsAffected = 0
|
|
519
574
|
|
|
520
575
|
if (typeof callback === 'function') {
|
|
521
|
-
this.
|
|
576
|
+
this._tracedCallback(CHANNELS.TRACE_EXECUTE, () => ({
|
|
577
|
+
procedure: command,
|
|
578
|
+
parameters: this._getParameterNames(),
|
|
579
|
+
requestId: IDS.get(this),
|
|
580
|
+
poolId: getPoolId(this)
|
|
581
|
+
}), this._execute, 1, [command, (err, recordsets, output, returnValue, rowsAffected, columns) => {
|
|
522
582
|
if (this.stream) {
|
|
523
583
|
if (err) this.emit('error', err)
|
|
524
584
|
err = null
|
|
@@ -540,33 +600,40 @@ class Request extends EventEmitter {
|
|
|
540
600
|
}
|
|
541
601
|
if (this.arrayRowMode) result.columns = columns
|
|
542
602
|
callback(null, result)
|
|
543
|
-
})
|
|
603
|
+
}])
|
|
544
604
|
return this
|
|
545
605
|
}
|
|
546
606
|
|
|
547
|
-
return
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
607
|
+
return this._tracedPromise(CHANNELS.TRACE_EXECUTE, () => ({
|
|
608
|
+
procedure: command,
|
|
609
|
+
parameters: this._getParameterNames(),
|
|
610
|
+
requestId: IDS.get(this),
|
|
611
|
+
poolId: getPoolId(this)
|
|
612
|
+
}), () => {
|
|
613
|
+
return new shared.Promise((resolve, reject) => {
|
|
614
|
+
this._execute(command, (err, recordsets, output, returnValue, rowsAffected, columns) => {
|
|
615
|
+
if (this.stream) {
|
|
616
|
+
if (err) this.emit('error', err)
|
|
617
|
+
err = null
|
|
618
|
+
|
|
619
|
+
this.emit('done', {
|
|
620
|
+
output,
|
|
621
|
+
rowsAffected,
|
|
622
|
+
returnValue
|
|
623
|
+
})
|
|
624
|
+
}
|
|
552
625
|
|
|
553
|
-
|
|
626
|
+
if (err) return reject(err)
|
|
627
|
+
const result = {
|
|
628
|
+
recordsets,
|
|
629
|
+
recordset: recordsets && recordsets[0],
|
|
554
630
|
output,
|
|
555
631
|
rowsAffected,
|
|
556
632
|
returnValue
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
const result = {
|
|
562
|
-
recordsets,
|
|
563
|
-
recordset: recordsets && recordsets[0],
|
|
564
|
-
output,
|
|
565
|
-
rowsAffected,
|
|
566
|
-
returnValue
|
|
567
|
-
}
|
|
568
|
-
if (this.arrayRowMode) result.columns = columns
|
|
569
|
-
resolve(result)
|
|
633
|
+
}
|
|
634
|
+
if (this.arrayRowMode) result.columns = columns
|
|
635
|
+
resolve(result)
|
|
636
|
+
})
|
|
570
637
|
})
|
|
571
638
|
})
|
|
572
639
|
}
|
|
@@ -598,6 +665,11 @@ class Request extends EventEmitter {
|
|
|
598
665
|
|
|
599
666
|
cancel () {
|
|
600
667
|
this._cancel()
|
|
668
|
+
if (!this._internal) {
|
|
669
|
+
publish(CHANNELS.REQUEST_CANCEL, () => ({
|
|
670
|
+
requestId: IDS.get(this)
|
|
671
|
+
}))
|
|
672
|
+
}
|
|
601
673
|
return true
|
|
602
674
|
}
|
|
603
675
|
|
package/lib/base/transaction.js
CHANGED
|
@@ -2,11 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
const debug = require('debug')('mssql:base')
|
|
4
4
|
const { EventEmitter } = require('node:events')
|
|
5
|
-
const { IDS } = require('../utils')
|
|
5
|
+
const { IDS, getPoolId } = require('../utils')
|
|
6
6
|
const globalConnection = require('../global-connection')
|
|
7
7
|
const { TransactionError } = require('../error')
|
|
8
8
|
const shared = require('../shared')
|
|
9
9
|
const ISOLATION_LEVEL = require('../isolationlevel')
|
|
10
|
+
const { CHANNELS, publish } = require('../diagnostics')
|
|
11
|
+
|
|
12
|
+
const ISOLATION_LEVEL_NAMES = Object.fromEntries(
|
|
13
|
+
Object.entries(ISOLATION_LEVEL).map(([name, value]) => [value, name])
|
|
14
|
+
)
|
|
10
15
|
|
|
11
16
|
/**
|
|
12
17
|
* Class Transaction.
|
|
@@ -101,6 +106,12 @@ class Transaction extends EventEmitter {
|
|
|
101
106
|
if (typeof callback === 'function') {
|
|
102
107
|
this._begin(isolationLevel, err => {
|
|
103
108
|
if (!err) {
|
|
109
|
+
publish(CHANNELS.TRANSACTION_BEGIN, () => ({
|
|
110
|
+
transactionId: IDS.get(this),
|
|
111
|
+
isolationLevel: this.isolationLevel,
|
|
112
|
+
isolationLevelName: ISOLATION_LEVEL_NAMES[this.isolationLevel],
|
|
113
|
+
poolId: getPoolId(this)
|
|
114
|
+
}))
|
|
104
115
|
this.emit('begin')
|
|
105
116
|
}
|
|
106
117
|
callback(err)
|
|
@@ -111,6 +122,12 @@ class Transaction extends EventEmitter {
|
|
|
111
122
|
return new shared.Promise((resolve, reject) => {
|
|
112
123
|
this._begin(isolationLevel, err => {
|
|
113
124
|
if (err) return reject(err)
|
|
125
|
+
publish(CHANNELS.TRANSACTION_BEGIN, () => ({
|
|
126
|
+
transactionId: IDS.get(this),
|
|
127
|
+
isolationLevel: this.isolationLevel,
|
|
128
|
+
isolationLevelName: ISOLATION_LEVEL_NAMES[this.isolationLevel],
|
|
129
|
+
poolId: getPoolId(this)
|
|
130
|
+
}))
|
|
114
131
|
this.emit('begin')
|
|
115
132
|
resolve(this)
|
|
116
133
|
})
|
|
@@ -155,6 +172,9 @@ class Transaction extends EventEmitter {
|
|
|
155
172
|
if (typeof callback === 'function') {
|
|
156
173
|
this._commit(err => {
|
|
157
174
|
if (!err) {
|
|
175
|
+
publish(CHANNELS.TRANSACTION_COMMIT, () => ({
|
|
176
|
+
transactionId: IDS.get(this)
|
|
177
|
+
}))
|
|
158
178
|
this.emit('commit')
|
|
159
179
|
}
|
|
160
180
|
callback(err)
|
|
@@ -165,6 +185,9 @@ class Transaction extends EventEmitter {
|
|
|
165
185
|
return new shared.Promise((resolve, reject) => {
|
|
166
186
|
this._commit(err => {
|
|
167
187
|
if (err) return reject(err)
|
|
188
|
+
publish(CHANNELS.TRANSACTION_COMMIT, () => ({
|
|
189
|
+
transactionId: IDS.get(this)
|
|
190
|
+
}))
|
|
168
191
|
this.emit('commit')
|
|
169
192
|
resolve()
|
|
170
193
|
})
|
|
@@ -214,6 +237,10 @@ class Transaction extends EventEmitter {
|
|
|
214
237
|
if (typeof callback === 'function') {
|
|
215
238
|
this._rollback(err => {
|
|
216
239
|
if (!err) {
|
|
240
|
+
publish(CHANNELS.TRANSACTION_ROLLBACK, () => ({
|
|
241
|
+
transactionId: IDS.get(this),
|
|
242
|
+
aborted: this._aborted
|
|
243
|
+
}))
|
|
217
244
|
this.emit('rollback', this._aborted)
|
|
218
245
|
}
|
|
219
246
|
callback(err)
|
|
@@ -224,6 +251,10 @@ class Transaction extends EventEmitter {
|
|
|
224
251
|
return new shared.Promise((resolve, reject) => {
|
|
225
252
|
return this._rollback(err => {
|
|
226
253
|
if (err) return reject(err)
|
|
254
|
+
publish(CHANNELS.TRANSACTION_ROLLBACK, () => ({
|
|
255
|
+
transactionId: IDS.get(this),
|
|
256
|
+
aborted: this._aborted
|
|
257
|
+
}))
|
|
227
258
|
this.emit('rollback', this._aborted)
|
|
228
259
|
resolve()
|
|
229
260
|
})
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const dc = require('node:diagnostics_channel')
|
|
4
|
+
|
|
5
|
+
// TracingChannel names
|
|
6
|
+
const TRACE_QUERY = 'mssql:query'
|
|
7
|
+
const TRACE_BATCH = 'mssql:batch'
|
|
8
|
+
const TRACE_EXECUTE = 'mssql:execute'
|
|
9
|
+
const TRACE_BULK = 'mssql:bulk'
|
|
10
|
+
const TRACE_CONNECT = 'mssql:connect'
|
|
11
|
+
const TRACE_POOL_ACQUIRE = 'mssql:pool:acquire'
|
|
12
|
+
const TRACE_PREPARED_STATEMENT_PREPARE = 'mssql:prepared-statement:prepare'
|
|
13
|
+
const TRACE_PREPARED_STATEMENT_EXECUTE = 'mssql:prepared-statement:execute'
|
|
14
|
+
|
|
15
|
+
// Point-event channel names
|
|
16
|
+
const CONNECTION_ACQUIRE = 'mssql:connection:acquire'
|
|
17
|
+
const CONNECTION_RELEASE = 'mssql:connection:release'
|
|
18
|
+
const CONNECTION_CREATE = 'mssql:connection:create'
|
|
19
|
+
const CONNECTION_DESTROY = 'mssql:connection:destroy'
|
|
20
|
+
const POOL_CLOSE = 'mssql:pool:close'
|
|
21
|
+
const TRANSACTION_BEGIN = 'mssql:transaction:begin'
|
|
22
|
+
const TRANSACTION_COMMIT = 'mssql:transaction:commit'
|
|
23
|
+
const TRANSACTION_ROLLBACK = 'mssql:transaction:rollback'
|
|
24
|
+
const REQUEST_CANCEL = 'mssql:request:cancel'
|
|
25
|
+
const PREPARED_STATEMENT_UNPREPARE = 'mssql:prepared-statement:unprepare'
|
|
26
|
+
|
|
27
|
+
const CHANNELS = Object.freeze({
|
|
28
|
+
TRACE_QUERY,
|
|
29
|
+
TRACE_BATCH,
|
|
30
|
+
TRACE_EXECUTE,
|
|
31
|
+
TRACE_BULK,
|
|
32
|
+
TRACE_CONNECT,
|
|
33
|
+
TRACE_POOL_ACQUIRE,
|
|
34
|
+
TRACE_PREPARED_STATEMENT_PREPARE,
|
|
35
|
+
TRACE_PREPARED_STATEMENT_EXECUTE,
|
|
36
|
+
CONNECTION_ACQUIRE,
|
|
37
|
+
CONNECTION_RELEASE,
|
|
38
|
+
CONNECTION_CREATE,
|
|
39
|
+
CONNECTION_DESTROY,
|
|
40
|
+
POOL_CLOSE,
|
|
41
|
+
TRANSACTION_BEGIN,
|
|
42
|
+
TRANSACTION_COMMIT,
|
|
43
|
+
TRANSACTION_ROLLBACK,
|
|
44
|
+
REQUEST_CANCEL,
|
|
45
|
+
PREPARED_STATEMENT_UNPREPARE
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
// Pre-create TracingChannel instances at module load time
|
|
49
|
+
const tracingChannels = {
|
|
50
|
+
[TRACE_QUERY]: dc.tracingChannel(TRACE_QUERY),
|
|
51
|
+
[TRACE_BATCH]: dc.tracingChannel(TRACE_BATCH),
|
|
52
|
+
[TRACE_EXECUTE]: dc.tracingChannel(TRACE_EXECUTE),
|
|
53
|
+
[TRACE_BULK]: dc.tracingChannel(TRACE_BULK),
|
|
54
|
+
[TRACE_CONNECT]: dc.tracingChannel(TRACE_CONNECT),
|
|
55
|
+
[TRACE_POOL_ACQUIRE]: dc.tracingChannel(TRACE_POOL_ACQUIRE),
|
|
56
|
+
[TRACE_PREPARED_STATEMENT_PREPARE]: dc.tracingChannel(TRACE_PREPARED_STATEMENT_PREPARE),
|
|
57
|
+
[TRACE_PREPARED_STATEMENT_EXECUTE]: dc.tracingChannel(TRACE_PREPARED_STATEMENT_EXECUTE)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Pre-create point-event channel instances at module load time
|
|
61
|
+
const pointChannels = {
|
|
62
|
+
[CONNECTION_ACQUIRE]: dc.channel(CONNECTION_ACQUIRE),
|
|
63
|
+
[CONNECTION_RELEASE]: dc.channel(CONNECTION_RELEASE),
|
|
64
|
+
[CONNECTION_CREATE]: dc.channel(CONNECTION_CREATE),
|
|
65
|
+
[CONNECTION_DESTROY]: dc.channel(CONNECTION_DESTROY),
|
|
66
|
+
[POOL_CLOSE]: dc.channel(POOL_CLOSE),
|
|
67
|
+
[TRANSACTION_BEGIN]: dc.channel(TRANSACTION_BEGIN),
|
|
68
|
+
[TRANSACTION_COMMIT]: dc.channel(TRANSACTION_COMMIT),
|
|
69
|
+
[TRANSACTION_ROLLBACK]: dc.channel(TRANSACTION_ROLLBACK),
|
|
70
|
+
[REQUEST_CANCEL]: dc.channel(REQUEST_CANCEL),
|
|
71
|
+
[PREPARED_STATEMENT_UNPREPARE]: dc.channel(PREPARED_STATEMENT_UNPREPARE)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// TracingChannel.hasSubscribers was added in Node 22. On 18.19 / 20 the
|
|
75
|
+
// aggregate property is undefined, so we fall back to checking the
|
|
76
|
+
// sub-channels directly (these have existed since the original
|
|
77
|
+
// diagnostics_channel API). Preserves the zero-cost fast path across all
|
|
78
|
+
// supported runtimes.
|
|
79
|
+
function tracingChannelHasSubscribers (tc) {
|
|
80
|
+
if (typeof tc.hasSubscribers === 'boolean') return tc.hasSubscribers
|
|
81
|
+
return tc.start.hasSubscribers ||
|
|
82
|
+
tc.end.hasSubscribers ||
|
|
83
|
+
tc.asyncStart.hasSubscribers ||
|
|
84
|
+
tc.asyncEnd.hasSubscribers ||
|
|
85
|
+
tc.error.hasSubscribers
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Trace an async operation using a TracingChannel.
|
|
90
|
+
*
|
|
91
|
+
* When subscribers are active, wraps `fn` with TracingChannel.tracePromise().
|
|
92
|
+
* When no subscribers are active, calls `fn` directly with zero overhead
|
|
93
|
+
* (no context allocation).
|
|
94
|
+
*
|
|
95
|
+
* @param {string} name - TracingChannel name (one of CHANNELS.TRACE_*)
|
|
96
|
+
* @param {Function} fn - The function to trace (must return a Promise)
|
|
97
|
+
* @param {Function} contextFactory - Factory function returning the context object
|
|
98
|
+
* @returns {Promise} The return value of fn
|
|
99
|
+
*/
|
|
100
|
+
function tracePromise (name, fn, contextFactory) {
|
|
101
|
+
const channel = tracingChannels[name]
|
|
102
|
+
if (tracingChannelHasSubscribers(channel)) {
|
|
103
|
+
return channel.tracePromise(fn, contextFactory())
|
|
104
|
+
}
|
|
105
|
+
return fn()
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Trace a callback-style async operation using a TracingChannel.
|
|
110
|
+
*
|
|
111
|
+
* When subscribers are active, delegates to TracingChannel.traceCallback,
|
|
112
|
+
* which replaces the callback at `position` in `args` with a wrapped
|
|
113
|
+
* version that publishes to start/end/asyncStart/asyncEnd/error. When
|
|
114
|
+
* no subscribers are active, calls `fn` directly with zero overhead.
|
|
115
|
+
*
|
|
116
|
+
* @param {string} name - TracingChannel name (one of CHANNELS.TRACE_*)
|
|
117
|
+
* @param {Function} fn - The function to call (receives callback at `position`)
|
|
118
|
+
* @param {number} position - Index of the callback within `args`
|
|
119
|
+
* @param {Function} contextFactory - Factory function returning the context object
|
|
120
|
+
* @param {*} thisArg - `this` binding for fn
|
|
121
|
+
* @param {Array} args - Arguments to pass to fn (includes the callback at `position`)
|
|
122
|
+
* @returns {*} The return value of fn
|
|
123
|
+
*/
|
|
124
|
+
function traceCallback (name, fn, position, contextFactory, thisArg, args) {
|
|
125
|
+
const channel = tracingChannels[name]
|
|
126
|
+
if (tracingChannelHasSubscribers(channel)) {
|
|
127
|
+
return channel.traceCallback(fn, position, contextFactory(), thisArg, ...args)
|
|
128
|
+
}
|
|
129
|
+
return fn.apply(thisArg, args)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Publish a point event on a named channel.
|
|
134
|
+
*
|
|
135
|
+
* Only allocates the message object when subscribers are active.
|
|
136
|
+
*
|
|
137
|
+
* @param {string} name - Point-event channel name (one of CHANNELS.*)
|
|
138
|
+
* @param {Function} factory - Factory function that returns the message object
|
|
139
|
+
*/
|
|
140
|
+
function publish (name, factory) {
|
|
141
|
+
const channel = pointChannels[name]
|
|
142
|
+
if (channel.hasSubscribers) {
|
|
143
|
+
channel.publish(factory())
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
module.exports = {
|
|
148
|
+
CHANNELS,
|
|
149
|
+
tracingChannels,
|
|
150
|
+
tracePromise,
|
|
151
|
+
traceCallback,
|
|
152
|
+
publish
|
|
153
|
+
}
|
|
@@ -8,6 +8,7 @@ const shared = require('../shared')
|
|
|
8
8
|
const ConnectionError = require('../error/connection-error')
|
|
9
9
|
const { platform } = require('node:os')
|
|
10
10
|
const { build } = require('@tediousjs/connection-string')
|
|
11
|
+
const { CHANNELS, publish } = require('../diagnostics')
|
|
11
12
|
|
|
12
13
|
const DEFAULT_CONNECTION_DRIVER = ['darwin', 'linux'].includes(platform()) ? 'ODBC Driver 17 for SQL Server' : 'SQL Server Native Client 11.0'
|
|
13
14
|
|
|
@@ -58,6 +59,12 @@ class ConnectionPool extends BaseConnectionPool {
|
|
|
58
59
|
IDS.add(tds, 'Connection', connedtionId)
|
|
59
60
|
tds.setUseUTC(this.config.options.useUTC)
|
|
60
61
|
debug('connection(%d): established', IDS.get(tds))
|
|
62
|
+
publish(CHANNELS.CONNECTION_CREATE, () => ({
|
|
63
|
+
connectionId: IDS.get(tds),
|
|
64
|
+
poolId: IDS.get(this),
|
|
65
|
+
server: this.config.server,
|
|
66
|
+
database: this.config.database
|
|
67
|
+
}))
|
|
61
68
|
resolve(tds)
|
|
62
69
|
})
|
|
63
70
|
})
|
|
@@ -80,9 +87,15 @@ class ConnectionPool extends BaseConnectionPool {
|
|
|
80
87
|
resolve()
|
|
81
88
|
return
|
|
82
89
|
}
|
|
83
|
-
|
|
90
|
+
const connectionId = IDS.get(tds)
|
|
91
|
+
const poolId = IDS.get(this)
|
|
92
|
+
debug('connection(%d): destroying', connectionId)
|
|
84
93
|
tds.close(() => {
|
|
85
|
-
debug('connection(%d): destroyed',
|
|
94
|
+
debug('connection(%d): destroyed', connectionId)
|
|
95
|
+
publish(CHANNELS.CONNECTION_DESTROY, () => ({
|
|
96
|
+
connectionId,
|
|
97
|
+
poolId
|
|
98
|
+
}))
|
|
86
99
|
resolve()
|
|
87
100
|
})
|
|
88
101
|
})
|
|
@@ -6,6 +6,7 @@ const BaseConnectionPool = require('../base/connection-pool')
|
|
|
6
6
|
const { IDS } = require('../utils')
|
|
7
7
|
const shared = require('../shared')
|
|
8
8
|
const ConnectionError = require('../error/connection-error')
|
|
9
|
+
const { CHANNELS, publish } = require('../diagnostics')
|
|
9
10
|
|
|
10
11
|
class ConnectionPool extends BaseConnectionPool {
|
|
11
12
|
_config () {
|
|
@@ -88,6 +89,12 @@ class ConnectionPool extends BaseConnectionPool {
|
|
|
88
89
|
|
|
89
90
|
debug('connection(%d): established', IDS.get(tedious))
|
|
90
91
|
this.collation = tedious.databaseCollation
|
|
92
|
+
publish(CHANNELS.CONNECTION_CREATE, () => ({
|
|
93
|
+
connectionId: IDS.get(tedious),
|
|
94
|
+
poolId: IDS.get(this),
|
|
95
|
+
server: this.config.server,
|
|
96
|
+
database: this.config.database
|
|
97
|
+
}))
|
|
91
98
|
resolveOnce(tedious)
|
|
92
99
|
})
|
|
93
100
|
IDS.add(tedious, 'Connection')
|
|
@@ -156,6 +163,8 @@ class ConnectionPool extends BaseConnectionPool {
|
|
|
156
163
|
return
|
|
157
164
|
}
|
|
158
165
|
debug('connection(%d): destroying', IDS.get(tedious))
|
|
166
|
+
const connectionId = IDS.get(tedious)
|
|
167
|
+
const poolId = IDS.get(this)
|
|
159
168
|
|
|
160
169
|
if (tedious.closed) {
|
|
161
170
|
debug('connection(%d): already closed', IDS.get(tedious))
|
|
@@ -163,6 +172,10 @@ class ConnectionPool extends BaseConnectionPool {
|
|
|
163
172
|
} else {
|
|
164
173
|
tedious.once('end', () => {
|
|
165
174
|
debug('connection(%d): destroyed', IDS.get(tedious))
|
|
175
|
+
publish(CHANNELS.CONNECTION_DESTROY, () => ({
|
|
176
|
+
connectionId,
|
|
177
|
+
poolId
|
|
178
|
+
}))
|
|
166
179
|
resolve()
|
|
167
180
|
})
|
|
168
181
|
|
|
@@ -4,6 +4,7 @@ const debug = require('debug')('mssql:tedi')
|
|
|
4
4
|
const BaseTransaction = require('../base/transaction')
|
|
5
5
|
const { IDS } = require('../utils')
|
|
6
6
|
const TransactionError = require('../error/transaction-error')
|
|
7
|
+
const { CHANNELS, publish } = require('../diagnostics')
|
|
7
8
|
|
|
8
9
|
class Transaction extends BaseTransaction {
|
|
9
10
|
constructor (parent) {
|
|
@@ -23,6 +24,10 @@ class Transaction extends BaseTransaction {
|
|
|
23
24
|
this._acquiredConfig = null
|
|
24
25
|
this._aborted = true
|
|
25
26
|
|
|
27
|
+
publish(CHANNELS.TRANSACTION_ROLLBACK, () => ({
|
|
28
|
+
transactionId: IDS.get(this),
|
|
29
|
+
aborted: true
|
|
30
|
+
}))
|
|
26
31
|
this.emit('rollback', true)
|
|
27
32
|
}
|
|
28
33
|
}
|
package/lib/udt.js
CHANGED
|
@@ -44,6 +44,12 @@ const SEGMENT = {
|
|
|
44
44
|
FIRST_ARC: 0x03
|
|
45
45
|
}; */
|
|
46
46
|
|
|
47
|
+
const ensureBytes = (buffer, needed) => {
|
|
48
|
+
if (buffer.position + needed > buffer.length) {
|
|
49
|
+
throw new Error(`Corrupt or truncated spatial data: expected ${needed} bytes at position ${buffer.position}, but only ${buffer.length - buffer.position} bytes remain`)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
47
53
|
class Point {
|
|
48
54
|
constructor () {
|
|
49
55
|
this.x = 0
|
|
@@ -64,6 +70,8 @@ const parsePoints = (buffer, count, isGeometryPoint) => {
|
|
|
64
70
|
return points
|
|
65
71
|
}
|
|
66
72
|
|
|
73
|
+
ensureBytes(buffer, count * 16)
|
|
74
|
+
|
|
67
75
|
if (isGeometryPoint) {
|
|
68
76
|
// GEOMETRY POINT (s2.1.6): x then y.
|
|
69
77
|
for (let i = 1; i <= count; i++) {
|
|
@@ -101,6 +109,8 @@ const parseZ = (buffer, points) => {
|
|
|
101
109
|
return
|
|
102
110
|
}
|
|
103
111
|
|
|
112
|
+
ensureBytes(buffer, points.length * 8)
|
|
113
|
+
|
|
104
114
|
points.forEach(point => {
|
|
105
115
|
point.z = buffer.readDoubleLE(buffer.position)
|
|
106
116
|
buffer.position += 8
|
|
@@ -114,6 +124,8 @@ const parseM = (buffer, points) => {
|
|
|
114
124
|
return
|
|
115
125
|
}
|
|
116
126
|
|
|
127
|
+
ensureBytes(buffer, points.length * 8)
|
|
128
|
+
|
|
117
129
|
points.forEach(point => {
|
|
118
130
|
point.m = buffer.readDoubleLE(buffer.position)
|
|
119
131
|
buffer.position += 8
|
|
@@ -139,6 +151,8 @@ const parseFigures = (buffer, count, properties) => {
|
|
|
139
151
|
pointOffset: 0
|
|
140
152
|
})
|
|
141
153
|
} else {
|
|
154
|
+
ensureBytes(buffer, count * 5)
|
|
155
|
+
|
|
142
156
|
for (let i = 1; i <= count; i++) {
|
|
143
157
|
figures.push({
|
|
144
158
|
attribute: buffer.readUInt8(buffer.position),
|
|
@@ -173,6 +187,8 @@ const parseShapes = (buffer, count, properties) => {
|
|
|
173
187
|
type: 0x02
|
|
174
188
|
})
|
|
175
189
|
} else {
|
|
190
|
+
ensureBytes(buffer, count * 9)
|
|
191
|
+
|
|
176
192
|
for (let i = 1; i <= count; i++) {
|
|
177
193
|
shapes.push({
|
|
178
194
|
parentOffset: buffer.readInt32LE(buffer.position),
|
|
@@ -195,6 +211,8 @@ const parseSegments = (buffer, count) => {
|
|
|
195
211
|
return segments
|
|
196
212
|
}
|
|
197
213
|
|
|
214
|
+
ensureBytes(buffer, count)
|
|
215
|
+
|
|
198
216
|
for (let i = 1; i <= count; i++) {
|
|
199
217
|
segments.push({ type: buffer.readUInt8(buffer.position) })
|
|
200
218
|
|
|
@@ -207,6 +225,8 @@ const parseSegments = (buffer, count) => {
|
|
|
207
225
|
const parseGeography = (buffer, isUsingGeometryPoints) => {
|
|
208
226
|
// s2.1.1 + s.2.1.2
|
|
209
227
|
|
|
228
|
+
ensureBytes(buffer, 6)
|
|
229
|
+
|
|
210
230
|
const srid = buffer.readInt32LE(0)
|
|
211
231
|
if (srid === -1) {
|
|
212
232
|
return null
|
|
@@ -220,9 +240,6 @@ const parseGeography = (buffer, isUsingGeometryPoints) => {
|
|
|
220
240
|
const flags = buffer.readUInt8(5)
|
|
221
241
|
buffer.position = 6
|
|
222
242
|
|
|
223
|
-
// console.log("srid", srid)
|
|
224
|
-
// console.log("version", version)
|
|
225
|
-
|
|
226
243
|
const properties = {
|
|
227
244
|
Z: (flags & (1 << 0)) > 0,
|
|
228
245
|
M: (flags & (1 << 1)) > 0,
|
|
@@ -232,23 +249,20 @@ const parseGeography = (buffer, isUsingGeometryPoints) => {
|
|
|
232
249
|
}
|
|
233
250
|
|
|
234
251
|
if (value.version === 2) {
|
|
235
|
-
properties.H = (flags & (1 <<
|
|
252
|
+
properties.H = (flags & (1 << 5)) > 0
|
|
236
253
|
}
|
|
237
254
|
|
|
238
|
-
// console.log("properties", properties);
|
|
239
|
-
|
|
240
255
|
let numberOfPoints
|
|
241
256
|
if (properties.P) {
|
|
242
257
|
numberOfPoints = 1
|
|
243
258
|
} else if (properties.L) {
|
|
244
259
|
numberOfPoints = 2
|
|
245
260
|
} else {
|
|
261
|
+
ensureBytes(buffer, 4)
|
|
246
262
|
numberOfPoints = buffer.readUInt32LE(buffer.position)
|
|
247
263
|
buffer.position += 4
|
|
248
264
|
}
|
|
249
265
|
|
|
250
|
-
// console.log("numberOfPoints", numberOfPoints)
|
|
251
|
-
|
|
252
266
|
value.points = parsePoints(buffer, numberOfPoints, isUsingGeometryPoints)
|
|
253
267
|
|
|
254
268
|
if (properties.Z) {
|
|
@@ -259,49 +273,37 @@ const parseGeography = (buffer, isUsingGeometryPoints) => {
|
|
|
259
273
|
parseM(buffer, value.points)
|
|
260
274
|
}
|
|
261
275
|
|
|
262
|
-
// console.log("points", points)
|
|
263
|
-
|
|
264
276
|
let numberOfFigures
|
|
265
277
|
if (properties.P) {
|
|
266
278
|
numberOfFigures = 1
|
|
267
279
|
} else if (properties.L) {
|
|
268
280
|
numberOfFigures = 1
|
|
269
281
|
} else {
|
|
282
|
+
ensureBytes(buffer, 4)
|
|
270
283
|
numberOfFigures = buffer.readUInt32LE(buffer.position)
|
|
271
284
|
buffer.position += 4
|
|
272
285
|
}
|
|
273
286
|
|
|
274
|
-
// console.log("numberOfFigures", numberOfFigures)
|
|
275
|
-
|
|
276
287
|
value.figures = parseFigures(buffer, numberOfFigures, properties)
|
|
277
288
|
|
|
278
|
-
// console.log("figures", figures)
|
|
279
|
-
|
|
280
289
|
let numberOfShapes
|
|
281
290
|
if (properties.P) {
|
|
282
291
|
numberOfShapes = 1
|
|
283
292
|
} else if (properties.L) {
|
|
284
293
|
numberOfShapes = 1
|
|
285
294
|
} else {
|
|
295
|
+
ensureBytes(buffer, 4)
|
|
286
296
|
numberOfShapes = buffer.readUInt32LE(buffer.position)
|
|
287
297
|
buffer.position += 4
|
|
288
298
|
}
|
|
289
299
|
|
|
290
|
-
// console.log("numberOfShapes", numberOfShapes)
|
|
291
|
-
|
|
292
300
|
value.shapes = parseShapes(buffer, numberOfShapes, properties)
|
|
293
301
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
if (value.version === 2 && buffer.position < buffer.length) {
|
|
302
|
+
if (value.version === 2 && buffer.position + 4 <= buffer.length) {
|
|
297
303
|
const numberOfSegments = buffer.readUInt32LE(buffer.position)
|
|
298
304
|
buffer.position += 4
|
|
299
305
|
|
|
300
|
-
// console.log("numberOfSegments", numberOfSegments)
|
|
301
|
-
|
|
302
306
|
value.segments = parseSegments(buffer, numberOfSegments)
|
|
303
|
-
|
|
304
|
-
// console.log("segments", segments)
|
|
305
307
|
} else {
|
|
306
308
|
value.segments = []
|
|
307
309
|
}
|
package/lib/utils.js
CHANGED
|
@@ -9,6 +9,14 @@ const INCREMENT = {
|
|
|
9
9
|
PreparedStatement: 1
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
const getPoolId = (obj) => {
|
|
13
|
+
let parent = obj && obj.parent
|
|
14
|
+
while (parent && !parent.pool) {
|
|
15
|
+
parent = parent.parent
|
|
16
|
+
}
|
|
17
|
+
return parent ? IDS.get(parent) : undefined
|
|
18
|
+
}
|
|
19
|
+
|
|
12
20
|
module.exports = {
|
|
13
21
|
objectHasProperty: (object, property) => Object.prototype.hasOwnProperty.call(object, property),
|
|
14
22
|
INCREMENT,
|
|
@@ -18,5 +26,6 @@ module.exports = {
|
|
|
18
26
|
if (id) return IDS.set(object, id)
|
|
19
27
|
IDS.set(object, INCREMENT[type]++)
|
|
20
28
|
}
|
|
21
|
-
}
|
|
29
|
+
},
|
|
30
|
+
getPoolId
|
|
22
31
|
}
|
package/package.json
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"azure",
|
|
22
22
|
"node-mssql"
|
|
23
23
|
],
|
|
24
|
-
"version": "12.
|
|
24
|
+
"version": "12.4.0",
|
|
25
25
|
"main": "index.js",
|
|
26
26
|
"type": "commonjs",
|
|
27
27
|
"repository": {
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"standard": "^17.0.0"
|
|
53
53
|
},
|
|
54
54
|
"engines": {
|
|
55
|
-
"node": ">=18"
|
|
55
|
+
"node": ">=18.19.0"
|
|
56
56
|
},
|
|
57
57
|
"files": [
|
|
58
58
|
"lib/",
|