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 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 acquirePromise = shared.Promise.resolve(this._acquire()).catch(err => {
375
- this.emit('error', err)
376
- throw err
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(callback)
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 new shared.Promise((resolve, reject) => {
426
- return this._connect(err => {
427
- if (err) return reject(err)
428
- resolve(this)
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(statement, callback)
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 new shared.Promise((resolve, reject) => {
202
- this._prepare(statement, err => {
203
- if (err) return reject(err)
204
- resolve(this)
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
- return this._execute(values, callback)
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 new shared.Promise((resolve, reject) => {
283
- this._execute(values, (err, recordset) => {
284
- if (err) return reject(err)
285
- resolve(recordset)
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(callback)
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 => {
@@ -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._batch(batch, (err, recordsets, output, rowsAffected) => {
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
- 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
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
- this.emit('done', {
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._bulk(table, options, (err, rowsAffected) => {
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 new shared.Promise((resolve, reject) => {
344
- this._bulk(table, options, (err, rowsAffected) => {
345
- if (err) return reject(err)
346
- resolve({
347
- rowsAffected
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._query(command, (err, recordsets, output, rowsAffected, columns) => {
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 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
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
- this.emit('done', {
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
- 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)
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._execute(command, (err, recordsets, output, returnValue, rowsAffected, columns) => {
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 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
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
- this.emit('done', {
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
- 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)
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
 
@@ -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
- 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
  })
@@ -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 << 3)) > 0
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
- // console.log( "shapes", shapes)
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.3.1",
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/",