mssql 12.2.2 → 12.3.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
@@ -133,6 +133,7 @@ const config = {
133
133
  ### Connections
134
134
 
135
135
  * [Pool Management](#pool-management)
136
+ * [Connection Validation](#connection-validation)
136
137
  * [ConnectionPool](#connections-1)
137
138
  * [connect](#connect-callback)
138
139
  * [close](#close)
@@ -574,6 +575,30 @@ sql.query('SELECT * FROM [example]').then((result) => {
574
575
  })
575
576
  ```
576
577
 
578
+ ### Connection Validation
579
+
580
+ When a connection is acquired from the pool, it can be validated to ensure it is still usable. This is controlled by the `validateConnection` config option.
581
+
582
+ ```javascript
583
+ const config = {
584
+ server: 'localhost',
585
+ // ...
586
+ validateConnection: true // default
587
+ }
588
+ ```
589
+
590
+ The following values are supported:
591
+
592
+ | Value | Description |
593
+ |---|---|
594
+ | `true` (default) | Executes `SELECT 1` against the connection before handing it to the caller. This is the most thorough check — it verifies end-to-end connectivity — but adds a round-trip query for every pool acquisition. |
595
+ | `'socket'` | Performs a lightweight, synchronous check of the underlying connection state and TCP socket health. No SQL query is executed. This is significantly cheaper at scale and catches most failure modes (closed connections, destroyed sockets, wrong protocol state), but will not detect issues like server-side session invalidation. **Tedious driver only** — with msnodesqlv8, this value falls back to `SELECT 1` behaviour because native ODBC connections do not expose socket-level properties. |
596
+ | `false` | Disables validation entirely. The connection is assumed to be healthy if it has not been flagged as closed or errored. Use this only if your application already handles stale connection errors gracefully. |
597
+
598
+ #### When to use `'socket'` mode
599
+
600
+ If your application maintains a large connection pool and you see high volumes of `SELECT 1` queries in your SQL Server monitoring, switching to `'socket'` mode can dramatically reduce overhead. TCP keepalive (enabled by default in tedious at 30-second intervals) will independently detect and close dead connections over time, so the socket-level check provides a good balance between reliability and performance.
601
+
577
602
  ## Configuration
578
603
 
579
604
  The following is an example configuration object:
@@ -608,6 +633,7 @@ const config = {
608
633
  - **pool.min** - The minimum of connections there can be in the pool (default: `0`).
609
634
  - **pool.idleTimeoutMillis** - The Number of milliseconds before closing an unused connection (default: `30000`).
610
635
  - **arrayRowMode** - Return row results as a an array instead of a keyed object. Also adds `columns` array. (default: `false`) See [Handling Duplicate Column Names](#handling-duplicate-column-names)
636
+ - **validateConnection** - Controls how connections are validated when acquired from the pool. See [Connection Validation](#connection-validation) for details. (default: `true`)
611
637
 
612
638
  Complete list of pool options can be found [here](https://github.com/vincit/tarn.js/#usage).
613
639
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  const { EventEmitter } = require('node:events')
4
4
  const debug = require('debug')('mssql:base')
5
- const { parseSqlConnectionString } = require('@tediousjs/connection-string')
5
+ const { parse, MSSQL_SCHEMA } = require('@tediousjs/connection-string')
6
6
  const tarn = require('tarn')
7
7
  const { IDS } = require('../utils')
8
8
  const ConnectionError = require('../error/connection-error')
@@ -99,6 +99,7 @@ class ConnectionPool extends EventEmitter {
99
99
  }
100
100
 
101
101
  static _parseAuthenticationType (type, entries) {
102
+ if (!type) return 'default'
102
103
  switch (type.toLowerCase()) {
103
104
  case 'active directory integrated':
104
105
  if (entries.includes('token')) {
@@ -121,7 +122,15 @@ class ConnectionPool extends EventEmitter {
121
122
  }
122
123
 
123
124
  static _parseConnectionString (connectionString) {
124
- const parsed = parseSqlConnectionString(connectionString, true, true)
125
+ const result = parse(connectionString)
126
+ const parsed = result.toSchema(MSSQL_SCHEMA)
127
+ // Include non-standard keys (client id, tenant id, token, msi endpoint, msi secret)
128
+ // that are used for authentication but not part of the MSSQL schema
129
+ for (const [key, value] of result) {
130
+ if (!(key in parsed)) {
131
+ parsed[key] = value
132
+ }
133
+ }
125
134
  return Object.entries(parsed).reduce((config, [key, value]) => {
126
135
  switch (key) {
127
136
  case 'application name':
@@ -18,12 +18,6 @@ class ConnectionError extends MSSQLError {
18
18
  super(message, code)
19
19
 
20
20
  this.name = 'ConnectionError'
21
-
22
- let err = message?.details
23
- if (err instanceof Array && (err = err.at(-1))) {
24
- this.message = err.message
25
- this.originalError = err
26
- }
27
21
  }
28
22
  }
29
23
 
@@ -7,7 +7,7 @@ const { IDS, INCREMENT } = require('../utils')
7
7
  const shared = require('../shared')
8
8
  const ConnectionError = require('../error/connection-error')
9
9
  const { platform } = require('node:os')
10
- const { buildConnectionString } = require('@tediousjs/connection-string')
10
+ const { build } = require('@tediousjs/connection-string')
11
11
 
12
12
  const DEFAULT_CONNECTION_DRIVER = ['darwin', 'linux'].includes(platform()) ? 'ODBC Driver 17 for SQL Server' : 'SQL Server Native Client 11.0'
13
13
 
@@ -22,7 +22,7 @@ class ConnectionPool extends BaseConnectionPool {
22
22
  }
23
23
 
24
24
  if (!this.config.connectionString) {
25
- cfg.conn_str = buildConnectionString({
25
+ cfg.conn_str = build({
26
26
  Driver: this.config.driver && this.config.driver !== 'msnodesqlv8' ? this.config.driver : DEFAULT_CONNECTION_DRIVER,
27
27
  Server: this.config.options.instanceName ? `${this.config.server}\\${this.config.options.instanceName}` : `${this.config.server},${this.config.port}`,
28
28
  Database: this.config.database,
@@ -43,10 +43,18 @@ class ConnectionPool extends BaseConnectionPool {
43
43
 
44
44
  msnodesql.open(cfg, (err, tds) => {
45
45
  if (err) {
46
+ let customErr = err?.details
47
+ if (customErr instanceof Array && (customErr = customErr.at(-1))) {
48
+ err.message = customErr.message
49
+ err.code = customErr.code
50
+ }
51
+
46
52
  err = new ConnectionError(err.message, err.code)
47
53
  return reject(err)
48
54
  }
49
55
 
56
+ tds.setUseNumericString(true)
57
+
50
58
  IDS.add(tds, 'Connection', connedtionId)
51
59
  tds.setUseUTC(this.config.options.useUTC)
52
60
  debug('connection(%d): established', IDS.get(tds))
@@ -31,6 +31,7 @@ const castParameter = function (value, type) {
31
31
  case TYPES.NChar:
32
32
  case TYPES.Xml:
33
33
  case TYPES.Text:
34
+ case TYPES.BigInt:
34
35
  case TYPES.NText:
35
36
  if ((typeof value !== 'string') && !(value instanceof String)) {
36
37
  value = value.toString()
@@ -39,7 +40,6 @@ const castParameter = function (value, type) {
39
40
 
40
41
  case TYPES.Int:
41
42
  case TYPES.TinyInt:
42
- case TYPES.BigInt:
43
43
  case TYPES.SmallInt:
44
44
  if ((typeof value !== 'number') && !(value instanceof Number)) {
45
45
  value = parseInt(value)
@@ -122,6 +122,8 @@ const createColumns = function (metadata, arrayRowMode) {
122
122
  return out
123
123
  }
124
124
 
125
+ const numericStringSqlTypes = new Set(['int', 'smallint', 'tinyint', 'float', 'real', 'numeric', 'decimal', 'money', 'smallmoney'])
126
+
125
127
  const valueCorrection = function (value, metadata) {
126
128
  const type = metadata && objectHasProperty(metadata, 'sqlType') && objectHasProperty(DECLARATIONS, metadata.sqlType)
127
129
  ? DECLARATIONS[metadata.sqlType]
@@ -137,6 +139,10 @@ const valueCorrection = function (value, metadata) {
137
139
  } else {
138
140
  return value
139
141
  }
142
+ } else if (typeof value === 'string' && numericStringSqlTypes.has(metadata.sqlType)) {
143
+ return Number(value)
144
+ } else if (typeof value === 'string' && metadata.sqlType === 'sql_variant' && value !== '' && !isNaN(Number(value))) {
145
+ return Number(value)
140
146
  } else {
141
147
  return value
142
148
  }
@@ -177,6 +183,10 @@ class Request extends BaseRequest {
177
183
  return callback(new RequestError('Canceled.', 'ECANCEL'))
178
184
  }
179
185
 
186
+ // Disable useNumericString during bulk operations to avoid
187
+ // msnodesqlv8 SQL generation bugs with its table manager
188
+ connection.setUseNumericString(false)
189
+
180
190
  const done = (err, rowCount) => {
181
191
  if (hasReturned) {
182
192
  return
@@ -184,6 +194,8 @@ class Request extends BaseRequest {
184
194
 
185
195
  hasReturned = true
186
196
 
197
+ connection.setUseNumericString(true)
198
+
187
199
  if (err) {
188
200
  if ((typeof err.sqlstate === 'string') && (err.sqlstate.toLowerCase() === '08s01')) {
189
201
  connection.hasError = true
@@ -117,15 +117,36 @@ class ConnectionPool extends BaseConnectionPool {
117
117
  }
118
118
 
119
119
  _poolValidate (tedious) {
120
- if (tedious && !tedious.closed && !tedious.hasError) {
121
- return !this.config.validateConnection || new shared.Promise((resolve) => {
122
- const req = new tds.Request('SELECT 1;', (err) => {
123
- resolve(!err)
124
- })
125
- tedious.execSql(req)
126
- })
120
+ if (!tedious || tedious.closed || tedious.hasError) {
121
+ return false
122
+ }
123
+
124
+ const mode = this.config.validateConnection
125
+
126
+ if (!mode) {
127
+ return true
128
+ }
129
+
130
+ // Socket-level validation: check connection state and socket health
131
+ // without executing a SQL query. Much cheaper than SELECT 1 at scale.
132
+ if (mode === 'socket') {
133
+ if (tedious.state !== tedious.STATE.LOGGED_IN) {
134
+ return false
135
+ }
136
+ if (!tedious.socket || tedious.socket.destroyed || !tedious.socket.writable) {
137
+ return false
138
+ }
139
+ return true
127
140
  }
128
- return false
141
+
142
+ // SQL-level validation (default): execute SELECT 1 to verify the
143
+ // connection is fully functional end-to-end.
144
+ return new shared.Promise((resolve) => {
145
+ const req = new tds.Request('SELECT 1;', (err) => {
146
+ resolve(!err)
147
+ })
148
+ tedious.execSql(req)
149
+ })
129
150
  }
130
151
 
131
152
  _poolDestroy (tedious) {
package/package.json CHANGED
@@ -21,7 +21,7 @@
21
21
  "azure",
22
22
  "node-mssql"
23
23
  ],
24
- "version": "12.2.2",
24
+ "version": "12.3.0",
25
25
  "main": "index.js",
26
26
  "type": "commonjs",
27
27
  "repository": {
@@ -34,7 +34,7 @@
34
34
  },
35
35
  "license": "MIT",
36
36
  "dependencies": {
37
- "@tediousjs/connection-string": "^0.6.0",
37
+ "@tediousjs/connection-string": "^1.0.0",
38
38
  "commander": "^11.0.0",
39
39
  "debug": "^4.3.3",
40
40
  "tarn": "^3.0.2",