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 {
|
|
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
|
|
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 {
|
|
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 =
|
|
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
|
|
121
|
-
return
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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",
|