mssql 6.2.3 → 6.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/CHANGELOG.txt CHANGED
@@ -1,3 +1,22 @@
1
+ v6.4.0 (2021-11-18)
2
+ -------------------
3
+ [new] Transaction/PreparedStatements expose the config from their parent connection
4
+ [fix] Fix inherited request configs from the pool. Specifically stream and arrayRowMode now inherit accurately from the connection config ([#1338](https://github.com/tediousjs/node-mssql/pull/1338))
5
+
6
+ v6.3.2 (2021-05-13)
7
+ -------------------
8
+ [fix] Bump various dependencies for security fixes
9
+
10
+ v6.3.1 (2021-01-01)
11
+ -------------------
12
+ [fix] Ensure line endings are unix style (LF)
13
+
14
+ v6.3.0 (2020-12-14)
15
+ -------------------
16
+ [new] Result sets with duplicate keys can now be handled using `request.arrayRowMode` ([#1130](https://github.com/tediousjs/node-mssql/pull/1130))
17
+ [fix] Issue with geography v2 parsing resolve ([#1138](https://github.com/tediousjs/node-mssql/pull/1138))
18
+ [fix] Fixed issue where msnodesqlv8 could sometimes terminate queries before all errors has been emitted causing queries to hang ([#1145](https://github.com/tediousjs/node-mssql/pull/1145))
19
+
1
20
  v6.2.3 (2020-09-25)
2
21
  -------------------
3
22
  [fix] Remove reference to global pool on connection error ([#1107](https://github.com/tediousjs/node-mssql/pull/1107))
@@ -53,6 +72,14 @@ v6.0.0 (2019-11-05)
53
72
  [change] Replaced pool library (`generic-pool`) with `tarn.js` ([#808](https://github.com/tediousjs/node-mssql/pull/808))
54
73
  [removed] Backoff try strategy for creating connections removed, `tarn.js` built-in retry strategy used instead
55
74
 
75
+ v5.1.5 (2021-05-13)
76
+ -------------------
77
+ [fix] Bump various dependencies for security fixes
78
+
79
+ v5.1.4 (2021-01-01)
80
+ -------------------
81
+ [fix] Ensure line endings are unix style (LF)
82
+
56
83
  v5.1.3 (2020-09-18)
57
84
  -------------------
58
85
  [fix] Avoid using deprecated `.inspect` on Objects ([#1071](https://github.com/tediousjs/node-mssql/pull/1071))
package/LICENSE.md CHANGED
@@ -1,22 +1,22 @@
1
- MIT License
2
-
3
- Copyright (c) 2013-2018 Patrik Simek and contributors
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
22
-
1
+ MIT License
2
+
3
+ Copyright (c) 2013-2018 Patrik Simek and contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
package/README.md CHANGED
@@ -6,7 +6,7 @@ Microsoft SQL Server client for Node.js
6
6
 
7
7
  Supported TDS drivers:
8
8
  - [Tedious][tedious-url] (pure JavaScript - Windows/macOS/Linux, default)
9
- - [Microsoft / Contributors Node V8 Driver for Node.js for SQL Server][msnodesqlv8-url] (native - Windows/Linux only)
9
+ - [Microsoft / Contributors Node V8 Driver for Node.js for SQL Server][msnodesqlv8-url] (v1 native - Windows only)
10
10
 
11
11
  ## Installation
12
12
 
@@ -96,6 +96,7 @@ Parts of the connection URI should be correctly URL encoded so that the URI can
96
96
  * [Table-Valued Parameter](#table-valued-parameter-tvp)
97
97
  * [Affected Rows](#affected-rows)
98
98
  * [JSON support](#json-support)
99
+ * [Handling Duplicate Column Names](#handling-duplicate-column-names)
99
100
  * [Errors](#errors)
100
101
  * [Informational messages](#informational-messages)
101
102
  * [Metadata](#metadata)
@@ -611,6 +612,7 @@ const config = {
611
612
  - **pool.max** - The maximum number of connections there can be in the pool (default: `10`).
612
613
  - **pool.min** - The minimum of connections there can be in the pool (default: `0`).
613
614
  - **pool.idleTimeoutMillis** - The Number of milliseconds before closing an unused connection (default: `30000`).
615
+ - **arrayRowMode** - Return row results as a an array instead of a keyed object. Also adds `columns` array. See [Handling Duplicate Column Names](#handling-duplicate-column-names)
614
616
 
615
617
  Complete list of pool options can be found [here](https://github.com/vincit/tarn.js/#usage).
616
618
 
@@ -658,7 +660,7 @@ More information about Tedious specific options: http://tediousjs.github.io/tedi
658
660
 
659
661
  ### Microsoft / Contributors Node V8 Driver for Node.js for SQL Server
660
662
 
661
- **Requires Node.js v10+ or newer. Windows/Linux only.** This driver is not part of the default package and must be installed separately by `npm install msnodesqlv8`. To use this driver, use this require syntax: `const sql = require('mssql/msnodesqlv8')`.
663
+ **Requires Node.js v10+ or newer. Windows only.** This driver is not part of the default package and must be installed separately by `npm install msnodesqlv8@^1`. To use this driver, use this require syntax: `const sql = require('mssql/msnodesqlv8')`.
662
664
 
663
665
  **Extra options:**
664
666
 
@@ -1566,6 +1568,189 @@ recordset = [ { a: { b: { c: 1, d: 2 }, x: 3, y: 4 } } ]
1566
1568
 
1567
1569
  More information about JSON support can be found in [official documentation](https://msdn.microsoft.com/en-us/library/dn921882.aspx).
1568
1570
 
1571
+ ## Handling Duplicate Column Names
1572
+
1573
+ If your queries contain output columns with identical names, the default behaviour of `mssql` will only return column metadata for the last column with that name. You will also not always be able to re-assemble the order of output columns requested.
1574
+
1575
+ Default behaviour:
1576
+ ```javascript
1577
+ const request = new sql.Request()
1578
+ request
1579
+ .query("select 'asdf' as name, 'qwerty' as other_name, 'jkl' as name")
1580
+ .then(result => {
1581
+ console.log(result)
1582
+ });
1583
+ ```
1584
+ Results in:
1585
+ ```javascript
1586
+ {
1587
+ recordsets: [
1588
+ [ { name: [ 'asdf', 'jkl' ], other_name: 'qwerty' } ]
1589
+ ],
1590
+ recordset: [ { name: [ 'asdf', 'jkl' ], other_name: 'qwerty' } ],
1591
+ output: {},
1592
+ rowsAffected: [ 1 ]
1593
+ }
1594
+ ```
1595
+
1596
+ You can use the `arrayRowMode` configuration parameter to return the row values as arrays and add a separate array of column values. `arrayRowMode` can be set globally during the initial connection, or per-request.
1597
+
1598
+ ```javascript
1599
+ const request = new sql.Request()
1600
+ request.arrayRowMode = true
1601
+ request
1602
+ .query("select 'asdf' as name, 'qwerty' as other_name, 'jkl' as name")
1603
+ .then(result => {
1604
+ console.log(result)
1605
+ });
1606
+ ```
1607
+
1608
+ Results in:
1609
+ ```javascript
1610
+ {
1611
+ recordsets: [ [ [ 'asdf', 'qwerty', 'jkl' ] ] ],
1612
+ recordset: [ [ 'asdf', 'qwerty', 'jkl' ] ],
1613
+ output: {},
1614
+ rowsAffected: [ 1 ],
1615
+ columns: [
1616
+ [
1617
+ {
1618
+ index: 0,
1619
+ name: 'name',
1620
+ length: 4,
1621
+ type: [sql.VarChar],
1622
+ scale: undefined,
1623
+ precision: undefined,
1624
+ nullable: false,
1625
+ caseSensitive: false,
1626
+ identity: false,
1627
+ readOnly: true
1628
+ },
1629
+ {
1630
+ index: 1,
1631
+ name: 'other_name',
1632
+ length: 6,
1633
+ type: [sql.VarChar],
1634
+ scale: undefined,
1635
+ precision: undefined,
1636
+ nullable: false,
1637
+ caseSensitive: false,
1638
+ identity: false,
1639
+ readOnly: true
1640
+ },
1641
+ {
1642
+ index: 2,
1643
+ name: 'name',
1644
+ length: 3,
1645
+ type: [sql.VarChar],
1646
+ scale: undefined,
1647
+ precision: undefined,
1648
+ nullable: false,
1649
+ caseSensitive: false,
1650
+ identity: false,
1651
+ readOnly: true
1652
+ }
1653
+ ]
1654
+ ]
1655
+ }
1656
+ ```
1657
+
1658
+ __Streaming Duplicate Column Names__
1659
+
1660
+ When using `arrayRowMode` with `stream` enabled, the output from the `recordset` event (as described in [Streaming](#streaming)) is returned as an array of column metadata, instead of as a keyed object. The order of the column metadata provided by the `recordset` event will match the order of row values when `arrayRowMode` is enabled.
1661
+
1662
+ Default behaviour (without `arrayRowMode`):
1663
+ ```javascript
1664
+ const request = new sql.Request()
1665
+ request.stream = true
1666
+ request.query("select 'asdf' as name, 'qwerty' as other_name, 'jkl' as name")
1667
+ request.on('recordset', recordset => console.log(recordset))
1668
+ ```
1669
+
1670
+
1671
+ Results in:
1672
+
1673
+ ```javascript
1674
+ {
1675
+ name: {
1676
+ index: 2,
1677
+ name: 'name',
1678
+ length: 3,
1679
+ type: [sql.VarChar],
1680
+ scale: undefined,
1681
+ precision: undefined,
1682
+ nullable: false,
1683
+ caseSensitive: false,
1684
+ identity: false,
1685
+ readOnly: true
1686
+ },
1687
+ other_name: {
1688
+ index: 1,
1689
+ name: 'other_name',
1690
+ length: 6,
1691
+ type: [sql.VarChar],
1692
+ scale: undefined,
1693
+ precision: undefined,
1694
+ nullable: false,
1695
+ caseSensitive: false,
1696
+ identity: false,
1697
+ readOnly: true
1698
+ }
1699
+ }
1700
+ ```
1701
+
1702
+ With `arrayRowMode`:
1703
+ ```javascript
1704
+ const request = new sql.Request()
1705
+ request.stream = true
1706
+ request.arrayRowMode = true
1707
+ request.query("select 'asdf' as name, 'qwerty' as other_name, 'jkl' as name")
1708
+
1709
+ request.on('recordset', recordset => console.log(recordset))
1710
+ ```
1711
+
1712
+ Results in:
1713
+ ```javascript
1714
+ [
1715
+ {
1716
+ index: 0,
1717
+ name: 'name',
1718
+ length: 4,
1719
+ type: [sql.VarChar],
1720
+ scale: undefined,
1721
+ precision: undefined,
1722
+ nullable: false,
1723
+ caseSensitive: false,
1724
+ identity: false,
1725
+ readOnly: true
1726
+ },
1727
+ {
1728
+ index: 1,
1729
+ name: 'other_name',
1730
+ length: 6,
1731
+ type: [sql.VarChar],
1732
+ scale: undefined,
1733
+ precision: undefined,
1734
+ nullable: false,
1735
+ caseSensitive: false,
1736
+ identity: false,
1737
+ readOnly: true
1738
+ },
1739
+ {
1740
+ index: 2,
1741
+ name: 'name',
1742
+ length: 3,
1743
+ type: [sql.VarChar],
1744
+ scale: undefined,
1745
+ precision: undefined,
1746
+ nullable: false,
1747
+ caseSensitive: false,
1748
+ identity: false,
1749
+ readOnly: true
1750
+ }
1751
+ ]
1752
+ ```
1753
+
1569
1754
  ## Errors
1570
1755
 
1571
1756
  There are 4 types of errors you can handle:
package/bin/mssql CHANGED
File without changes
@@ -59,6 +59,7 @@ class ConnectionPool extends EventEmitter {
59
59
  this.config.options = this.config.options || {}
60
60
  this.config.stream = this.config.stream || false
61
61
  this.config.parseJSON = this.config.parseJSON || false
62
+ this.config.arrayRowMode = this.config.arrayRowMode || false
62
63
 
63
64
  if (/^(.*)\\(.*)$/.exec(this.config.server)) {
64
65
  this.config.server = RegExp.$1
@@ -35,6 +35,10 @@ class PreparedStatement extends EventEmitter {
35
35
  this.parameters = {}
36
36
  }
37
37
 
38
+ get config () {
39
+ return this.parent.config
40
+ }
41
+
38
42
  get connected () {
39
43
  return this.parent.connected
40
44
  }
@@ -292,6 +296,7 @@ class PreparedStatement extends EventEmitter {
292
296
  _execute (values, callback) {
293
297
  const req = new shared.driver.Request(this)
294
298
  req.stream = this.stream
299
+ req.arrayRowMode = this.arrayRowMode
295
300
  req.input('handle', TYPES.Int, this._handle)
296
301
 
297
302
  // copy parameters with new values
@@ -25,7 +25,7 @@ class Request extends EventEmitter {
25
25
  /**
26
26
  * Create new Request.
27
27
  *
28
- * @param {Connection|ConnectionPool|Transaction|PreparedStatement} parent If ommited, global connection is used instead.
28
+ * @param {Connection|ConnectionPool|Transaction|PreparedStatement} parent If omitted, global connection is used instead.
29
29
  */
30
30
 
31
31
  constructor (parent) {
@@ -38,6 +38,8 @@ class Request extends EventEmitter {
38
38
  this._paused = false
39
39
  this.parent = parent || globalConnection.pool
40
40
  this.parameters = {}
41
+ this.stream = null
42
+ this.arrayRowMode = null
41
43
  }
42
44
 
43
45
  get paused () {
@@ -223,7 +225,8 @@ class Request extends EventEmitter {
223
225
  */
224
226
 
225
227
  batch (batch, callback) {
226
- if (this.stream == null && this.connection) this.stream = this.connection.config.stream
228
+ if (this.stream === null && this.parent) this.stream = this.parent.config.stream
229
+ if (this.arrayRowMode === null && this.parent) this.arrayRowMode = this.parent.config.arrayRowMode
227
230
  this.rowsAffected = 0
228
231
 
229
232
  if (typeof callback === 'function') {
@@ -286,11 +289,11 @@ class Request extends EventEmitter {
286
289
  */
287
290
 
288
291
  _batch (batch, callback) {
289
- if (!this.connection) {
292
+ if (!this.parent) {
290
293
  return setImmediate(callback, new RequestError('No connection is specified for that request.', 'ENOCONN'))
291
294
  }
292
295
 
293
- if (!this.connection.connected) {
296
+ if (!this.parent.connected) {
294
297
  return setImmediate(callback, new ConnectionError('Connection is closed.', 'ECONNCLOSED'))
295
298
  }
296
299
 
@@ -315,7 +318,8 @@ class Request extends EventEmitter {
315
318
  options = {}
316
319
  }
317
320
 
318
- if (this.stream == null && this.connection) this.stream = this.connection.config.stream
321
+ if (this.stream === null && this.parent) this.stream = this.parent.config.stream
322
+ if (this.arrayRowMode === null && this.parent) this.arrayRowMode = this.parent.config.arrayRowMode
319
323
 
320
324
  if (this.stream || typeof callback === 'function') {
321
325
  this._bulk(table, options, (err, rowsAffected) => {
@@ -391,11 +395,12 @@ class Request extends EventEmitter {
391
395
  */
392
396
 
393
397
  query (command, callback) {
394
- if (this.stream == null && this.connection) this.stream = this.connection.config.stream
398
+ if (this.stream === null && this.parent) this.stream = this.parent.config.stream
399
+ if (this.arrayRowMode === null && this.parent) this.arrayRowMode = this.parent.config.arrayRowMode
395
400
  this.rowsAffected = 0
396
401
 
397
402
  if (typeof callback === 'function') {
398
- this._query(command, (err, recordsets, output, rowsAffected) => {
403
+ this._query(command, (err, recordsets, output, rowsAffected, columns) => {
399
404
  if (this.stream) {
400
405
  if (err) this.emit('error', err)
401
406
  err = null
@@ -407,12 +412,14 @@ class Request extends EventEmitter {
407
412
  }
408
413
 
409
414
  if (err) return callback(err)
410
- callback(null, {
415
+ const result = {
411
416
  recordsets,
412
417
  recordset: recordsets && recordsets[0],
413
418
  output,
414
419
  rowsAffected
415
- })
420
+ }
421
+ if (this.arrayRowMode) result.columns = columns
422
+ callback(null, result)
416
423
  })
417
424
  return this
418
425
  }
@@ -425,7 +432,7 @@ class Request extends EventEmitter {
425
432
  }
426
433
 
427
434
  return new shared.Promise((resolve, reject) => {
428
- this._query(command, (err, recordsets, output, rowsAffected) => {
435
+ this._query(command, (err, recordsets, output, rowsAffected, columns) => {
429
436
  if (this.stream) {
430
437
  if (err) this.emit('error', err)
431
438
  err = null
@@ -437,12 +444,14 @@ class Request extends EventEmitter {
437
444
  }
438
445
 
439
446
  if (err) return reject(err)
440
- resolve({
447
+ const result = {
441
448
  recordsets,
442
449
  recordset: recordsets && recordsets[0],
443
450
  output,
444
451
  rowsAffected
445
- })
452
+ }
453
+ if (this.arrayRowMode) result.columns = columns
454
+ resolve(result)
446
455
  })
447
456
  })
448
457
  }
@@ -475,11 +484,12 @@ class Request extends EventEmitter {
475
484
  */
476
485
 
477
486
  execute (command, callback) {
478
- if (this.stream == null && this.connection) this.stream = this.connection.config.stream
487
+ if (this.stream === null && this.parent) this.stream = this.parent.config.stream
488
+ if (this.arrayRowMode === null && this.parent) this.arrayRowMode = this.parent.config.arrayRowMode
479
489
  this.rowsAffected = 0
480
490
 
481
491
  if (typeof callback === 'function') {
482
- this._execute(command, (err, recordsets, output, returnValue, rowsAffected) => {
492
+ this._execute(command, (err, recordsets, output, returnValue, rowsAffected, columns) => {
483
493
  if (this.stream) {
484
494
  if (err) this.emit('error', err)
485
495
  err = null
@@ -492,19 +502,21 @@ class Request extends EventEmitter {
492
502
  }
493
503
 
494
504
  if (err) return callback(err)
495
- callback(null, {
505
+ const result = {
496
506
  recordsets,
497
507
  recordset: recordsets && recordsets[0],
498
508
  output,
499
509
  rowsAffected,
500
510
  returnValue
501
- })
511
+ }
512
+ if (this.arrayRowMode) result.columns = columns
513
+ callback(null, result)
502
514
  })
503
515
  return this
504
516
  }
505
517
 
506
518
  return new shared.Promise((resolve, reject) => {
507
- this._execute(command, (err, recordsets, output, returnValue, rowsAffected) => {
519
+ this._execute(command, (err, recordsets, output, returnValue, rowsAffected, columns) => {
508
520
  if (this.stream) {
509
521
  if (err) this.emit('error', err)
510
522
  err = null
@@ -517,13 +529,15 @@ class Request extends EventEmitter {
517
529
  }
518
530
 
519
531
  if (err) return reject(err)
520
- resolve({
532
+ const result = {
521
533
  recordsets,
522
534
  recordset: recordsets && recordsets[0],
523
535
  output,
524
536
  rowsAffected,
525
537
  returnValue
526
- })
538
+ }
539
+ if (this.arrayRowMode) result.columns = columns
540
+ resolve(result)
527
541
  })
528
542
  })
529
543
  }
@@ -37,6 +37,10 @@ class Transaction extends EventEmitter {
37
37
  this.name = ''
38
38
  }
39
39
 
40
+ get config () {
41
+ return this.parent.config
42
+ }
43
+
40
44
  get connected () {
41
45
  return this.parent.connected
42
46
  }
package/lib/cli.js CHANGED
@@ -1,113 +1,113 @@
1
- 'use strict'
2
-
3
- const fs = require('fs')
4
- const path = require('path')
5
- const sql = require('./tedious')
6
-
7
- const exit = process.exit
8
- const write = text => process.stdout.write(text)
9
- Buffer.prototype.toJSON = () => {
10
- return `0x${this.toString('hex')}`
11
- }
12
-
13
- // Resolve config path
14
-
15
- let cfgPath = path.resolve(process.argv[2] || process.cwd())
16
- if (fs.lstatSync(cfgPath).isDirectory()) {
17
- cfgPath = path.resolve(cfgPath, './.mssql.json')
18
- }
19
- if (!fs.existsSync(cfgPath)) {
20
- console.error('Config file not found.')
21
- exit(1)
22
- }
23
-
24
- // Config checks & parse
25
-
26
- let config
27
- try {
28
- config = fs.readFileSync(cfgPath)
29
- } catch (err) {
30
- console.error(`Failed to load config file. ${err.message}`)
31
- exit(1)
32
- }
33
-
34
- try {
35
- config = JSON.parse(config)
36
- } catch (err) {
37
- console.error(`Failed to parse config file. ${err.message}`)
38
- exit(1)
39
- }
40
-
41
- // Read stdin
42
-
43
- const buffer = []
44
-
45
- process.stdin.setEncoding('utf8')
46
- process.stdin.on('readable', () => buffer.push(process.stdin.read()))
47
-
48
- process.stdin.on('end', () => {
49
- const statement = buffer.join('')
50
- let rst = 0
51
- let index = 0
52
-
53
- if (!statement.length) {
54
- console.error('Statement is empty.')
55
- exit(1)
56
- }
57
-
58
- sql.connect(config, err => {
59
- if (err) {
60
- console.error(err.message)
61
- exit(1)
62
- }
63
-
64
- write('[')
65
-
66
- const request = new sql.Request()
67
- request.stream = true
68
- request.on('recordset', () => {
69
- index = 0
70
- if (rst++ > 0) {
71
- write('],')
72
- }
73
-
74
- return write('[')
75
- })
76
-
77
- request.on('error', err => {
78
- console.error(err.message)
79
- sql.close()
80
- return exit(1)
81
- })
82
-
83
- request.on('row', row => {
84
- if (index++ > 0) {
85
- write(',')
86
- }
87
-
88
- return write(JSON.stringify(row))
89
- })
90
-
91
- request.on('done', () => {
92
- if (rst > 0) {
93
- write(']')
94
- }
95
-
96
- write(']\n')
97
- sql.close()
98
- return exit(0)
99
- })
100
-
101
- request.query(statement)
102
- })
103
- })
104
-
105
- process.on('uncaughtException', err => {
106
- if (err.code === 'EPIPE') {
107
- console.error('Failed to pipe output stream.')
108
- } else {
109
- console.error(err.message)
110
- }
111
-
112
- exit(1)
113
- })
1
+ 'use strict'
2
+
3
+ const fs = require('fs')
4
+ const path = require('path')
5
+ const sql = require('./tedious')
6
+
7
+ const exit = process.exit
8
+ const write = text => process.stdout.write(text)
9
+ Buffer.prototype.toJSON = () => {
10
+ return `0x${this.toString('hex')}`
11
+ }
12
+
13
+ // Resolve config path
14
+
15
+ let cfgPath = path.resolve(process.argv[2] || process.cwd())
16
+ if (fs.lstatSync(cfgPath).isDirectory()) {
17
+ cfgPath = path.resolve(cfgPath, './.mssql.json')
18
+ }
19
+ if (!fs.existsSync(cfgPath)) {
20
+ console.error('Config file not found.')
21
+ exit(1)
22
+ }
23
+
24
+ // Config checks & parse
25
+
26
+ let config
27
+ try {
28
+ config = fs.readFileSync(cfgPath)
29
+ } catch (err) {
30
+ console.error(`Failed to load config file. ${err.message}`)
31
+ exit(1)
32
+ }
33
+
34
+ try {
35
+ config = JSON.parse(config)
36
+ } catch (err) {
37
+ console.error(`Failed to parse config file. ${err.message}`)
38
+ exit(1)
39
+ }
40
+
41
+ // Read stdin
42
+
43
+ const buffer = []
44
+
45
+ process.stdin.setEncoding('utf8')
46
+ process.stdin.on('readable', () => buffer.push(process.stdin.read()))
47
+
48
+ process.stdin.on('end', () => {
49
+ const statement = buffer.join('')
50
+ let rst = 0
51
+ let index = 0
52
+
53
+ if (!statement.length) {
54
+ console.error('Statement is empty.')
55
+ exit(1)
56
+ }
57
+
58
+ sql.connect(config, err => {
59
+ if (err) {
60
+ console.error(err.message)
61
+ exit(1)
62
+ }
63
+
64
+ write('[')
65
+
66
+ const request = new sql.Request()
67
+ request.stream = true
68
+ request.on('recordset', () => {
69
+ index = 0
70
+ if (rst++ > 0) {
71
+ write('],')
72
+ }
73
+
74
+ return write('[')
75
+ })
76
+
77
+ request.on('error', err => {
78
+ console.error(err.message)
79
+ sql.close()
80
+ return exit(1)
81
+ })
82
+
83
+ request.on('row', row => {
84
+ if (index++ > 0) {
85
+ write(',')
86
+ }
87
+
88
+ return write(JSON.stringify(row))
89
+ })
90
+
91
+ request.on('done', () => {
92
+ if (rst > 0) {
93
+ write(']')
94
+ }
95
+
96
+ write(']\n')
97
+ sql.close()
98
+ return exit(0)
99
+ })
100
+
101
+ request.query(statement)
102
+ })
103
+ })
104
+
105
+ process.on('uncaughtException', err => {
106
+ if (err.code === 'EPIPE') {
107
+ console.error('Failed to pipe output stream.')
108
+ } else {
109
+ console.error(err.message)
110
+ }
111
+
112
+ exit(1)
113
+ })
@@ -1,9 +1,9 @@
1
- 'use strict'
2
-
3
- module.exports = {
4
- READ_UNCOMMITTED: 0x01,
5
- READ_COMMITTED: 0x02,
6
- REPEATABLE_READ: 0x03,
7
- SERIALIZABLE: 0x04,
8
- SNAPSHOT: 0x05
9
- }
1
+ 'use strict'
2
+
3
+ module.exports = {
4
+ READ_UNCOMMITTED: 0x01,
5
+ READ_COMMITTED: 0x02,
6
+ REPEATABLE_READ: 0x03,
7
+ SERIALIZABLE: 0x04,
8
+ SNAPSHOT: 0x05
9
+ }
@@ -88,11 +88,13 @@ const castParameter = function (value, type) {
88
88
  return value
89
89
  }
90
90
 
91
- const createColumns = function (metadata) {
92
- const out = {}
91
+ const createColumns = function (metadata, arrayRowMode) {
92
+ let out = {}
93
+ if (arrayRowMode) out = []
93
94
  for (let index = 0, length = metadata.length; index < length; index++) {
94
95
  const column = metadata[index]
95
- out[column.name] = {
96
+ const colName = column.name
97
+ const outColumn = {
96
98
  index,
97
99
  name: column.name,
98
100
  length: column.size,
@@ -101,14 +103,19 @@ const createColumns = function (metadata) {
101
103
  }
102
104
 
103
105
  if (column.udtType != null) {
104
- out[column.name].udt = {
106
+ outColumn.udt = {
105
107
  name: column.udtType
106
108
  }
107
109
 
108
110
  if (DECLARATIONS[column.udtType]) {
109
- out[column.name].type = DECLARATIONS[column.udtType]
111
+ outColumn.type = DECLARATIONS[column.udtType]
110
112
  }
111
113
  }
114
+ if (arrayRowMode) {
115
+ out.push(outColumn)
116
+ } else {
117
+ out[colName] = outColumn
118
+ }
112
119
  }
113
120
 
114
121
  return out
@@ -236,6 +243,7 @@ class Request extends BaseRequest {
236
243
  }
237
244
 
238
245
  const recordsets = []
246
+ const recordsetcolumns = []
239
247
  const errors = []
240
248
  const errorHandlers = {}
241
249
  const output = {}
@@ -410,7 +418,7 @@ class Request extends BaseRequest {
410
418
  Object.defineProperty(recordset, 'columns', {
411
419
  enumerable: false,
412
420
  configurable: true,
413
- value: createColumns(metadata)
421
+ value: createColumns(metadata, this.arrayRowMode)
414
422
  })
415
423
 
416
424
  Object.defineProperty(recordset, 'toTable', {
@@ -425,19 +433,35 @@ class Request extends BaseRequest {
425
433
  chunksBuffer = []
426
434
  }
427
435
 
436
+ let hasReturnColumn = false
437
+ if (recordset.columns.___return___ != null) {
438
+ hasReturnColumn = true
439
+ } else if (this.arrayRowMode) {
440
+ for (let i = 0; i < columns.length; i++) {
441
+ if (columns[i].name === '___return___') {
442
+ hasReturnColumn = true
443
+ break
444
+ }
445
+ }
446
+ }
428
447
  if (this.stream) {
429
- if (recordset.columns.___return___ == null) {
448
+ if (!hasReturnColumn) {
430
449
  this.emit('recordset', recordset.columns)
431
450
  }
432
451
  } else {
433
452
  recordsets.push(recordset)
434
453
  }
454
+ if (this.arrayRowMode) recordsetcolumns.push(recordset.columns)
435
455
  })
436
456
 
437
457
  req.on('row', rownumber => {
438
458
  if (row && isChunkedRecordset) return
439
459
 
440
- row = {}
460
+ if (this.arrayRowMode) {
461
+ row = []
462
+ } else {
463
+ row = {}
464
+ }
441
465
 
442
466
  if (!this.stream) recordset.push(row)
443
467
  })
@@ -448,17 +472,32 @@ class Request extends BaseRequest {
448
472
  } else {
449
473
  data = valueCorrection(data, columns[idx])
450
474
 
451
- const exi = row[columns[idx].name]
452
- if (exi != null) {
453
- if (exi instanceof Array) {
454
- exi.push(data)
475
+ if (this.arrayRowMode) {
476
+ row.push(data)
477
+ } else {
478
+ const exi = row[columns[idx].name]
479
+ if (exi != null) {
480
+ if (exi instanceof Array) {
481
+ exi.push(data)
482
+ } else {
483
+ row[columns[idx].name] = [exi, data]
484
+ }
455
485
  } else {
456
- row[columns[idx].name] = [exi, data]
486
+ row[columns[idx].name] = data
487
+ }
488
+ }
489
+ let hasReturnColumn = false
490
+ if (row && row.___return___ != null) {
491
+ hasReturnColumn = true
492
+ } else if (this.arrayRowMode) {
493
+ for (let i = 0; i < columns.length; i++) {
494
+ if (columns[i].name === '___return___') {
495
+ hasReturnColumn = true
496
+ break
497
+ }
457
498
  }
458
- } else {
459
- row[columns[idx].name] = data
460
499
  }
461
- if (row.___return___ == null) {
500
+ if (!hasReturnColumn) {
462
501
  if (this.stream && !this.paused && idx === columns.length - 1) {
463
502
  this.emit('row', row)
464
503
  }
@@ -484,6 +523,14 @@ class Request extends BaseRequest {
484
523
  serverName: msg.serverName,
485
524
  procName: msg.procName
486
525
  })
526
+
527
+ // query terminated
528
+ if (msg.code === 3621 && !hasReturned) {
529
+ // if the query has been terminated it's probably best to throw the last meaningful error if there was one
530
+ // pop it off the errors array so it doesn't get put in twice
531
+ const error = errors.length > 0 ? errors.pop() : msg
532
+ handleError(req, connection, error.originalError || error, false)
533
+ }
487
534
  })
488
535
 
489
536
  req.on('error', errorHandlers.error = handleError.bind(null, req, connection))
@@ -551,9 +598,9 @@ class Request extends BaseRequest {
551
598
  debug('request(%d): completed', IDS.get(this))
552
599
 
553
600
  if (this.stream) {
554
- callback(null, this._nested ? row : null, output, rowsAffected)
601
+ callback(null, this._nested ? row : null, output, rowsAffected, recordsetcolumns)
555
602
  } else {
556
- callback(null, recordsets, output, rowsAffected)
603
+ callback(null, recordsets, output, rowsAffected, recordsetcolumns)
557
604
  }
558
605
  })
559
606
  })
@@ -610,7 +657,7 @@ class Request extends BaseRequest {
610
657
 
611
658
  this._nested = true
612
659
 
613
- this._query(cmd, (err, recordsets, output, rowsAffected) => {
660
+ this._query(cmd, (err, recordsets, output, rowsAffected, recordsetcolumns) => {
614
661
  this._nested = false
615
662
 
616
663
  if (err) return callback(err)
@@ -622,25 +669,49 @@ class Request extends BaseRequest {
622
669
  last = recordsets.pop()
623
670
  if (last) last = last[0]
624
671
  }
625
-
626
- if (last && (last.___return___ != null)) {
627
- returnValue = last.___return___
628
-
629
- for (const name in this.parameters) {
630
- if (!objectHasProperty(this.parameters, name)) {
672
+ const lastColumns = recordsetcolumns.pop()
673
+
674
+ if (last && this.arrayRowMode && lastColumns) {
675
+ let returnColumnIdx = null
676
+ const parametersNameToLastIdxDict = {}
677
+ for (let i = 0; i < lastColumns.length; i++) {
678
+ if (lastColumns[i].name === '___return___') {
679
+ returnColumnIdx = i
680
+ } else if (objectHasProperty(this.parameters, lastColumns[i].name)) {
681
+ parametersNameToLastIdxDict[lastColumns[i].name] = i
682
+ }
683
+ }
684
+ if (returnColumnIdx != null) {
685
+ returnValue = last[returnColumnIdx]
686
+ }
687
+ for (const name in parametersNameToLastIdxDict) {
688
+ if (!objectHasProperty(parametersNameToLastIdxDict, name)) {
631
689
  continue
632
690
  }
633
691
  const param = this.parameters[name]
634
692
  if (param.io === 2) {
635
- output[param.name] = last[param.name]
693
+ output[param.name] = last[parametersNameToLastIdxDict[name]]
636
694
  }
637
695
  }
638
- }
696
+ } else {
697
+ if (last && (last.___return___ != null)) {
698
+ returnValue = last.___return___
639
699
 
700
+ for (const name in this.parameters) {
701
+ if (!objectHasProperty(this.parameters, name)) {
702
+ continue
703
+ }
704
+ const param = this.parameters[name]
705
+ if (param.io === 2) {
706
+ output[param.name] = last[param.name]
707
+ }
708
+ }
709
+ }
710
+ }
640
711
  if (this.stream) {
641
- callback(null, null, output, returnValue, rowsAffected)
712
+ callback(null, null, output, returnValue, rowsAffected, recordsetcolumns)
642
713
  } else {
643
- callback(null, recordsets, output, returnValue, rowsAffected)
714
+ callback(null, recordsets, output, returnValue, rowsAffected, recordsetcolumns)
644
715
  }
645
716
  })
646
717
  })
@@ -117,11 +117,12 @@ const getMssqlType = function (type, length) {
117
117
  }
118
118
  }
119
119
 
120
- const createColumns = function (metadata) {
121
- const out = {}
120
+ const createColumns = function (metadata, arrayRowMode) {
121
+ let out = {}
122
+ if (arrayRowMode) out = []
122
123
  for (let index = 0, length = metadata.length; index < length; index++) {
123
124
  const column = metadata[index]
124
- out[column.colName] = {
125
+ const outColumn = {
125
126
  index,
126
127
  name: column.colName,
127
128
  length: column.dataLength,
@@ -135,7 +136,7 @@ const createColumns = function (metadata) {
135
136
  }
136
137
 
137
138
  if (column.udtInfo) {
138
- out[column.colName].udt = {
139
+ outColumn.udt = {
139
140
  name: column.udtInfo.typeName,
140
141
  database: column.udtInfo.dbname,
141
142
  schema: column.udtInfo.owningSchema,
@@ -143,9 +144,15 @@ const createColumns = function (metadata) {
143
144
  }
144
145
 
145
146
  if (DECLARATIONS[column.udtInfo.typeName]) {
146
- out[column.colName].type = DECLARATIONS[column.udtInfo.typeName]
147
+ outColumn.type = DECLARATIONS[column.udtInfo.typeName]
147
148
  }
148
149
  }
150
+
151
+ if (arrayRowMode) {
152
+ out.push(outColumn)
153
+ } else {
154
+ out[column.colName] = outColumn
155
+ }
149
156
  }
150
157
 
151
158
  return out
@@ -347,6 +354,7 @@ class Request extends BaseRequest {
347
354
  if (err) return callback(err)
348
355
 
349
356
  const recordsets = []
357
+ const recordsetcolumns = []
350
358
  const errors = []
351
359
  const errorHandlers = {}
352
360
  const output = {}
@@ -466,9 +474,9 @@ class Request extends BaseRequest {
466
474
  }
467
475
 
468
476
  if (this.stream) {
469
- callback(null, null, output, rowsAffected)
477
+ callback(null, null, output, rowsAffected, recordsetcolumns)
470
478
  } else {
471
- callback(error, recordsets, output, rowsAffected)
479
+ callback(error, recordsets, output, rowsAffected, recordsetcolumns)
472
480
  }
473
481
  }
474
482
  })
@@ -476,7 +484,7 @@ class Request extends BaseRequest {
476
484
  this._setCurrentRequest(req)
477
485
 
478
486
  req.on('columnMetadata', metadata => {
479
- columns = createColumns(metadata)
487
+ columns = createColumns(metadata, this.arrayRowMode)
480
488
 
481
489
  isChunkedRecordset = false
482
490
  if (metadata.length === 1 && (metadata[0].colName === JSON_COLUMN_ID || metadata[0].colName === XML_COLUMN_ID)) {
@@ -494,6 +502,7 @@ class Request extends BaseRequest {
494
502
  this.emit('recordset', columns)
495
503
  }
496
504
  }
505
+ if (this.arrayRowMode) recordsetcolumns.push(columns)
497
506
  })
498
507
 
499
508
  const doneHandler = (rowCount, more) => {
@@ -568,19 +577,27 @@ class Request extends BaseRequest {
568
577
  return chunksBuffer.push(columns[0].value)
569
578
  }
570
579
 
571
- row = {}
580
+ if (this.arrayRowMode) {
581
+ row = []
582
+ } else {
583
+ row = {}
584
+ }
572
585
  for (const col of columns) {
573
586
  col.value = valueCorrection(col.value, col.metadata)
574
587
 
575
- const exi = row[col.metadata.colName]
576
- if (exi != null) {
577
- if (exi instanceof Array) {
578
- exi.push(col.value)
588
+ if (this.arrayRowMode) {
589
+ row.push(col.value)
590
+ } else {
591
+ const exi = row[col.metadata.colName]
592
+ if (exi != null) {
593
+ if (exi instanceof Array) {
594
+ exi.push(col.value)
595
+ } else {
596
+ row[col.metadata.colName] = [exi, col.value]
597
+ }
579
598
  } else {
580
- row[col.metadata.colName] = [exi, col.value]
599
+ row[col.metadata.colName] = col.value
581
600
  }
582
- } else {
583
- row[col.metadata.colName] = col.value
584
601
  }
585
602
  }
586
603
 
@@ -684,6 +701,7 @@ class Request extends BaseRequest {
684
701
  if (err) return callback(err)
685
702
 
686
703
  const recordsets = []
704
+ const recordsetcolumns = []
687
705
  const errors = []
688
706
  const errorHandlers = {}
689
707
  const output = {}
@@ -800,9 +818,9 @@ class Request extends BaseRequest {
800
818
  }
801
819
 
802
820
  if (this.stream) {
803
- callback(null, null, output, returnValue, rowsAffected)
821
+ callback(null, null, output, returnValue, rowsAffected, recordsetcolumns)
804
822
  } else {
805
- callback(error, recordsets, output, returnValue, rowsAffected)
823
+ callback(error, recordsets, output, returnValue, rowsAffected, recordsetcolumns)
806
824
  }
807
825
  }
808
826
  })
@@ -810,7 +828,7 @@ class Request extends BaseRequest {
810
828
  this._setCurrentRequest(req)
811
829
 
812
830
  req.on('columnMetadata', metadata => {
813
- columns = createColumns(metadata)
831
+ columns = createColumns(metadata, this.arrayRowMode)
814
832
 
815
833
  isChunkedRecordset = false
816
834
  if ((metadata.length === 1) && (metadata[0].colName === JSON_COLUMN_ID || metadata[0].colName === XML_COLUMN_ID)) {
@@ -819,6 +837,7 @@ class Request extends BaseRequest {
819
837
  }
820
838
 
821
839
  if (this.stream) this.emit('recordset', columns)
840
+ if (this.arrayRowMode) recordsetcolumns.push(columns)
822
841
  })
823
842
 
824
843
  req.on('row', columns => {
@@ -828,19 +847,27 @@ class Request extends BaseRequest {
828
847
  return chunksBuffer.push(columns[0].value)
829
848
  }
830
849
 
831
- row = {}
850
+ if (this.arrayRowMode) {
851
+ row = []
852
+ } else {
853
+ row = {}
854
+ }
832
855
  for (const col of columns) {
833
856
  col.value = valueCorrection(col.value, col.metadata)
834
857
 
835
- const exi = row[col.metadata.colName]
836
- if (exi != null) {
837
- if (exi instanceof Array) {
838
- exi.push(col.value)
858
+ if (this.arrayRowMode) {
859
+ row.push(col.value)
860
+ } else {
861
+ const exi = row[col.metadata.colName]
862
+ if (exi != null) {
863
+ if (exi instanceof Array) {
864
+ exi.push(col.value)
865
+ } else {
866
+ row[col.metadata.colName] = [exi, col.value]
867
+ }
839
868
  } else {
840
- row[col.metadata.colName] = [exi, col.value]
869
+ row[col.metadata.colName] = col.value
841
870
  }
842
- } else {
843
- row[col.metadata.colName] = col.value
844
871
  }
845
872
  }
846
873
 
package/lib/udt.js CHANGED
@@ -271,7 +271,7 @@ const parseGeography = buffer => {
271
271
 
272
272
  // console.log( "shapes", shapes)
273
273
 
274
- if (value.version === 2) {
274
+ if (value.version === 2 && buffer.position < buffer.length) {
275
275
  const numberOfSegments = buffer.readUInt32LE(buffer.position)
276
276
  buffer.position += 4
277
277
 
package/package.json CHANGED
@@ -21,18 +21,18 @@
21
21
  "azure",
22
22
  "node-mssql"
23
23
  ],
24
- "version": "6.2.3",
24
+ "version": "6.4.0",
25
25
  "main": "index.js",
26
26
  "repository": "github:tediousjs/node-mssql",
27
27
  "license": "MIT",
28
28
  "dependencies": {
29
- "debug": "^4",
29
+ "debug": "^4.3.2",
30
30
  "tarn": "^1.1.5",
31
- "tedious": "^6.6.2"
31
+ "tedious": "^6.7.1"
32
32
  },
33
33
  "devDependencies": {
34
- "mocha": "^6.2.2",
35
- "standard": "^14"
34
+ "mocha": "^6.2.3",
35
+ "standard": "^14.3.4"
36
36
  },
37
37
  "engines": {
38
38
  "node": ">=6"
package/test.js CHANGED
@@ -1,40 +1,40 @@
1
- 'use strict'
2
-
3
- const mssql = require('./')
4
- const wtf = require('wtfnode');
5
-
6
- const sqlConfig = {
7
- password: '&r8wrucH',
8
- database: 'StockDB',
9
- stream: false,
10
- options: {
11
- // trustServerCertificate: true,
12
- enableArithAbort: true,
13
- encrypt: true,
14
- abortTransactionOnError: false,
15
- },
16
- pool: {
17
- max: 1,
18
- min: 0,
19
- },
20
- port: 1433,
21
- user: 'dhensby',
22
- server: '4sa.database.windows.net',
23
- }
24
-
25
- // Bad URI, connection timeouts, hangs process
26
- mssql.connect({ ...sqlConfig, port: 2345 }).catch(err => {
27
- console.error(err.message);
28
- console.log('reconnecting...');
29
-
30
- // Try to connect using the right URI
31
- return mssql
32
- .connect(sqlConfig)
33
- .then(() => console.log('connected'))
34
- .then(() => mssql.close())
35
- .then(() => console.log('closed'))
36
- .catch(err => {
37
- console.error(err.message)
38
- wtf.dump()
39
- }); // <-- Fails here
40
- });
1
+ 'use strict'
2
+ const mssql = require('./')
3
+
4
+ const [, , max] = process.argv
5
+
6
+ const sqlConfig = {
7
+ password: 'P@ssw0rd',
8
+ database: 'test',
9
+ stream: false,
10
+ // driver: 'msnodesqlv8',
11
+ options: {
12
+ trustServerCertificate: true,
13
+ enableArithAbort: true,
14
+ encrypt: true,
15
+ abortTransactionOnError: false
16
+ },
17
+ pool: {
18
+ max: max ? parseInt(max) : 10,
19
+ min: 0
20
+ },
21
+ port: 1433,
22
+ user: 'sa',
23
+ server: 'localhost',
24
+ arrayRowMode: true
25
+ }
26
+
27
+ mssql.connect(sqlConfig).then((pool) => {
28
+ console.log('Connected to SQL server')
29
+ const request = pool.request()
30
+ request.stream = true
31
+ request.on('error', (err) => {
32
+ console.error('good', err)
33
+ })
34
+ return request.query('SELECT * FROM nontable')
35
+ }).catch((err) => {
36
+ console.error('bad', err)
37
+ }).then(() => {
38
+ console.log('Disconnecting from SQL server')
39
+ return mssql.close()
40
+ }).then(() => console.log('Disconnected'))