mssql 12.3.1 → 12.5.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.
@@ -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.
@@ -26,10 +27,11 @@ class Request extends EventEmitter {
26
27
  /**
27
28
  * Create new Request.
28
29
  *
29
- * @param {Connection|ConnectionPool|Transaction|PreparedStatement} parent If omitted, global connection is used instead.
30
+ * @param {Connection|ConnectionPool|Transaction|PreparedStatement} [parent] If omitted, global connection is used instead.
31
+ * @param {{ requestTimeout?: number }} [overrides]
30
32
  */
31
33
 
32
- constructor (parent) {
34
+ constructor (parent, overrides = {}) {
33
35
  super()
34
36
 
35
37
  IDS.add(this, 'Request')
@@ -37,10 +39,15 @@ class Request extends EventEmitter {
37
39
 
38
40
  this.canceled = false
39
41
  this._paused = false
42
+ this._internal = false
40
43
  this.parent = parent || globalConnection.pool
41
44
  this.parameters = {}
42
45
  this.stream = null
43
46
  this.arrayRowMode = null
47
+ this.overrides = {}
48
+ if (Number.isFinite(overrides?.requestTimeout) && overrides.requestTimeout >= 0) {
49
+ this.overrides.requestTimeout = overrides.requestTimeout
50
+ }
44
51
  }
45
52
 
46
53
  get paused () {
@@ -218,6 +225,24 @@ class Request extends EventEmitter {
218
225
  return this.output(name, type, value)
219
226
  }
220
227
 
228
+ _getParameterNames () {
229
+ return Object.keys(this.parameters)
230
+ }
231
+
232
+ _tracedPromise (channel, contextFactory, fn) {
233
+ if (this._internal) {
234
+ return fn()
235
+ }
236
+ return tracePromise(channel, fn, contextFactory)
237
+ }
238
+
239
+ _tracedCallback (channel, contextFactory, fn, position, args) {
240
+ if (this._internal) {
241
+ return fn.apply(this, args)
242
+ }
243
+ return traceCallback(channel, fn, position, contextFactory, this, args)
244
+ }
245
+
221
246
  /**
222
247
  * Execute the SQL batch.
223
248
  *
@@ -232,7 +257,11 @@ class Request extends EventEmitter {
232
257
  this.rowsAffected = 0
233
258
 
234
259
  if (typeof callback === 'function') {
235
- this._batch(batch, (err, recordsets, output, rowsAffected) => {
260
+ this._tracedCallback(CHANNELS.TRACE_BATCH, () => ({
261
+ command: batch,
262
+ requestId: IDS.get(this),
263
+ poolId: getPoolId(this)
264
+ }), this._batch, 1, [batch, (err, recordsets, output, rowsAffected) => {
236
265
  if (this.stream) {
237
266
  if (err) this.emit('error', err)
238
267
  err = null
@@ -250,7 +279,7 @@ class Request extends EventEmitter {
250
279
  output,
251
280
  rowsAffected
252
281
  })
253
- })
282
+ }])
254
283
  return this
255
284
  }
256
285
 
@@ -261,24 +290,31 @@ class Request extends EventEmitter {
261
290
  batch = this._template(strings, values)
262
291
  }
263
292
 
264
- return new shared.Promise((resolve, reject) => {
265
- this._batch(batch, (err, recordsets, output, rowsAffected) => {
266
- if (this.stream) {
267
- if (err) this.emit('error', err)
268
- err = null
293
+ const batchCommand = batch
294
+ return this._tracedPromise(CHANNELS.TRACE_BATCH, () => ({
295
+ command: batchCommand,
296
+ requestId: IDS.get(this),
297
+ poolId: getPoolId(this)
298
+ }), () => {
299
+ return new shared.Promise((resolve, reject) => {
300
+ this._batch(batchCommand, (err, recordsets, output, rowsAffected) => {
301
+ if (this.stream) {
302
+ if (err) this.emit('error', err)
303
+ err = null
304
+
305
+ this.emit('done', {
306
+ output,
307
+ rowsAffected
308
+ })
309
+ }
269
310
 
270
- this.emit('done', {
311
+ if (err) return reject(err)
312
+ resolve({
313
+ recordsets,
314
+ recordset: recordsets && recordsets[0],
271
315
  output,
272
316
  rowsAffected
273
317
  })
274
- }
275
-
276
- if (err) return reject(err)
277
- resolve({
278
- recordsets,
279
- recordset: recordsets && recordsets[0],
280
- output,
281
- rowsAffected
282
318
  })
283
319
  })
284
320
  })
@@ -324,7 +360,12 @@ class Request extends EventEmitter {
324
360
  if (this.arrayRowMode === null && this.parent) this.arrayRowMode = this.parent.config.arrayRowMode
325
361
 
326
362
  if (this.stream || typeof callback === 'function') {
327
- this._bulk(table, options, (err, rowsAffected) => {
363
+ this._tracedCallback(CHANNELS.TRACE_BULK, () => ({
364
+ table: table.path || table.name,
365
+ rowCount: table.rows ? table.rows.length : 0,
366
+ requestId: IDS.get(this),
367
+ poolId: getPoolId(this)
368
+ }), this._bulk, 2, [table, options, (err, rowsAffected) => {
328
369
  if (this.stream) {
329
370
  if (err) this.emit('error', err)
330
371
  return this.emit('done', {
@@ -336,15 +377,22 @@ class Request extends EventEmitter {
336
377
  callback(null, {
337
378
  rowsAffected
338
379
  })
339
- })
380
+ }])
340
381
  return this
341
382
  }
342
383
 
343
- return new shared.Promise((resolve, reject) => {
344
- this._bulk(table, options, (err, rowsAffected) => {
345
- if (err) return reject(err)
346
- resolve({
347
- rowsAffected
384
+ return this._tracedPromise(CHANNELS.TRACE_BULK, () => ({
385
+ table: table.path || table.name,
386
+ rowCount: table.rows ? table.rows.length : 0,
387
+ requestId: IDS.get(this),
388
+ poolId: getPoolId(this)
389
+ }), () => {
390
+ return new shared.Promise((resolve, reject) => {
391
+ this._bulk(table, options, (err, rowsAffected) => {
392
+ if (err) return reject(err)
393
+ resolve({
394
+ rowsAffected
395
+ })
348
396
  })
349
397
  })
350
398
  })
@@ -429,7 +477,12 @@ class Request extends EventEmitter {
429
477
  this.rowsAffected = 0
430
478
 
431
479
  if (typeof callback === 'function') {
432
- this._query(command, (err, recordsets, output, rowsAffected, columns) => {
480
+ this._tracedCallback(CHANNELS.TRACE_QUERY, () => ({
481
+ command,
482
+ parameters: this._getParameterNames(),
483
+ requestId: IDS.get(this),
484
+ poolId: getPoolId(this)
485
+ }), this._query, 1, [command, (err, recordsets, output, rowsAffected, columns) => {
433
486
  if (this.stream) {
434
487
  if (err) this.emit('error', err)
435
488
  err = null
@@ -449,7 +502,7 @@ class Request extends EventEmitter {
449
502
  }
450
503
  if (this.arrayRowMode) result.columns = columns
451
504
  callback(null, result)
452
- })
505
+ }])
453
506
  return this
454
507
  }
455
508
 
@@ -460,27 +513,34 @@ class Request extends EventEmitter {
460
513
  command = this._template(strings, values)
461
514
  }
462
515
 
463
- return new shared.Promise((resolve, reject) => {
464
- this._query(command, (err, recordsets, output, rowsAffected, columns) => {
465
- if (this.stream) {
466
- if (err) this.emit('error', err)
467
- err = null
516
+ return this._tracedPromise(CHANNELS.TRACE_QUERY, () => ({
517
+ command,
518
+ parameters: this._getParameterNames(),
519
+ requestId: IDS.get(this),
520
+ poolId: getPoolId(this)
521
+ }), () => {
522
+ return new shared.Promise((resolve, reject) => {
523
+ this._query(command, (err, recordsets, output, rowsAffected, columns) => {
524
+ if (this.stream) {
525
+ if (err) this.emit('error', err)
526
+ err = null
527
+
528
+ this.emit('done', {
529
+ output,
530
+ rowsAffected
531
+ })
532
+ }
468
533
 
469
- this.emit('done', {
534
+ if (err) return reject(err)
535
+ const result = {
536
+ recordsets,
537
+ recordset: recordsets && recordsets[0],
470
538
  output,
471
539
  rowsAffected
472
- })
473
- }
474
-
475
- if (err) return reject(err)
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)
540
+ }
541
+ if (this.arrayRowMode) result.columns = columns
542
+ resolve(result)
543
+ })
484
544
  })
485
545
  })
486
546
  }
@@ -518,7 +578,12 @@ class Request extends EventEmitter {
518
578
  this.rowsAffected = 0
519
579
 
520
580
  if (typeof callback === 'function') {
521
- this._execute(command, (err, recordsets, output, returnValue, rowsAffected, columns) => {
581
+ this._tracedCallback(CHANNELS.TRACE_EXECUTE, () => ({
582
+ procedure: command,
583
+ parameters: this._getParameterNames(),
584
+ requestId: IDS.get(this),
585
+ poolId: getPoolId(this)
586
+ }), this._execute, 1, [command, (err, recordsets, output, returnValue, rowsAffected, columns) => {
522
587
  if (this.stream) {
523
588
  if (err) this.emit('error', err)
524
589
  err = null
@@ -540,33 +605,40 @@ class Request extends EventEmitter {
540
605
  }
541
606
  if (this.arrayRowMode) result.columns = columns
542
607
  callback(null, result)
543
- })
608
+ }])
544
609
  return this
545
610
  }
546
611
 
547
- return new shared.Promise((resolve, reject) => {
548
- this._execute(command, (err, recordsets, output, returnValue, rowsAffected, columns) => {
549
- if (this.stream) {
550
- if (err) this.emit('error', err)
551
- err = null
612
+ return this._tracedPromise(CHANNELS.TRACE_EXECUTE, () => ({
613
+ procedure: command,
614
+ parameters: this._getParameterNames(),
615
+ requestId: IDS.get(this),
616
+ poolId: getPoolId(this)
617
+ }), () => {
618
+ return new shared.Promise((resolve, reject) => {
619
+ this._execute(command, (err, recordsets, output, returnValue, rowsAffected, columns) => {
620
+ if (this.stream) {
621
+ if (err) this.emit('error', err)
622
+ err = null
623
+
624
+ this.emit('done', {
625
+ output,
626
+ rowsAffected,
627
+ returnValue
628
+ })
629
+ }
552
630
 
553
- this.emit('done', {
631
+ if (err) return reject(err)
632
+ const result = {
633
+ recordsets,
634
+ recordset: recordsets && recordsets[0],
554
635
  output,
555
636
  rowsAffected,
556
637
  returnValue
557
- })
558
- }
559
-
560
- if (err) return reject(err)
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)
638
+ }
639
+ if (this.arrayRowMode) result.columns = columns
640
+ resolve(result)
641
+ })
570
642
  })
571
643
  })
572
644
  }
@@ -598,6 +670,11 @@ class Request extends EventEmitter {
598
670
 
599
671
  cancel () {
600
672
  this._cancel()
673
+ if (!this._internal) {
674
+ publish(CHANNELS.REQUEST_CANCEL, () => ({
675
+ requestId: IDS.get(this)
676
+ }))
677
+ }
601
678
  return true
602
679
  }
603
680
 
@@ -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.
@@ -23,10 +28,11 @@ class Transaction extends EventEmitter {
23
28
  /**
24
29
  * Create new Transaction.
25
30
  *
26
- * @param {Connection} [parent] If ommited, global connection is used instead.
31
+ * @param {Connection} [parent] If omitted, global connection is used instead.
32
+ * @param {{ requestTimeout?: number }} [overrides]
27
33
  */
28
34
 
29
- constructor (parent) {
35
+ constructor (parent, overrides = {}) {
30
36
  super()
31
37
 
32
38
  IDS.add(this, 'Transaction')
@@ -35,6 +41,10 @@ class Transaction extends EventEmitter {
35
41
  this.parent = parent || globalConnection.pool
36
42
  this.isolationLevel = Transaction.defaultIsolationLevel
37
43
  this.name = ''
44
+ this.overrides = {}
45
+ if (Number.isFinite(overrides?.requestTimeout) && overrides.requestTimeout >= 0) {
46
+ this.overrides.requestTimeout = overrides.requestTimeout
47
+ }
38
48
  }
39
49
 
40
50
  get config () {
@@ -101,6 +111,12 @@ class Transaction extends EventEmitter {
101
111
  if (typeof callback === 'function') {
102
112
  this._begin(isolationLevel, err => {
103
113
  if (!err) {
114
+ publish(CHANNELS.TRANSACTION_BEGIN, () => ({
115
+ transactionId: IDS.get(this),
116
+ isolationLevel: this.isolationLevel,
117
+ isolationLevelName: ISOLATION_LEVEL_NAMES[this.isolationLevel],
118
+ poolId: getPoolId(this)
119
+ }))
104
120
  this.emit('begin')
105
121
  }
106
122
  callback(err)
@@ -111,6 +127,12 @@ class Transaction extends EventEmitter {
111
127
  return new shared.Promise((resolve, reject) => {
112
128
  this._begin(isolationLevel, err => {
113
129
  if (err) return reject(err)
130
+ publish(CHANNELS.TRANSACTION_BEGIN, () => ({
131
+ transactionId: IDS.get(this),
132
+ isolationLevel: this.isolationLevel,
133
+ isolationLevelName: ISOLATION_LEVEL_NAMES[this.isolationLevel],
134
+ poolId: getPoolId(this)
135
+ }))
114
136
  this.emit('begin')
115
137
  resolve(this)
116
138
  })
@@ -155,6 +177,9 @@ class Transaction extends EventEmitter {
155
177
  if (typeof callback === 'function') {
156
178
  this._commit(err => {
157
179
  if (!err) {
180
+ publish(CHANNELS.TRANSACTION_COMMIT, () => ({
181
+ transactionId: IDS.get(this)
182
+ }))
158
183
  this.emit('commit')
159
184
  }
160
185
  callback(err)
@@ -165,6 +190,9 @@ class Transaction extends EventEmitter {
165
190
  return new shared.Promise((resolve, reject) => {
166
191
  this._commit(err => {
167
192
  if (err) return reject(err)
193
+ publish(CHANNELS.TRANSACTION_COMMIT, () => ({
194
+ transactionId: IDS.get(this)
195
+ }))
168
196
  this.emit('commit')
169
197
  resolve()
170
198
  })
@@ -196,11 +224,16 @@ class Transaction extends EventEmitter {
196
224
  /**
197
225
  * Returns new request using this transaction.
198
226
  *
227
+ * @param {{ requestTimeout?: number }} [config]
199
228
  * @return {Request}
200
229
  */
201
230
 
202
- request () {
203
- return new shared.driver.Request(this)
231
+ request (config) {
232
+ const overrides = { ...this.overrides }
233
+ if (Number.isFinite(config?.requestTimeout) && config.requestTimeout >= 0) {
234
+ overrides.requestTimeout = config.requestTimeout
235
+ }
236
+ return new shared.driver.Request(this, overrides)
204
237
  }
205
238
 
206
239
  /**
@@ -214,6 +247,10 @@ class Transaction extends EventEmitter {
214
247
  if (typeof callback === 'function') {
215
248
  this._rollback(err => {
216
249
  if (!err) {
250
+ publish(CHANNELS.TRANSACTION_ROLLBACK, () => ({
251
+ transactionId: IDS.get(this),
252
+ aborted: this._aborted
253
+ }))
217
254
  this.emit('rollback', this._aborted)
218
255
  }
219
256
  callback(err)
@@ -224,6 +261,10 @@ class Transaction extends EventEmitter {
224
261
  return new shared.Promise((resolve, reject) => {
225
262
  return this._rollback(err => {
226
263
  if (err) return reject(err)
264
+ publish(CHANNELS.TRANSACTION_ROLLBACK, () => ({
265
+ transactionId: IDS.get(this),
266
+ aborted: this._aborted
267
+ }))
227
268
  this.emit('rollback', this._aborted)
228
269
  resolve()
229
270
  })
@@ -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
- debug('connection(%d): destroying', IDS.get(tds))
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', IDS.get(tds))
94
+ debug('connection(%d): destroyed', connectionId)
95
+ publish(CHANNELS.CONNECTION_DESTROY, () => ({
96
+ connectionId,
97
+ poolId
98
+ }))
86
99
  resolve()
87
100
  })
88
101
  })
@@ -172,7 +172,7 @@ class Request extends BaseRequest {
172
172
  setImmediate(callback, new RequestError("You can't use table variables for bulk insert.", 'ENAME'))
173
173
  }
174
174
 
175
- this.parent.acquire(this, (err, connection) => {
175
+ this.parent.acquire(this, (err, connection, config) => {
176
176
  let hasReturned = false
177
177
  if (!err) {
178
178
  debug('connection(%d): borrowed to request #%d', IDS.get(connection), IDS.get(this))
@@ -244,7 +244,10 @@ class Request extends BaseRequest {
244
244
  objectid = table.path
245
245
  }
246
246
 
247
- return connection.queryRaw(`if object_id('${objectid.replace(/'/g, '\'\'')}') is null ${table.declare()}`, function (err) {
247
+ return connection.queryRaw({
248
+ query_str: `if object_id('${objectid.replace(/'/g, '\'\'')}') is null ${table.declare()}`,
249
+ query_timeout: (this.overrides.requestTimeout ?? config.requestTimeout) / 1000 // msnodesqlv8 timeouts are in seconds (<1 second not supported)
250
+ }, function (err) {
248
251
  if (err) { return done(err) }
249
252
  go()
250
253
  })
@@ -389,7 +392,7 @@ class Request extends BaseRequest {
389
392
 
390
393
  const req = connection.queryRaw({
391
394
  query_str: command,
392
- query_timeout: config.requestTimeout / 1000 // msnodesqlv8 timeouts are in seconds (<1 second not supported)
395
+ query_timeout: (this.overrides.requestTimeout ?? config.requestTimeout) / 1000 // msnodesqlv8 timeouts are in seconds (<1 second not supported)
393
396
  }, params)
394
397
 
395
398
  this._setCurrentRequest(req)