mssql 9.1.2 → 9.1.4
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 +112 -9
- package/lib/base/prepared-statement.js +18 -10
- package/lib/base/request.js +5 -5
- package/lib/datatypes.js +26 -12
- package/lib/msnodesqlv8/connection-pool.js +9 -2
- package/lib/msnodesqlv8/request.js +91 -24
- package/lib/table.js +3 -3
- package/lib/tedious/request.js +128 -34
- package/lib/utils.js +88 -0
- package/package.json +6 -6
- package/CHANGELOG.txt +0 -689
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Microsoft SQL Server client for Node.js
|
|
4
4
|
|
|
5
|
-
[![NPM Version][npm-image]][npm-url] [![NPM Downloads][downloads-image]][downloads-url] [![
|
|
5
|
+
[![NPM Version][npm-image]][npm-url] [![NPM Downloads][downloads-image]][downloads-url] [](https://gitter.im/patriksimek/node-mssql?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
|
6
6
|
|
|
7
7
|
Supported TDS drivers:
|
|
8
8
|
- [Tedious][tedious-url] (pure JavaScript - Windows/macOS/Linux, default)
|
|
@@ -108,6 +108,13 @@ async () => {
|
|
|
108
108
|
* [bulk](#bulk-table-options-callback)
|
|
109
109
|
* [cancel](#cancel)
|
|
110
110
|
|
|
111
|
+
### Identifier rules
|
|
112
|
+
|
|
113
|
+
* [Parameter names](#parameter-names)
|
|
114
|
+
* [Column names](#column-names)
|
|
115
|
+
* [Type names and sizes](#type-names-and-sizes)
|
|
116
|
+
* [Procedure names](#procedure-names)
|
|
117
|
+
|
|
111
118
|
### Transactions
|
|
112
119
|
|
|
113
120
|
* [Transaction](#transaction)
|
|
@@ -746,6 +753,7 @@ request.execute('procedure_name', (err, result) => {
|
|
|
746
753
|
|
|
747
754
|
__Errors__
|
|
748
755
|
- EREQUEST (`RequestError`) - *Message from SQL Server*
|
|
756
|
+
- EINJECT (`RequestError`) - Procedure name or parameter type is not a valid identifier. See [Identifiers](#identifiers).
|
|
749
757
|
- ECANCEL (`RequestError`) - Cancelled.
|
|
750
758
|
- ETIMEOUT (`RequestError`) - Request timeout.
|
|
751
759
|
- ENOCONN (`RequestError`) - No connection is specified for that request.
|
|
@@ -798,7 +806,7 @@ sql.map.register(Number, sql.BigInt)
|
|
|
798
806
|
|
|
799
807
|
__Errors__ (synchronous)
|
|
800
808
|
- EARGS (`RequestError`) - Invalid number of arguments.
|
|
801
|
-
- EINJECT (`RequestError`) -
|
|
809
|
+
- EINJECT (`RequestError`) - Parameter name is not a valid identifier. See [Identifiers](#identifiers).
|
|
802
810
|
|
|
803
811
|
---------------------------------------
|
|
804
812
|
|
|
@@ -823,7 +831,7 @@ request.output('output_parameter', sql.VarChar(50), 'abc')
|
|
|
823
831
|
|
|
824
832
|
__Errors__ (synchronous)
|
|
825
833
|
- EARGS (`RequestError`) - Invalid number of arguments.
|
|
826
|
-
- EINJECT (`RequestError`) -
|
|
834
|
+
- EINJECT (`RequestError`) - Parameter name is not a valid identifier. See [Identifiers](#identifiers).
|
|
827
835
|
|
|
828
836
|
---------------------------------------
|
|
829
837
|
|
|
@@ -907,6 +915,7 @@ __Errors__
|
|
|
907
915
|
- ECONNCLOSED (`ConnectionError`) - Connection is closed.
|
|
908
916
|
- ENOTBEGUN (`TransactionError`) - Transaction has not begun.
|
|
909
917
|
- EABORT (`TransactionError`) - Transaction was aborted (by user or because of an error).
|
|
918
|
+
- EINJECT (`RequestError`) - Parameter type is not a valid identifier. See [Identifiers](#identifiers).
|
|
910
919
|
|
|
911
920
|
```javascript
|
|
912
921
|
const request = new sql.Request()
|
|
@@ -952,6 +961,7 @@ __Errors__
|
|
|
952
961
|
- ECONNCLOSED (`ConnectionError`) - Connection is closed.
|
|
953
962
|
- ENOTBEGUN (`TransactionError`) - Transaction has not begun.
|
|
954
963
|
- EABORT (`TransactionError`) - Transaction was aborted (by user or because of an error).
|
|
964
|
+
- EINJECT (`RequestError`) - Parameter type is not a valid identifier. See [Identifiers](#identifiers).
|
|
955
965
|
|
|
956
966
|
You can enable multiple recordsets in queries with the `request.multiple = true` command.
|
|
957
967
|
|
|
@@ -998,6 +1008,7 @@ __Errors__
|
|
|
998
1008
|
- ECONNCLOSED (`ConnectionError`) - Connection is closed.
|
|
999
1009
|
- ENOTBEGUN (`TransactionError`) - Transaction has not begun.
|
|
1000
1010
|
- EABORT (`TransactionError`) - Transaction was aborted (by user or because of an error).
|
|
1011
|
+
- EINJECT (`RequestError`) - Column name or parameter type is not a valid identifier. See [Identifiers](#identifiers).
|
|
1001
1012
|
|
|
1002
1013
|
---------------------------------------
|
|
1003
1014
|
|
|
@@ -1228,7 +1239,7 @@ ps.input('input_parameter', sql.VarChar(50))
|
|
|
1228
1239
|
|
|
1229
1240
|
__Errors__ (synchronous)
|
|
1230
1241
|
- EARGS (`PreparedStatementError`) - Invalid number of arguments.
|
|
1231
|
-
- EINJECT (`PreparedStatementError`) -
|
|
1242
|
+
- EINJECT (`PreparedStatementError`) - Parameter name is not a valid identifier. See [Identifiers](#identifiers).
|
|
1232
1243
|
|
|
1233
1244
|
---------------------------------------
|
|
1234
1245
|
|
|
@@ -1250,7 +1261,7 @@ ps.output('output_parameter', sql.VarChar(50))
|
|
|
1250
1261
|
|
|
1251
1262
|
__Errors__ (synchronous)
|
|
1252
1263
|
- EARGS (`PreparedStatementError`) - Invalid number of arguments.
|
|
1253
|
-
- EINJECT (`PreparedStatementError`) -
|
|
1264
|
+
- EINJECT (`PreparedStatementError`) - Parameter name is not a valid identifier. See [Identifiers](#identifiers).
|
|
1254
1265
|
|
|
1255
1266
|
---------------------------------------
|
|
1256
1267
|
|
|
@@ -1823,6 +1834,100 @@ Results in:
|
|
|
1823
1834
|
]
|
|
1824
1835
|
```
|
|
1825
1836
|
|
|
1837
|
+
## Identifiers
|
|
1838
|
+
|
|
1839
|
+
Values are sent to the server as parameters and may hold anything. A few things
|
|
1840
|
+
are not values, and cannot be: the name of a parameter, the name of a column in a
|
|
1841
|
+
bulk load, the type a parameter is declared as, and the name of a stored procedure.
|
|
1842
|
+
Those are *identifiers*, and
|
|
1843
|
+
no SQL database can bind them, so this library builds them into the statement it
|
|
1844
|
+
sends. It checks them, and rejects anything that could end the identifier and have
|
|
1845
|
+
the rest of the value read as SQL.
|
|
1846
|
+
|
|
1847
|
+
That check is not a substitute for your own. If any of these comes from somewhere
|
|
1848
|
+
you do not control, map it to a value you expect first:
|
|
1849
|
+
|
|
1850
|
+
```javascript
|
|
1851
|
+
const columns = { name: 'name', created: 'created_at' } // what you allow
|
|
1852
|
+
const column = columns[req.query.sortBy] // what they asked for
|
|
1853
|
+
if (!column) throw new Error('unknown column')
|
|
1854
|
+
|
|
1855
|
+
request.input(column, sql.NVarChar, value)
|
|
1856
|
+
```
|
|
1857
|
+
|
|
1858
|
+
### Parameter names
|
|
1859
|
+
|
|
1860
|
+
`request.input()`, `request.output()` and the same methods on a prepared statement
|
|
1861
|
+
take a name without the leading `@`. It may hold letters, digits, marks and
|
|
1862
|
+
connector punctuation from any script, along with `@`, `#`, `$` and the two
|
|
1863
|
+
zero-width joiners. A name that is not a string or a number is rejected, as is one
|
|
1864
|
+
holding an invisible format character, because one of them is a separator the
|
|
1865
|
+
server treats as ending the identifier.
|
|
1866
|
+
|
|
1867
|
+
Anything else raises `EINJECT`. Note that a name this library accepts may still be
|
|
1868
|
+
one the server rejects; the check is deliberately wider than the server's own rules
|
|
1869
|
+
so that it only refuses what could change the shape of the statement.
|
|
1870
|
+
|
|
1871
|
+
### Column names
|
|
1872
|
+
|
|
1873
|
+
`table.columns.add()` names a column in a bulk load, and the name is emitted as a
|
|
1874
|
+
quoted identifier. A `]` ends that quoting, so a name holding one must double it,
|
|
1875
|
+
which is how T-SQL escapes it:
|
|
1876
|
+
|
|
1877
|
+
```javascript
|
|
1878
|
+
table.columns.add('a]]b', sql.Int) // the column named a]b
|
|
1879
|
+
```
|
|
1880
|
+
|
|
1881
|
+
A single `]` raises `EINJECT`. Column names are not otherwise restricted: spaces,
|
|
1882
|
+
punctuation and reserved words are all fine, because the quoting handles them.
|
|
1883
|
+
|
|
1884
|
+
The keys of `options.order` are also identifiers, but the driver writes them into the bulk
|
|
1885
|
+
statement's `ORDER (...)` clause *unquoted* rather than as `[name]`. They follow the type
|
|
1886
|
+
name rule instead: bare, or quoted with brackets or double quotes.
|
|
1887
|
+
|
|
1888
|
+
```javascript
|
|
1889
|
+
request.bulk(table, { order: { created_at: 'ASC' } })
|
|
1890
|
+
request.bulk(table, { order: { '[Created At]': 'DESC' } }) // quote a spaced name yourself
|
|
1891
|
+
```
|
|
1892
|
+
|
|
1893
|
+
### Type names and sizes
|
|
1894
|
+
|
|
1895
|
+
`sql.TVP()` takes the name of a table type, optionally qualified with a schema and
|
|
1896
|
+
optionally quoted with brackets or double quotes. A size — the length, precision or
|
|
1897
|
+
scale of a type such as `sql.VarChar()` — must be a number, or the word `max` the
|
|
1898
|
+
declaration accepts in its place. Padding is trimmed.
|
|
1899
|
+
|
|
1900
|
+
```javascript
|
|
1901
|
+
request.input('rows', sql.TVP('dbo.[My Type]'), table)
|
|
1902
|
+
request.input('name', sql.VarChar(50), value)
|
|
1903
|
+
request.input('note', sql.VarChar('max'), value)
|
|
1904
|
+
```
|
|
1905
|
+
|
|
1906
|
+
A size is checked wherever it is built into SQL, which includes the declaration a
|
|
1907
|
+
bulk load sends and the parameter list a query sends, not only `sql.TVP()`.
|
|
1908
|
+
|
|
1909
|
+
### Procedure names
|
|
1910
|
+
|
|
1911
|
+
`request.execute()` takes the name of a stored procedure, optionally qualified with
|
|
1912
|
+
a server, database and schema and optionally quoted with brackets or double quotes.
|
|
1913
|
+
An omitted part may be left empty, as T-SQL allows. The `msnodesqlv8` driver builds
|
|
1914
|
+
this into the statement it sends; the `tedious` driver sends it as a bound remote
|
|
1915
|
+
procedure call and could not be injected through it. Both check it, so a name is
|
|
1916
|
+
accepted or rejected the same way whichever driver you use.
|
|
1917
|
+
|
|
1918
|
+
```javascript
|
|
1919
|
+
request.execute('dbo.[My Procedure]')
|
|
1920
|
+
request.execute('master..sp_who') // an omitted schema is fine
|
|
1921
|
+
```
|
|
1922
|
+
|
|
1923
|
+
The first and last parts must be present, so `.proc` and `db..` are rejected.
|
|
1924
|
+
|
|
1925
|
+
Anything else raises `EINJECT`. Numbered procedures — the deprecated
|
|
1926
|
+
`procedure;1` form — are rejected, because the `;` cannot be told apart from the
|
|
1927
|
+
end of a statement.
|
|
1928
|
+
|
|
1929
|
+
---------------------------------------
|
|
1930
|
+
|
|
1826
1931
|
## Errors
|
|
1827
1932
|
|
|
1828
1933
|
There are 4 types of errors you can handle:
|
|
@@ -1859,10 +1964,10 @@ Name | Code | Message
|
|
|
1859
1964
|
`RequestError` | ECANCEL | Cancelled.
|
|
1860
1965
|
`RequestError` | ETIMEOUT | Request timeout.
|
|
1861
1966
|
`RequestError` | EARGS | Invalid number of arguments.
|
|
1862
|
-
`RequestError` | EINJECT |
|
|
1967
|
+
`RequestError` | EINJECT | Parameter name, column name or type is not a valid identifier.
|
|
1863
1968
|
`RequestError` | ENOCONN | No connection is specified for that request.
|
|
1864
1969
|
`PreparedStatementError` | EARGS | Invalid number of arguments.
|
|
1865
|
-
`PreparedStatementError` | EINJECT |
|
|
1970
|
+
`PreparedStatementError` | EINJECT | Parameter name is not a valid identifier.
|
|
1866
1971
|
`PreparedStatementError` | EALREADYPREPARED | Statement is already prepared.
|
|
1867
1972
|
`PreparedStatementError` | ENOTPREPARED | Statement is not prepared.
|
|
1868
1973
|
|
|
@@ -2093,8 +2198,6 @@ to create new connections or not
|
|
|
2093
2198
|
[downloads-url]: https://www.npmjs.com/package/mssql
|
|
2094
2199
|
[david-image]: https://img.shields.io/david/tediousjs/node-mssql.svg?style=flat-square
|
|
2095
2200
|
[david-url]: https://david-dm.org/tediousjs/node-mssql
|
|
2096
|
-
[appveyor-image]: https://ci.appveyor.com/api/projects/status/e5gq1a0ujwams9t7/branch/master?svg=true
|
|
2097
|
-
[appveyor-url]: https://ci.appveyor.com/project/tediousjs/node-mssql
|
|
2098
2201
|
|
|
2099
2202
|
[tedious-url]: https://www.npmjs.com/package/tedious
|
|
2100
2203
|
[msnodesqlv8-url]: https://www.npmjs.com/package/msnodesqlv8
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const debug = require('debug')('mssql:base')
|
|
4
4
|
const { EventEmitter } = require('events')
|
|
5
|
-
const { IDS, objectHasProperty } = require('../utils')
|
|
5
|
+
const { IDS, objectHasProperty, isValidParameterName } = require('../utils')
|
|
6
6
|
const globalConnection = require('../global-connection')
|
|
7
7
|
const { TransactionError, PreparedStatementError } = require('../error')
|
|
8
8
|
const shared = require('../shared')
|
|
@@ -91,8 +91,8 @@ class PreparedStatement extends EventEmitter {
|
|
|
91
91
|
*/
|
|
92
92
|
|
|
93
93
|
input (name, type) {
|
|
94
|
-
if ((
|
|
95
|
-
throw new PreparedStatementError(`
|
|
94
|
+
if (!isValidParameterName(name)) {
|
|
95
|
+
throw new PreparedStatementError(`Invalid parameter name '${name}'. Parameter names may only contain identifier characters`, 'EINJECT')
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
if (arguments.length < 2) {
|
|
@@ -144,8 +144,8 @@ class PreparedStatement extends EventEmitter {
|
|
|
144
144
|
*/
|
|
145
145
|
|
|
146
146
|
output (name, type) {
|
|
147
|
-
if (
|
|
148
|
-
throw new PreparedStatementError(`
|
|
147
|
+
if (!isValidParameterName(name)) {
|
|
148
|
+
throw new PreparedStatementError(`Invalid parameter name '${name}'. Parameter names may only contain identifier characters`, 'EINJECT')
|
|
149
149
|
}
|
|
150
150
|
|
|
151
151
|
if (arguments.length < 2) {
|
|
@@ -235,17 +235,25 @@ class PreparedStatement extends EventEmitter {
|
|
|
235
235
|
const req = new shared.driver.Request(this)
|
|
236
236
|
req.stream = false
|
|
237
237
|
req.output('handle', TYPES.Int)
|
|
238
|
-
|
|
239
|
-
|
|
238
|
+
let params
|
|
239
|
+
try {
|
|
240
|
+
params = []
|
|
240
241
|
for (const name in this.parameters) {
|
|
241
242
|
if (!objectHasProperty(this.parameters, name)) {
|
|
242
243
|
continue
|
|
243
244
|
}
|
|
244
245
|
const param = this.parameters[name]
|
|
245
|
-
|
|
246
|
+
// a connection is held here, so a rejected declaration has to be reported
|
|
247
|
+
// through the path that releases it
|
|
248
|
+
params.push(`@${name} ${declare(param.type, param)}${param.io === 2 ? ' output' : ''}`)
|
|
246
249
|
}
|
|
247
|
-
|
|
248
|
-
|
|
250
|
+
} catch (e) {
|
|
251
|
+
this.parent.release(this._acquiredConnection)
|
|
252
|
+
this._acquiredConnection = null
|
|
253
|
+
this._acquiredConfig = null
|
|
254
|
+
return callback(new PreparedStatementError(e, 'EREQUEST'))
|
|
255
|
+
}
|
|
256
|
+
req.input('params', TYPES.NVarChar, params.join(','))
|
|
249
257
|
req.input('stmt', TYPES.NVarChar, this.statement)
|
|
250
258
|
req.execute('sp_prepare', (err, result) => {
|
|
251
259
|
if (err) {
|
package/lib/base/request.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
const debug = require('debug')('mssql:base')
|
|
4
4
|
const { EventEmitter } = require('events')
|
|
5
5
|
const { Readable } = require('stream')
|
|
6
|
-
const { IDS, objectHasProperty } = require('../utils')
|
|
6
|
+
const { IDS, objectHasProperty, isValidParameterName } = require('../utils')
|
|
7
7
|
const globalConnection = require('../global-connection')
|
|
8
8
|
const { RequestError, ConnectionError } = require('../error')
|
|
9
9
|
const { TYPES } = require('../datatypes')
|
|
@@ -107,8 +107,8 @@ class Request extends EventEmitter {
|
|
|
107
107
|
*/
|
|
108
108
|
|
|
109
109
|
input (name, type, value) {
|
|
110
|
-
if ((
|
|
111
|
-
throw new RequestError(`
|
|
110
|
+
if (!isValidParameterName(name)) {
|
|
111
|
+
throw new RequestError(`Invalid parameter name '${name}'. Parameter names may only contain identifier characters`, 'EINJECT')
|
|
112
112
|
}
|
|
113
113
|
|
|
114
114
|
if (arguments.length < 2) {
|
|
@@ -170,8 +170,8 @@ class Request extends EventEmitter {
|
|
|
170
170
|
output (name, type, value) {
|
|
171
171
|
if (!type) { type = TYPES.NVarChar }
|
|
172
172
|
|
|
173
|
-
if ((
|
|
174
|
-
throw new RequestError(`
|
|
173
|
+
if (!isValidParameterName(name)) {
|
|
174
|
+
throw new RequestError(`Invalid parameter name '${name}'. Parameter names may only contain identifier characters`, 'EINJECT')
|
|
175
175
|
}
|
|
176
176
|
|
|
177
177
|
if ((type === TYPES.Text) || (type === TYPES.NText) || (type === TYPES.Image)) {
|
package/lib/datatypes.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
'use strict'
|
|
2
|
-
const objectHasProperty = require('./utils')
|
|
2
|
+
const { objectHasProperty, assertSafeTypeName, assertSafeTypeSize } = require('./utils')
|
|
3
3
|
const inspect = Symbol.for('nodejs.util.inspect.custom')
|
|
4
4
|
|
|
5
5
|
const TYPES = {
|
|
@@ -132,19 +132,33 @@ for (const key in TYPES) {
|
|
|
132
132
|
}
|
|
133
133
|
|
|
134
134
|
module.exports.declare = (type, options) => {
|
|
135
|
+
// sizes and the type name reach the generated declaration directly, so they are checked
|
|
136
|
+
// here rather than trusted from whatever built the parameter. Each is read only in the
|
|
137
|
+
// branch that emits it, so a type that takes no size still needs no options.
|
|
135
138
|
switch (type) {
|
|
136
|
-
case TYPES.VarChar: case TYPES.VarBinary:
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
case TYPES.
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
case TYPES.
|
|
145
|
-
|
|
139
|
+
case TYPES.VarChar: case TYPES.VarBinary: {
|
|
140
|
+
const length = assertSafeTypeSize(options.length)
|
|
141
|
+
return `${type.declaration} (${length > 8000 ? 'MAX' : (length == null ? 'MAX' : length)})`
|
|
142
|
+
}
|
|
143
|
+
case TYPES.NVarChar: {
|
|
144
|
+
const length = assertSafeTypeSize(options.length)
|
|
145
|
+
return `${type.declaration} (${length > 4000 ? 'MAX' : (length == null ? 'MAX' : length)})`
|
|
146
|
+
}
|
|
147
|
+
case TYPES.Char: case TYPES.NChar: case TYPES.Binary: {
|
|
148
|
+
const length = assertSafeTypeSize(options.length)
|
|
149
|
+
return `${type.declaration} (${length == null ? 1 : length})`
|
|
150
|
+
}
|
|
151
|
+
case TYPES.Decimal: case TYPES.Numeric: {
|
|
152
|
+
const precision = assertSafeTypeSize(options.precision)
|
|
153
|
+
const scale = assertSafeTypeSize(options.scale)
|
|
154
|
+
return `${type.declaration} (${precision == null ? 18 : precision}, ${scale == null ? 0 : scale})`
|
|
155
|
+
}
|
|
156
|
+
case TYPES.Time: case TYPES.DateTime2: case TYPES.DateTimeOffset: {
|
|
157
|
+
const scale = assertSafeTypeSize(options.scale)
|
|
158
|
+
return `${type.declaration} (${scale == null ? 7 : scale})`
|
|
159
|
+
}
|
|
146
160
|
case TYPES.TVP:
|
|
147
|
-
return `${options.tvpType} readonly`
|
|
161
|
+
return `${assertSafeTypeName(options.tvpType)} readonly`
|
|
148
162
|
default:
|
|
149
163
|
return type.declaration
|
|
150
164
|
}
|
|
@@ -38,8 +38,15 @@ class ConnectionPool extends BaseConnectionPool {
|
|
|
38
38
|
return this.config.options.trustedConnection ? 'Yes' : 'No'
|
|
39
39
|
case 'encrypt':
|
|
40
40
|
return this.config.options.encrypt ? 'Yes' : 'No'
|
|
41
|
-
default:
|
|
42
|
-
|
|
41
|
+
default: {
|
|
42
|
+
let val = this.config[key] || ''
|
|
43
|
+
// quote strings that contain '{' or '}' but not ones that start and end with them (assume they are already quoted)
|
|
44
|
+
if (val && typeof val === 'string' && !(val.startsWith('{') && val.endsWith('}')) && (val.indexOf('{') !== -1 || val.indexOf('}') !== -1)) {
|
|
45
|
+
// quote values in `{}` and escape any existing `}` chars
|
|
46
|
+
val = `{${val.replace(/}/g, '}}')}}`
|
|
47
|
+
}
|
|
48
|
+
return val
|
|
49
|
+
}
|
|
43
50
|
}
|
|
44
51
|
})
|
|
45
52
|
|
|
@@ -4,7 +4,7 @@ const msnodesql = require('msnodesqlv8')
|
|
|
4
4
|
const debug = require('debug')('mssql:msv8')
|
|
5
5
|
const BaseRequest = require('../base/request')
|
|
6
6
|
const RequestError = require('../error/request-error')
|
|
7
|
-
const { IDS, objectHasProperty } = require('../utils')
|
|
7
|
+
const { IDS, objectHasProperty, assertSafeColumnName, assertSafeTypeName, assertSafeTypeSize, assertSafeProcedureName } = require('../utils')
|
|
8
8
|
const { TYPES, DECLARATIONS, declare } = require('../datatypes')
|
|
9
9
|
const { PARSERS: UDT } = require('../udt')
|
|
10
10
|
const Table = require('../table')
|
|
@@ -154,16 +154,32 @@ class Request extends BaseRequest {
|
|
|
154
154
|
|
|
155
155
|
try {
|
|
156
156
|
table._makeBulk()
|
|
157
|
+
// Checked before a connection is borrowed, so that a rejected name cannot leave
|
|
158
|
+
// the pool short of one.
|
|
159
|
+
table.columns.forEach(col => {
|
|
160
|
+
assertSafeColumnName(col.name)
|
|
161
|
+
// This driver hands the column definition to its table manager rather than writing
|
|
162
|
+
// it into SQL, so a size never reaches a statement here. It is checked all the same,
|
|
163
|
+
// so that a column either works on both drivers or on neither.
|
|
164
|
+
assertSafeTypeSize(col.length)
|
|
165
|
+
assertSafeTypeSize(col.precision)
|
|
166
|
+
assertSafeTypeSize(col.scale)
|
|
167
|
+
})
|
|
168
|
+
// checked here too, so an ordering key is accepted or rejected the same way on
|
|
169
|
+
// either driver even though this one does not build the statement itself
|
|
170
|
+
if (options && options.order) {
|
|
171
|
+
Object.keys(options.order).forEach(key => assertSafeTypeName(key))
|
|
172
|
+
}
|
|
157
173
|
} catch (e) {
|
|
158
174
|
return callback(new RequestError(e, 'EREQUEST'))
|
|
159
175
|
}
|
|
160
176
|
|
|
161
177
|
if (!table.name) {
|
|
162
|
-
setImmediate(callback, new RequestError('Table name must be specified for bulk insert.', 'ENAME'))
|
|
178
|
+
return setImmediate(callback, new RequestError('Table name must be specified for bulk insert.', 'ENAME'))
|
|
163
179
|
}
|
|
164
180
|
|
|
165
181
|
if (table.name.charAt(0) === '@') {
|
|
166
|
-
setImmediate(callback, new RequestError("You can't use table variables for bulk insert.", 'ENAME'))
|
|
182
|
+
return setImmediate(callback, new RequestError("You can't use table variables for bulk insert.", 'ENAME'))
|
|
167
183
|
}
|
|
168
184
|
|
|
169
185
|
this.parent.acquire(this, (err, connection) => {
|
|
@@ -189,8 +205,11 @@ class Request extends BaseRequest {
|
|
|
189
205
|
connection.hasError = true
|
|
190
206
|
}
|
|
191
207
|
|
|
208
|
+
// a rejected identifier keeps its own code; anything else is a driver
|
|
209
|
+
// error, whose numeric code must not become the public one
|
|
210
|
+
const rejectedIdentifier = err.code === 'EINJECT'
|
|
192
211
|
err = new RequestError(err)
|
|
193
|
-
err.code = 'EREQUEST'
|
|
212
|
+
err.code = rejectedIdentifier ? 'EINJECT' : 'EREQUEST'
|
|
194
213
|
}
|
|
195
214
|
|
|
196
215
|
this.parent.release(connection)
|
|
@@ -210,14 +229,29 @@ class Request extends BaseRequest {
|
|
|
210
229
|
}
|
|
211
230
|
|
|
212
231
|
const rows = []
|
|
213
|
-
|
|
214
|
-
const
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
232
|
+
try {
|
|
233
|
+
for (const col of table.columns) {
|
|
234
|
+
// re-checked here for the same reason as the name below, in case the
|
|
235
|
+
// definition changed since it was checked above
|
|
236
|
+
assertSafeTypeSize(col.length)
|
|
237
|
+
assertSafeTypeSize(col.precision)
|
|
238
|
+
assertSafeTypeSize(col.scale)
|
|
218
239
|
}
|
|
219
240
|
|
|
220
|
-
|
|
241
|
+
for (const row of Array.from(table.rows)) {
|
|
242
|
+
const item = {}
|
|
243
|
+
for (let index = 0; index < table.columns.length; index++) {
|
|
244
|
+
const col = table.columns[index]
|
|
245
|
+
// this driver hands the name to its table manager rather than writing
|
|
246
|
+
// it into SQL, but it is checked here all the same: a name either
|
|
247
|
+
// works on both drivers or on neither
|
|
248
|
+
item[assertSafeColumnName(col.name)] = row[index]
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
rows.push(item)
|
|
252
|
+
}
|
|
253
|
+
} catch (e) {
|
|
254
|
+
return done(e)
|
|
221
255
|
}
|
|
222
256
|
|
|
223
257
|
mgr.insertRows(rows, done)
|
|
@@ -232,7 +266,16 @@ class Request extends BaseRequest {
|
|
|
232
266
|
objectid = table.path
|
|
233
267
|
}
|
|
234
268
|
|
|
235
|
-
|
|
269
|
+
// A connection is held from here on, so a column name that changed since it
|
|
270
|
+
// was checked above must be reported through `done`, which releases it.
|
|
271
|
+
let statement
|
|
272
|
+
try {
|
|
273
|
+
statement = `if object_id('${objectid.replace(/'/g, '\'\'')}') is null ${table.declare()}`
|
|
274
|
+
} catch (e) {
|
|
275
|
+
return done(e)
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return connection.queryRaw(statement, function (err) {
|
|
236
279
|
if (err) { return done(err) }
|
|
237
280
|
go()
|
|
238
281
|
})
|
|
@@ -314,12 +357,18 @@ class Request extends BaseRequest {
|
|
|
314
357
|
|
|
315
358
|
if (!this._nested) {
|
|
316
359
|
const input = []
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
360
|
+
try {
|
|
361
|
+
for (const name in this.parameters) {
|
|
362
|
+
if (!objectHasProperty(this.parameters, name)) {
|
|
363
|
+
continue
|
|
364
|
+
}
|
|
365
|
+
const param = this.parameters[name]
|
|
366
|
+
// this callback is dispatched on its own tick, so a rejected declaration has
|
|
367
|
+
// to be handed back rather than thrown
|
|
368
|
+
input.push(`@${param.name} ${declare(param.type, param)}`)
|
|
320
369
|
}
|
|
321
|
-
|
|
322
|
-
|
|
370
|
+
} catch (e) {
|
|
371
|
+
return callback(new RequestError(e, 'EREQUEST'))
|
|
323
372
|
}
|
|
324
373
|
|
|
325
374
|
const sets = []
|
|
@@ -634,14 +683,32 @@ class Request extends BaseRequest {
|
|
|
634
683
|
if (err) return callback(err)
|
|
635
684
|
|
|
636
685
|
const params = []
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
686
|
+
let procedureName
|
|
687
|
+
try {
|
|
688
|
+
// this is built into `exec @___return___ = <name>` below and run as SQL text, so it is
|
|
689
|
+
// checked before it is emitted
|
|
690
|
+
procedureName = assertSafeProcedureName(procedure)
|
|
691
|
+
for (const name in this.parameters) {
|
|
692
|
+
if (!objectHasProperty(this.parameters, name)) {
|
|
693
|
+
continue
|
|
694
|
+
}
|
|
695
|
+
const param = this.parameters[name]
|
|
696
|
+
if (param.io === 2) {
|
|
697
|
+
// this callback is dispatched on its own tick, so a rejected declaration has
|
|
698
|
+
// to be handed back rather than thrown
|
|
699
|
+
params.push(`@${param.name} ${declare(param.type, param)}`)
|
|
700
|
+
} else {
|
|
701
|
+
// An input parameter is bound rather than written into the command, so only an
|
|
702
|
+
// output parameter's size is declared here and nothing would otherwise check an
|
|
703
|
+
// input's. It is checked so that a size is accepted or rejected the same way
|
|
704
|
+
// whichever driver is in use.
|
|
705
|
+
assertSafeTypeSize(param.length)
|
|
706
|
+
assertSafeTypeSize(param.precision)
|
|
707
|
+
assertSafeTypeSize(param.scale)
|
|
708
|
+
}
|
|
644
709
|
}
|
|
710
|
+
} catch (e) {
|
|
711
|
+
return callback(new RequestError(e, 'EREQUEST'))
|
|
645
712
|
}
|
|
646
713
|
|
|
647
714
|
// set output params w/ values
|
|
@@ -657,7 +724,7 @@ class Request extends BaseRequest {
|
|
|
657
724
|
}
|
|
658
725
|
|
|
659
726
|
let cmd = `declare ${['@___return___ int'].concat(params).join(', ')};${sets.join(';')};`
|
|
660
|
-
cmd += `exec @___return___ = ${
|
|
727
|
+
cmd += `exec @___return___ = ${procedureName} `
|
|
661
728
|
|
|
662
729
|
const spp = []
|
|
663
730
|
for (const name in this.parameters) {
|
package/lib/table.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const TYPES = require('./datatypes').TYPES
|
|
4
4
|
const declareType = require('./datatypes').declare
|
|
5
|
-
const objectHasProperty = require('./utils')
|
|
5
|
+
const { objectHasProperty, assertSafeColumnName } = require('./utils')
|
|
6
6
|
|
|
7
7
|
const MAX = 65535 // (1 << 16) - 1
|
|
8
8
|
const JSON_COLUMN_ID = 'JSON_F52E2B61-18A1-11d1-B105-00805F49916B'
|
|
@@ -99,9 +99,9 @@ Table.prototype._makeBulk = function _makeBulk () {
|
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
Table.prototype.declare = function declare () {
|
|
102
|
-
const pkey = this.columns.filter(col => col.primary === true).map(col => `[${col.name}]`)
|
|
102
|
+
const pkey = this.columns.filter(col => col.primary === true).map(col => `[${assertSafeColumnName(col.name)}]`)
|
|
103
103
|
const cols = this.columns.map(col => {
|
|
104
|
-
const def = [`[${col.name}] ${declareType(col.type, col)}`]
|
|
104
|
+
const def = [`[${assertSafeColumnName(col.name)}] ${declareType(col.type, col)}`]
|
|
105
105
|
|
|
106
106
|
if (col.nullable === true) {
|
|
107
107
|
def.push('null')
|