mssql 7.3.0 → 8.0.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,7 +1,14 @@
1
- v7.3.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))
1
+ v8.0.0 (2022-01-30)
2
+ -------------------
3
+ [new] Add table.rows.clear() method to allow for chunking updates ([#1094](https://github.com/tediousjs/node-mssql/pull/1094))
4
+ [new] Add valueHandler map to store callbacks that are used to process row values ([#1356](https://github.com/tediousjs/node-mssql/pull/1356))
5
+ [change] msnodesqlv8 driver detects os platform and attempts to pick correct connections string for it ((#1318)[https://github.com/tediousjs/node-mssql/pull/1318])
6
+ [change] Updated to latest Tedious 14 ((#1312)[https://github.com/tediousjs/node-mssql/pull/1312])
7
+ [change] Errors for bad bulk load parameters have slightly different error messages ((#1318)[https://github.com/tediousjs/node-mssql/pull/1318])
8
+ [change] Options provided to the driver via the config.options object will not be overridden with other values if set explicitly ((#1340)[https://github.com/tediousjs/node-mssql/pull/1340])
9
+ [change] Duplicate column names will now be presented as an array even if the values are empty ((#1240)[https://github.com/tediousjs/node-mssql/pull/1240])
10
+ [change] Update tarn pool dependency ((#1344)[https://github.com/tediousjs/node-mssql/pull/1344])
11
+ [removed] Remove connection string parser helpers ((#1342)[https://github.com/tediousjs/node-mssql/pull/1342])
5
12
 
6
13
  v7.2.1 (2021-08-19)
7
14
  -------------------
@@ -49,11 +56,6 @@ v7.0.0 (2021-05-06)
49
56
  [fix] Connection config objects are now deep cloned when stored against a connection pool ([#1217](https://github.com/tediousjs/node-mssql/pull/1217))
50
57
  [removed] Support for connection uri format has been removed (eg: `mssql://user:password@host/?params=values`)
51
58
 
52
- v6.4.0 (2021-11-18)
53
- -------------------
54
- [new] Transaction/PreparedStatements expose the config from their parent connection
55
- [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))
56
-
57
59
  v6.3.2 (2021-05-13)
58
60
  -------------------
59
61
  [fix] Bump various dependencies for security fixes
@@ -257,7 +259,7 @@ v4.0.4 (2017-04-25)
257
259
 
258
260
  v4.0.3 (2017-04-25)
259
261
  -------------------
260
- [fix] Fixed broken CLI & debugging
262
+ [fix] Fixed broken CLI & debugging
261
263
 
262
264
  v4.0.2 (2017-04-19)
263
265
  -------------------
@@ -278,7 +280,7 @@ v4.0.0 (2017-04-01)
278
280
  [change] Removed verbose and debug mode
279
281
  [change] Removed 'driver' from options
280
282
  [change] Removed Transaction and Prepared Statement queues
281
- [change] Removed 'multiple' directive
283
+ [change] Removed 'multiple' directive
282
284
  [change] Connection renamed to ConnectionPool
283
285
  [change] Updated to latest Tedious 2.0.0
284
286
 
package/README.md CHANGED
@@ -370,264 +370,169 @@ function processRows() {
370
370
  }
371
371
  ```
372
372
 
373
- ## Pool Management
374
-
375
- An important concept to understand when using this library is [Connection Pooling](https://en.wikipedia.org/wiki/Connection_pool)
376
- as this library uses connection pooling extensively.
373
+ ## Connection Pools
377
374
 
378
- As one Node JS process is able to handle multiple requests at once, we can take advantage of this long running process
379
- to create a pool of database connections for reuse; this saves overhead of connecting to the database for each request
375
+ An important concept to understand when using this library is [Connection Pooling](https://en.wikipedia.org/wiki/Connection_pool) as this library uses connection pooling extensively. As one Node JS process is able to handle multiple requests at once, we can take advantage of this long running process to create a pool of database connections for reuse; this saves overhead of connecting to the database for each request
380
376
  (as would be the case in something like PHP, where one process handles one request).
381
377
 
382
- With the advantages of pooling comes some added complexities, but these are mostly just conceptual and once you understand
383
- how the pooling is working, it is simple to make use of it efficiently and effectively.
378
+ With the advantages of pooling comes some added complexities, but these are mostly just conceptual and once you understand how the pooling is working, it is simple to make use of it efficiently and effectively.
384
379
 
385
- ### The Global Connection (Pool)
380
+ ### The Global Connection Pool
386
381
 
387
- To assist with pool management in your application there is the global `connect()` function that is available for use. As of
388
- v6 of this library a developer can make repeated calls to this function to obtain the global connection pool. This means you
389
- do not need to keep track of the pool in your application (as used to be the case). If the global pool is already connected,
390
- it will resolve to the connected pool. For example:
382
+ To assist with pool management in your application there is the `sql.connect()` function that is used to connect to the global connection pool. You can make repeated calls to this function, and if the global pool is already connected, it will resolve to the connected pool. The following example obtains the global connection pool by running `sql.connect()`, and then runs the query against the pool.
383
+
384
+ NB: It's important to note that there can only be one global connection pool connected at a time. Providing a different connection config to the `connect()` function will not create a new connection if it is already connected.
391
385
 
392
386
  ```js
393
387
  const sql = require('mssql')
388
+ const config = { ... }
394
389
 
395
390
  // run a query against the global connection pool
396
391
  function runQuery(query) {
397
392
  // sql.connect() will return the existing global pool if it exists or create a new one if it doesn't
398
- return sql.connect().then((pool) => {
393
+ return sql.connect(config).then((pool) => {
399
394
  return pool.query(query)
400
395
  })
401
396
  }
402
-
403
397
  ```
404
398
 
405
- Here we obtain the global connection pool by running `sql.connect()` and we then run the query against the pool.
406
- We also do *not* close the pool after the query is executed and that is because other queries may need to be run against
407
- this pool and closing it will add an overhead to running the query. We should only ever close the pool when our application
408
- is finished. For example, if we are running some kind of CLI tool or a CRON job:
399
+ Awaiting or `.then`-ing the pool creation is a safe way to ensure that the pool is always ready, without knowing where it is needed first. In practice, once the pool is created then there will be no delay for the next `connect()` call.
409
400
 
410
- ```js
411
- const sql = require('mssql')
401
+ Also notice that we do *not* close the global pool by calling `sql.close()` after the query is executed, because other queries may need to be run against this pool and closing it will add additional overhead to running subsequent queries. You should only ever close the global pool if you're certain the application is finished. Or for example, if you are running some kind of CLI tool or a CRON job you can close the pool at the end of the script.
412
402
 
413
- (() => {
414
- sql.connect().then(pool => {
415
- return pool.query('SELECT 1')
416
- }).then(result => {
417
- // do something with result
418
- }).then(() => {
419
- return sql.close()
420
- })
421
- })()
422
- ```
423
-
424
- Here the connection will be closed and the node process will exit once the queries and other application logic has completed.
425
- You should aim to only close the pool once in your application, when it is exiting or you know your application will never make
426
- another SQL query.
403
+ ### Global Pool Single Instance
427
404
 
428
- ### Advanced Pool Management
429
-
430
- In some instances you will not want to use the connection pool, you may have multiple databases to connect to or you may have
431
- one pool for read-only operations and another pool for read-write. In this instance you will need to implement your own pool
432
- management.
405
+ The ability to call `connect()` and `close()` repeatedly on the global pool is intended to make pool management easier, however it is better to maintain your own reference to the pool, where `connect()` is called **once**, and the resulting global pool's connection promise is re-used throughout the entire application.
433
406
 
434
- That could look something like this:
407
+ For example, in Express applications, the following approach uses a single global pool instance added to the `app.locals` so the application has access to it when needed. The server start is then chained inside the `connect()` promise.
435
408
 
436
409
  ```js
437
- const { ConnectionPool } = require('mssql')
438
- const POOLS = {}
439
-
440
- function createPool(config, name) {
441
- if (getPool(name)) {
442
- return Promise.reject(new Error('Pool with this name already exists'))
443
- }
444
- return (new ConnectionPool(config)).connect().then((pool) => {
445
- return POOLS[name] = pool
410
+ const express = require('express')
411
+ const sql = require('mssql')
412
+ const config = {/*...*/}
413
+ //instantiate a connection pool
414
+ const appPool = new sql.ConnectionPool(config)
415
+ //require route handlers and use the same connection pool everywhere
416
+ const route1 = require('./routes/route1')
417
+ const app = express()
418
+ app.get('/path', route1)
419
+
420
+ //connect the pool and start the web server when done
421
+ appPool.connect().then(function(pool) {
422
+ app.locals.db = pool;
423
+ const server = app.listen(3000, function () {
424
+ const host = server.address().address
425
+ const port = server.address().port
426
+ console.log('Example app listening at http://%s:%s', host, port)
446
427
  })
447
- }
448
-
449
- function closePool(name) {
450
- const pool = getPool(name)
451
- if (pool) {
452
- delete POOLS[name]
453
- return pool.close()
454
- }
455
- return Promise.resolve()
456
- }
457
-
458
- function getPool(name) {
459
- if (Object.prototype.hasOwnProperty.apply(POOLS, name)) {
460
- return POOLS[name]
461
- }
462
- }
463
-
464
- module.exports = {
465
- closePool,
466
- createPool,
467
- getPool
468
- }
428
+ }).catch(function(err) {
429
+ console.error('Error creating connection pool', err)
430
+ });
469
431
  ```
470
432
 
471
- This helper file can then be used in your application to create, fetch and close your pools. As with the global pools, you
472
- should aim to only close a pool when you know it will never be needed by the application again; typically this will be when
473
- your application is shutting down.
474
-
475
- ## Connection Pools
476
-
477
- Using a single connection pool for your application/service is recommended.
478
- Instantiating a pool with a callback, or immediately calling `.connect`, is asynchronous to ensure a connection can be
479
- established before returning. From that point, you're able to acquire connections as normal:
480
-
481
- ```javascript
482
- const sql = require('mssql')
483
-
484
- // async/await style:
485
- const pool1 = new sql.ConnectionPool(config);
486
- const pool1Connect = pool1.connect();
487
-
488
- pool1.on('error', err => {
489
- // ... error handler
490
- })
433
+ Then the route uses the connection pool in the `app.locals` object:
491
434
 
492
- async function messageHandler() {
493
- await pool1Connect; // ensures that the pool has been created
494
- try {
495
- const request = pool1.request(); // or: new sql.Request(pool1)
496
- const result = await request.query('select 1 as number')
497
- console.dir(result)
498
- return result;
499
- } catch (err) {
500
- console.error('SQL error', err);
435
+ ```js
436
+ // ./routes/route1.js
437
+ const sql = require('mssql');
438
+
439
+ module.exports = function(req, res) {
440
+ req.locals.db.query('SELECT TOP 10 * FROM table_name', function(err, recordset) {
441
+ if (err) {
442
+ console.error(err)
443
+ res.status(500).send('SERVER ERROR')
444
+ return
501
445
  }
502
- }
503
-
504
- // promise style:
505
- const pool2 = new sql.ConnectionPool(config)
506
- const pool2Connect = pool2.connect()
507
-
508
- pool2.on('error', err => {
509
- // ... error handler
510
- })
511
-
512
- function runStoredProcedure() {
513
- return pool2Connect.then((pool) => {
514
- pool.request() // or: new sql.Request(pool2)
515
- .input('input_parameter', sql.Int, 10)
516
- .output('output_parameter', sql.VarChar(50))
517
- .execute('procedure_name', (err, result) => {
518
- // ... error checks
519
- console.dir(result)
520
- })
521
- }).catch(err => {
522
- // ... error handler
523
- })
446
+ res.status(200).json({ message: 'success' })
447
+ })
524
448
  }
525
449
  ```
526
450
 
527
- Awaiting or `.then`ing the pool creation is a safe way to ensure that the pool is always ready, without knowing where it
528
- is needed first. In practice, once the pool is created then there will be no delay for the next operation.
451
+ ### Advanced Pool Management
529
452
 
530
- As of v6.1.0 you can make repeat calls to `ConnectionPool.connect()` and `ConnectonPool.close()` without an error being
531
- thrown, allowing for the safe use of `mssql.connect().then(...)` throughout your code as well as making multiple calls to
532
- close when your application is shutting down.
453
+ For some use-cases you may want to implement your own connection pool management, rather than using the global connection pool. Reasons for doing this include:
533
454
 
534
- The ability to call `connect()` repeatedly is intended to make pool management easier, however it is still recommended
535
- to follow the example above where `connect()` is called once and using the original resolved connection promise.
536
- Repeatedly calling `connect()` when running queries risks running into problems when `close()` is called on the pool.
455
+ * Supporting connections to multiple databases
456
+ * Creation of separate pools for read vs read/write operations
537
457
 
538
- **ES6 Tagged template literals**
458
+ The following code is an example of a custom connection pool implementation.
539
459
 
540
- ```javascript
541
- new sql.ConnectionPool(config).connect().then(pool => {
542
- return pool.query`select * from mytable where id = ${value}`
543
- }).then(result => {
544
- console.dir(result)
545
- }).catch(err => {
546
- // ... error checks
547
- })
548
- ```
549
-
550
- All values are automatically sanitized against sql injection.
460
+ ```js
461
+ // pool-manager.js
462
+ const mssql = require('mssql')
463
+ const pools = new Map();
551
464
 
552
- ### Managing connection pools
465
+ module.exports = {
466
+ /**
467
+ * Get or create a pool. If a pool doesn't exist the config must be provided.
468
+ * If the pool does exist the config is ignored (even if it was different to the one provided
469
+ * when creating the pool)
470
+ *
471
+ * @param {string} name
472
+ * @param {{}} [config]
473
+ * @return {Promise.<mssql.ConnectionPool>}
474
+ */
475
+ get: (name, config) => {
476
+ if (!pools.has(name)) {
477
+ if (!config) {
478
+ throw new Error('Pool does not exist');
479
+ }
480
+ const pool = new mssql.ConnectionPool(config);
481
+ // automatically remove the pool from the cache if `pool.close()` is called
482
+ const close = pool.close.bind(pool);
483
+ pool.close = (...args) => {
484
+ pools.delete(name);
485
+ return close(...args);
486
+ }
487
+ pools.set(name, pool.connect());
488
+ }
489
+ return pools.get(name);
490
+ },
491
+ /**
492
+ * Closes all the pools and removes them from the store
493
+ *
494
+ * @return {Promise<mssql.ConnectionPool[]>}
495
+ */
496
+ closeAll: () => Promise.all(Array.from(pools.values()).map((connect) => {
497
+ return connect.then((pool) => pool.close());
498
+ })),
499
+ };
500
+ ```
553
501
 
554
- Most applications will only need a single `ConnectionPool` that can be shared throughout the code. To aid the sharing
555
- of a single pool this library exposes a set of functions to access a single global connection. eg:
502
+ This file can then be used in your application to create, fetch, and close pools.
556
503
 
557
504
  ```js
558
- // as part of your application's boot process
505
+ const { get } = require('./pool-manager')
559
506
 
560
- const sql = require('mssql')
561
- const poolPromise = sql.connect()
507
+ async function example() {
508
+ const pool = await get('default')
509
+ return pool.request().query('SELECT 1')
510
+ }
511
+ ```
562
512
 
563
- // during your applications runtime
513
+ Similar to the global connection pool, you should aim to only close a pool when you know it will never be needed by the application again. Typically this will only be when your application is shutting down.
564
514
 
565
- poolPromise.then(() => {
566
- return sql.query('SELECT 1')
567
- }).then(result => {
568
- console.dir(result)
569
- })
515
+ ### Result value manipulation
570
516
 
571
- // when your application exits
572
- poolPromise.then(() => {
573
- return sql.close()
574
- })
575
- ```
517
+ In some instances it is desirable to manipulate the record data as it is returned from the database, this may be to cast it as a particular object (eg: `moment` object instead of `Date`) or similar.
576
518
 
577
- If you require multiple pools per application (perhaps you have many DBs you need to connect to or you want a read-only
578
- pool), then you will need to manage your pools yourself. The best way to do this is to create a shared library file that
579
- can hold references to the pools for you. For example:
519
+ In v8.0.0+ it is possible to register per-datatype handlers:
580
520
 
581
521
  ```js
582
522
  const sql = require('mssql')
583
523
 
584
- const pools = {}
585
-
586
- // manage a set of pools by name (config will be required to create the pool)
587
- // a pool will be removed when it is closed
588
- async function getPool(name, config) {
589
- if (!Object.prototype.hasOwnProperty.call(pools, name)) {
590
- const pool = new sql.ConnectionPool(config)
591
- const close = pool.close.bind(pool)
592
- pool.close = (...args) => {
593
- delete pools[name]
594
- return close(...args)
595
- }
596
- await pool.connect()
597
- pools[name] = pool
598
- }
599
- return pools[name]
600
- }
601
-
602
- // close all pools
603
- function closeAll() {
604
- return Promise.all(Object.values(pools).map((pool) => {
605
- return pool.close()
606
- }))
607
- }
608
-
609
- module.exports = {
610
- closeAll,
611
- getPool
612
- }
613
- ```
614
-
615
- You can then use this library file in your code to get a connected pool when you need it:
524
+ // in this example all integer values will return 1 more than their actual value in the database
525
+ sql.valueHandler.set(sql.TYPES.Int, (value) => value + 1)
616
526
 
617
- ```js
618
- const { getPool } = require('./path/to/file')
619
-
620
- // run a query
621
- async function runQuery(query, config) {
622
- // pool will always be connected when the promise has resolved - may reject if the connection config is invalid
623
- const pool = await getPool('default', config)
624
- const result = await pool.request().query(query)
625
- return result
626
- }
527
+ sql.query('SELECT * FROM [example]').then((result) => {
528
+ // all `int` columns will return a manipulated value as per the callback above
529
+ })
627
530
  ```
628
531
 
629
532
  ## Configuration
630
533
 
534
+ The following is an example configuration object:
535
+
631
536
  ```javascript
632
537
  const config = {
633
538
  user: '...',
@@ -1616,6 +1521,20 @@ request.execute('MyCustomStoredProcedure', (err, result) => {
1616
1521
 
1617
1522
  **TIP**: You can also create Table variable from any recordset with `recordset.toTable()`. You can optionally specify table type name in the first argument.
1618
1523
 
1524
+ You can clear the table rows for easier batching by using `table.rows.clear()`
1525
+
1526
+ ```js
1527
+ const tvp = new sql.Table() // You can optionally specify table type name in the first argument.
1528
+
1529
+ // Columns must correspond with type we have created in database.
1530
+ tvp.columns.add('a', sql.VarChar(50))
1531
+ tvp.columns.add('b', sql.Int)
1532
+
1533
+ // Add rows
1534
+ tvp.rows.add('hello tvp', 777) // Values are in same order as columns.
1535
+ tvp.rows.clear()
1536
+ ```
1537
+
1619
1538
  ## Response Schema
1620
1539
 
1621
1540
  An object returned from a `sucessful` basic query would look like the following.
@@ -7,7 +7,7 @@ const tarn = require('tarn')
7
7
  const { IDS } = require('../utils')
8
8
  const ConnectionError = require('../error/connection-error')
9
9
  const shared = require('../shared')
10
- const deepclone = require('rfdc/default')
10
+ const clone = require('rfdc/default')
11
11
 
12
12
  /**
13
13
  * Class ConnectionPool.
@@ -44,7 +44,7 @@ class ConnectionPool extends EventEmitter {
44
44
 
45
45
  if (typeof config === 'string') {
46
46
  try {
47
- this.config = this.parseConnectionString(config)
47
+ this.config = this.constructor.parseConnectionString(config)
48
48
  } catch (ex) {
49
49
  if (typeof callback === 'function') {
50
50
  return setImmediate(callback, ex)
@@ -52,7 +52,7 @@ class ConnectionPool extends EventEmitter {
52
52
  throw ex
53
53
  }
54
54
  } else {
55
- this.config = deepclone(config)
55
+ this.config = clone(config)
56
56
  }
57
57
 
58
58
  // set defaults
@@ -85,11 +85,11 @@ class ConnectionPool extends EventEmitter {
85
85
  return this._healthy
86
86
  }
87
87
 
88
- parseConnectionString (connectionString) {
88
+ static parseConnectionString (connectionString) {
89
89
  return this._parseConnectionString(connectionString)
90
90
  }
91
91
 
92
- _parseConnectionString (connectionString) {
92
+ static _parseConnectionString (connectionString) {
93
93
  const parsed = parseSqlConnectionString(connectionString, true, true)
94
94
  return Object.entries(parsed).reduce((config, [key, value]) => {
95
95
  switch (key) {
package/lib/base/index.js CHANGED
@@ -54,6 +54,13 @@ Object.defineProperty(module.exports, 'Promise', {
54
54
  }
55
55
  })
56
56
 
57
+ Object.defineProperty(module.exports, 'valueHandler', {
58
+ enumerable: true,
59
+ value: shared.valueHandler,
60
+ writable: false,
61
+ configurable: false
62
+ })
63
+
57
64
  for (const key in TYPES) {
58
65
  const value = TYPES[key]
59
66
  module.exports.exports[key] = value
@@ -35,10 +35,6 @@ class PreparedStatement extends EventEmitter {
35
35
  this.parameters = {}
36
36
  }
37
37
 
38
- get config () {
39
- return this.parent.config
40
- }
41
-
42
38
  get connected () {
43
39
  return this.parent.connected
44
40
  }
@@ -26,7 +26,7 @@ class Request extends EventEmitter {
26
26
  /**
27
27
  * Create new Request.
28
28
  *
29
- * @param {Connection|ConnectionPool|Transaction|PreparedStatement} parent If omitted, global connection is used instead.
29
+ * @param {Connection|ConnectionPool|Transaction|PreparedStatement} parent If ommited, global connection is used instead.
30
30
  */
31
31
 
32
32
  constructor (parent) {
@@ -39,8 +39,6 @@ class Request extends EventEmitter {
39
39
  this._paused = false
40
40
  this.parent = parent || globalConnection.pool
41
41
  this.parameters = {}
42
- this.stream = null
43
- this.arrayRowMode = null
44
42
  }
45
43
 
46
44
  get paused () {
@@ -226,8 +224,8 @@ class Request extends EventEmitter {
226
224
  */
227
225
 
228
226
  batch (batch, callback) {
229
- if (this.stream === null && this.parent) this.stream = this.parent.config.stream
230
- if (this.arrayRowMode === null && this.parent) this.arrayRowMode = this.parent.config.arrayRowMode
227
+ if (this.stream == null && this.connection) this.stream = this.connection.config.stream
228
+ if (this.arrayRowMode == null && this.connection) this.arrayRowMode = this.connection.config.arrayRowMode
231
229
  this.rowsAffected = 0
232
230
 
233
231
  if (typeof callback === 'function') {
@@ -290,11 +288,11 @@ class Request extends EventEmitter {
290
288
  */
291
289
 
292
290
  _batch (batch, callback) {
293
- if (!this.parent) {
291
+ if (!this.connection) {
294
292
  return setImmediate(callback, new RequestError('No connection is specified for that request.', 'ENOCONN'))
295
293
  }
296
294
 
297
- if (!this.parent.connected) {
295
+ if (!this.connection.connected) {
298
296
  return setImmediate(callback, new ConnectionError('Connection is closed.', 'ECONNCLOSED'))
299
297
  }
300
298
 
@@ -319,8 +317,8 @@ class Request extends EventEmitter {
319
317
  options = {}
320
318
  }
321
319
 
322
- if (this.stream === null && this.parent) this.stream = this.parent.config.stream
323
- if (this.arrayRowMode === null && this.parent) this.arrayRowMode = this.parent.config.arrayRowMode
320
+ if (this.stream == null && this.connection) this.stream = this.connection.config.stream
321
+ if (this.arrayRowMode == null && this.connection) this.arrayRowMode = this.connection.config.arrayRowMode
324
322
 
325
323
  if (this.stream || typeof callback === 'function') {
326
324
  this._bulk(table, options, (err, rowsAffected) => {
@@ -421,8 +419,8 @@ class Request extends EventEmitter {
421
419
  */
422
420
 
423
421
  query (command, callback) {
424
- if (this.stream === null && this.parent) this.stream = this.parent.config.stream
425
- if (this.arrayRowMode === null && this.parent) this.arrayRowMode = this.parent.config.arrayRowMode
422
+ if (this.stream == null && this.connection) this.stream = this.connection.config.stream
423
+ if (this.arrayRowMode == null && this.connection) this.arrayRowMode = this.connection.config.arrayRowMode
426
424
  this.rowsAffected = 0
427
425
 
428
426
  if (typeof callback === 'function') {
@@ -510,8 +508,8 @@ class Request extends EventEmitter {
510
508
  */
511
509
 
512
510
  execute (command, callback) {
513
- if (this.stream === null && this.parent) this.stream = this.parent.config.stream
514
- if (this.arrayRowMode === null && this.parent) this.arrayRowMode = this.parent.config.arrayRowMode
511
+ if (this.stream == null && this.connection) this.stream = this.connection.config.stream
512
+ if (this.arrayRowMode == null && this.connection) this.arrayRowMode = this.connection.config.arrayRowMode
515
513
  this.rowsAffected = 0
516
514
 
517
515
  if (typeof callback === 'function') {
@@ -37,10 +37,6 @@ class Transaction extends EventEmitter {
37
37
  this.name = ''
38
38
  }
39
39
 
40
- get config () {
41
- return this.parent.config
42
- }
43
-
44
40
  get connected () {
45
41
  return this.parent.connected
46
42
  }
@@ -6,9 +6,11 @@ const BaseConnectionPool = require('../base/connection-pool')
6
6
  const { IDS, INCREMENT } = require('../utils')
7
7
  const shared = require('../shared')
8
8
  const ConnectionError = require('../error/connection-error')
9
+ const { platform } = require('os')
9
10
 
10
- const CONNECTION_STRING_PORT = 'Driver=SQL Server Native Client 11.0;Server=#{server},#{port};Database=#{database};Uid=#{user};Pwd=#{password};Trusted_Connection=#{trusted};Encrypt=#{encrypt};'
11
- const CONNECTION_STRING_NAMED_INSTANCE = 'Driver=SQL Server Native Client 11.0;Server=#{server}\\#{instance};Database=#{database};Uid=#{user};Pwd=#{password};Trusted_Connection=#{trusted};Encrypt=#{encrypt};'
11
+ const CONNECTION_DRIVER = ['darwin', 'linux'].includes(platform()) ? 'ODBC Driver 17 for SQL Server' : 'SQL Server Native Client 11.0'
12
+ const CONNECTION_STRING_PORT = `Driver=${CONNECTION_DRIVER};Server=#{server},#{port};Database=#{database};Uid=#{user};Pwd=#{password};Trusted_Connection=#{trusted};Encrypt=#{encrypt};`
13
+ const CONNECTION_STRING_NAMED_INSTANCE = `Driver=${CONNECTION_DRIVER};Server=#{server}\\#{instance};Database=#{database};Uid=#{user};Pwd=#{password};Trusted_Connection=#{trusted};Encrypt=#{encrypt};`
12
14
 
13
15
  class ConnectionPool extends BaseConnectionPool {
14
16
  _poolCreate () {
@@ -22,6 +22,13 @@ Object.defineProperty(module.exports, 'Promise', {
22
22
  }
23
23
  })
24
24
 
25
+ Object.defineProperty(module.exports, 'valueHandler', {
26
+ enumerable: true,
27
+ value: base.valueHandler,
28
+ writable: false,
29
+ configurable: false
30
+ })
31
+
25
32
  base.driver.name = 'msnodesqlv8'
26
33
  base.driver.ConnectionPool = ConnectionPool
27
34
  base.driver.Transaction = Transaction
@@ -8,6 +8,7 @@ const { IDS, objectHasProperty } = require('../utils')
8
8
  const { TYPES, DECLARATIONS, declare } = require('../datatypes')
9
9
  const { PARSERS: UDT } = require('../udt')
10
10
  const Table = require('../table')
11
+ const { valueHandler } = require('../shared')
11
12
 
12
13
  const JSON_COLUMN_ID = 'JSON_F52E2B61-18A1-11d1-B105-00805F49916B'
13
14
  const XML_COLUMN_ID = 'XML_F52E2B61-18A1-11d1-B105-00805F49916B'
@@ -122,7 +123,12 @@ const createColumns = function (metadata, arrayRowMode) {
122
123
  }
123
124
 
124
125
  const valueCorrection = function (value, metadata) {
125
- if ((metadata.sqlType === 'time') && (value != null)) {
126
+ const type = metadata && objectHasProperty(metadata, 'sqlType') && objectHasProperty(DECLARATIONS, metadata.sqlType)
127
+ ? DECLARATIONS[metadata.sqlType]
128
+ : null
129
+ if (type && valueHandler.has(type)) {
130
+ return valueHandler.get(type)(value)
131
+ } else if ((metadata.sqlType === 'time') && (value != null)) {
126
132
  value.setFullYear(1970)
127
133
  return value
128
134
  } else if ((metadata.sqlType === 'udt') && (value != null)) {
package/lib/shared.js CHANGED
@@ -105,3 +105,10 @@ Object.defineProperty(module.exports, 'Promise', {
105
105
  PromiseLibrary = value
106
106
  }
107
107
  })
108
+
109
+ Object.defineProperty(module.exports, 'valueHandler', {
110
+ enumerable: true,
111
+ value: new Map(),
112
+ writable: false,
113
+ configurable: false
114
+ })
package/lib/table.js CHANGED
@@ -48,6 +48,13 @@ function Table (name) {
48
48
  }
49
49
  }
50
50
  )
51
+
52
+ Object.defineProperty(this.rows, 'clear', {
53
+ value () {
54
+ return this.splice(0, this.length)
55
+ }
56
+ }
57
+ )
51
58
  }
52
59
 
53
60
  /*
@@ -34,24 +34,23 @@ class ConnectionPool extends BaseConnectionPool {
34
34
  }, this.config.authentication)
35
35
  }
36
36
 
37
- cfg.options.database = this.config.database
38
- cfg.options.port = this.config.port
39
- cfg.options.connectTimeout = this.config.connectionTimeout || this.config.timeout || 15000
40
- cfg.options.requestTimeout = this.config.requestTimeout != null ? this.config.requestTimeout : 15000
37
+ cfg.options.database = cfg.options.database || this.config.database
38
+ cfg.options.port = cfg.options.port || this.config.port
39
+ cfg.options.connectTimeout = cfg.options.connectTimeout || this.config.connectionTimeout || this.config.timeout || 15000
40
+ cfg.options.requestTimeout = cfg.options.requestTimeout || this.config.requestTimeout != null ? this.config.requestTimeout : 15000
41
41
  cfg.options.tdsVersion = cfg.options.tdsVersion || '7_4'
42
- cfg.options.rowCollectionOnDone = false
43
- cfg.options.rowCollectionOnRequestCompletion = false
44
- cfg.options.useColumnNames = false
42
+ cfg.options.rowCollectionOnDone = cfg.options.rowCollectionOnDone || false
43
+ cfg.options.rowCollectionOnRequestCompletion = cfg.options.rowCollectionOnRequestCompletion || false
44
+ cfg.options.useColumnNames = cfg.options.useColumnNames || false
45
45
  cfg.options.appName = cfg.options.appName || 'node-mssql'
46
46
 
47
47
  // tedious always connect via tcp when port is specified
48
48
  if (cfg.options.instanceName) delete cfg.options.port
49
49
 
50
50
  if (isNaN(cfg.options.requestTimeout)) cfg.options.requestTimeout = 15000
51
- if (cfg.options.requestTimeout === Infinity) cfg.options.requestTimeout = 0
52
- if (cfg.options.requestTimeout < 0) cfg.options.requestTimeout = 0
51
+ if (cfg.options.requestTimeout === Infinity || cfg.options.requestTimeout < 0) cfg.options.requestTimeout = 0
53
52
 
54
- if (this.config.debug) {
53
+ if (!cfg.options.debug && this.config.debug) {
55
54
  cfg.options.debug = {
56
55
  packet: true,
57
56
  token: true,
@@ -73,6 +72,7 @@ class ConnectionPool extends BaseConnectionPool {
73
72
  }
74
73
 
75
74
  debug('connection(%d): established', IDS.get(tedious))
75
+ this.collation = tedious.databaseCollation
76
76
  resolveOnce(tedious)
77
77
  })
78
78
  IDS.add(tedious, 'Connection')
@@ -22,6 +22,13 @@ Object.defineProperty(module.exports, 'Promise', {
22
22
  }
23
23
  })
24
24
 
25
+ Object.defineProperty(module.exports, 'valueHandler', {
26
+ enumerable: true,
27
+ value: base.valueHandler,
28
+ writable: false,
29
+ configurable: false
30
+ })
31
+
25
32
  base.driver.name = 'tedious'
26
33
  base.driver.ConnectionPool = ConnectionPool
27
34
  base.driver.Transaction = Transaction
@@ -8,6 +8,7 @@ const { IDS, objectHasProperty } = require('../utils')
8
8
  const { TYPES, DECLARATIONS, declare, cast } = require('../datatypes')
9
9
  const Table = require('../table')
10
10
  const { PARSERS: UDT } = require('../udt')
11
+ const { valueHandler } = require('../shared')
11
12
 
12
13
  const JSON_COLUMN_ID = 'JSON_F52E2B61-18A1-11d1-B105-00805F49916B'
13
14
  const XML_COLUMN_ID = 'XML_F52E2B61-18A1-11d1-B105-00805F49916B'
@@ -159,7 +160,10 @@ const createColumns = function (metadata, arrayRowMode) {
159
160
  }
160
161
 
161
162
  const valueCorrection = function (value, metadata) {
162
- if ((metadata.type === tds.TYPES.UDT) && (value != null)) {
163
+ const type = getMssqlType(metadata.type)
164
+ if (valueHandler.has(type)) {
165
+ return valueHandler.get(type)(value)
166
+ } else if ((metadata.type === tds.TYPES.UDT) && (value != null)) {
163
167
  if (UDT[metadata.udtInfo.typeName]) {
164
168
  return UDT[metadata.udtInfo.typeName](value)
165
169
  } else {
@@ -334,26 +338,18 @@ class Request extends BaseRequest {
334
338
  bulk.addColumn(col.name, getTediousType(col.type), { nullable: col.nullable, length: col.length, scale: col.scale, precision: col.precision })
335
339
  }
336
340
 
337
- for (const row of table.rows) {
338
- try {
339
- bulk.addRow(row)
340
- } catch (e) {
341
- return handleError(true, connection, e)
342
- }
343
- }
344
-
345
341
  if (table.create) {
346
342
  const objectid = table.temporary ? `tempdb..[${table.name}]` : table.path
347
343
  const req = new tds.Request(`if object_id('${objectid.replace(/'/g, '\'\'')}') is null ${table.declare()}`, err => {
348
344
  if (err) return done(err)
349
345
 
350
- connection.execBulkLoad(bulk)
346
+ connection.execBulkLoad(bulk, table.rows)
351
347
  })
352
348
  this._setCurrentRequest(req)
353
349
 
354
350
  connection.execSqlBatch(req)
355
351
  } else {
356
- connection.execBulkLoad(bulk)
352
+ connection.execBulkLoad(bulk, table.rows)
357
353
  }
358
354
  })
359
355
  })
@@ -608,7 +604,7 @@ class Request extends BaseRequest {
608
604
  row.push(col.value)
609
605
  } else {
610
606
  const exi = row[col.metadata.colName]
611
- if (exi != null) {
607
+ if (exi !== undefined) {
612
608
  if (exi instanceof Array) {
613
609
  exi.push(col.value)
614
610
  } else {
@@ -645,7 +641,7 @@ class Request extends BaseRequest {
645
641
  const param = this.parameters[name]
646
642
 
647
643
  try {
648
- param.value = getTediousType(param.type).validate(param.value)
644
+ param.value = getTediousType(param.type).validate(param.value, this.parent.collation)
649
645
  } catch (e) {
650
646
  e.message = `Validation failed for parameter '${name}'. ${e.message}`
651
647
  const err = new RequestError(e, 'EPARAM')
package/package.json CHANGED
@@ -21,19 +21,19 @@
21
21
  "azure",
22
22
  "node-mssql"
23
23
  ],
24
- "version": "7.3.0",
24
+ "version": "8.0.0",
25
25
  "main": "index.js",
26
26
  "repository": "github:tediousjs/node-mssql",
27
27
  "license": "MIT",
28
28
  "dependencies": {
29
29
  "@tediousjs/connection-string": "^0.3.0",
30
- "debug": "^4.3.2",
30
+ "debug": "^4.3.3",
31
31
  "rfdc": "^1.3.0",
32
- "tarn": "^3.0.1",
33
- "tedious": "^11.4.0"
32
+ "tarn": "^3.0.2",
33
+ "tedious": "^14.0.0"
34
34
  },
35
35
  "devDependencies": {
36
- "mocha": "^8.4.0",
36
+ "mocha": "^9.1.3",
37
37
  "standard": "^16.0.4"
38
38
  },
39
39
  "engines": {
@@ -1,289 +0,0 @@
1
- 'use strict'
2
-
3
- const URL = require('url').URL
4
-
5
- const IGNORE_KEYS = ['stream']
6
- const oror = function () {
7
- for (let i = 0, l = arguments.length; i < l; i++) {
8
- if (arguments[i] !== null && arguments[i] !== undefined) {
9
- return arguments[i]
10
- }
11
- }
12
- }
13
-
14
- const parseConnectionURI = function (uri) {
15
- const parsed = new URL(uri)
16
- let instance
17
- let user
18
- let password
19
-
20
- const path = parsed.pathname.substr(1).split('/')
21
- if (path.length > 1) {
22
- instance = path.shift()
23
- }
24
-
25
- if (parsed.username) {
26
- const auth = [parsed.username, parsed.password]
27
- user = decodeURIComponent(auth.shift())
28
- password = decodeURIComponent(auth.join(':'))
29
- }
30
-
31
- const port = parsed.port ? `,${parsed.port}` : (instance ? `\\${instance}` : '')
32
- const object = {
33
- server: `${parsed.hostname}${port}`,
34
- uid: user || '',
35
- pwd: password || '',
36
- database: path[0]
37
- }
38
-
39
- if (parsed.searchParams) {
40
- parsed.searchParams.forEach((value, key) => {
41
- if (key === 'domain') {
42
- object.uid = `${value}\\${object.uid}`
43
- } else {
44
- object[key] = value
45
- }
46
- })
47
- }
48
-
49
- Object.defineProperty(object, 'toString', {
50
- value () {
51
- const out = []
52
- for (const key in this) {
53
- if (IGNORE_KEYS.indexOf(key) === -1) {
54
- out.push(`${key}={${this[key]}}`)
55
- }
56
- }
57
- return out.join(';')
58
- }
59
- })
60
-
61
- return object
62
- }
63
-
64
- const parseConnectionString = function (string) {
65
- let cursor = 0
66
- let parsing = 'name'
67
- let param = null
68
- let buffer = ''
69
- let quotes = null
70
- const parsed = {}
71
- const original = {}
72
-
73
- Object.defineProperty(parsed, '__original__', { value: original })
74
- Object.defineProperty(parsed, 'toString', {
75
- value () {
76
- const out = []
77
- for (const key in this) {
78
- if (IGNORE_KEYS.indexOf(key) === -1) {
79
- const esc = original[key].escape || ['', '']
80
- out.push(`${original[key].name}=${esc[0] || ''}${this[key]}${esc[1] || ''}`)
81
- }
82
- }
83
- return out.join(';')
84
- }
85
- })
86
-
87
- while (cursor < string.length) {
88
- const char = string.charAt(cursor)
89
- switch (char) {
90
- case '=':
91
- if (parsing === 'name') {
92
- buffer = buffer.trim()
93
- param = buffer.toLowerCase()
94
- original[param] = { name: buffer }
95
- parsing = 'value'
96
- buffer = ''
97
- } else {
98
- buffer += char
99
- }
100
- break
101
-
102
- case '\'': case '"':
103
- if (parsing === 'value') {
104
- if (!buffer.trim().length) {
105
- // value is wrapped in qotes
106
- original[param].escape = [char, char]
107
- quotes = char
108
- buffer = ''
109
- } else if (quotes) {
110
- if (char === quotes) {
111
- // found same char as used for wrapping quotes
112
- if (char === string.charAt(cursor + 1)) {
113
- // escaped quote
114
- buffer += char
115
- cursor++
116
- } else {
117
- // end of value
118
- parsed[param] = buffer
119
- param = null
120
- parsing = null
121
- buffer = ''
122
- quotes = null
123
- }
124
- } else {
125
- buffer += char
126
- }
127
- } else {
128
- buffer += char
129
- }
130
- } else {
131
- throw new Error('Invalid connection string.')
132
- }
133
- break
134
-
135
- case '{':
136
- if (parsing === 'value') {
137
- if (buffer.trim().length === 0) {
138
- // value is wrapped in qotes
139
- original[param].escape = ['{', '}']
140
- quotes = '{}'
141
- buffer = ''
142
- } else {
143
- buffer += char
144
- }
145
- } else {
146
- throw new Error('Invalid connection string.')
147
- }
148
- break
149
-
150
- case '}':
151
- if (parsing === 'value') {
152
- if (quotes === '{}') {
153
- // end of value
154
- parsed[param] = buffer
155
- param = null
156
- parsing = null
157
- buffer = ''
158
- quotes = null
159
- } else {
160
- buffer += char
161
- }
162
- } else {
163
- throw new Error('Invalid connection string.')
164
- }
165
- break
166
-
167
- case ';':
168
- if (parsing === 'value') {
169
- if (quotes) {
170
- buffer += char
171
- } else {
172
- // end of value
173
- parsed[param] = buffer
174
- param = null
175
- parsing = 'name'
176
- buffer = ''
177
- }
178
- } else {
179
- buffer = ''
180
- parsing = 'name'
181
- }
182
- break
183
-
184
- default:
185
- buffer += char
186
- }
187
-
188
- cursor++
189
- }
190
-
191
- if (parsing === 'value') {
192
- // end of value
193
- parsed[param] = buffer
194
- }
195
-
196
- return parsed
197
- }
198
-
199
- const resolveConnectionString = function (string, driver) {
200
- const parsed = /^(mssql|tedious|msnodesql|tds):\/\//i.test(string) ? parseConnectionURI(string) : parseConnectionString(string)
201
- const stream = (parsed.stream || '').toLowerCase()
202
- const encrypt = (parsed.encrypt || '').toLowerCase()
203
-
204
- if (driver === 'msnodesqlv8') {
205
- parsed.driver = 'SQL Server Native Client 11.0'
206
-
207
- if (parsed.__original__) {
208
- parsed.__original__.driver = { name: 'Driver', escape: ['{', '}'] }
209
- }
210
-
211
- return { connectionString: parsed.toString() }
212
- }
213
-
214
- let user = parsed.uid || parsed.uid || parsed['user id']
215
- let server = parsed.server || parsed.address || parsed.addr || parsed['data source'] || parsed['network address']
216
-
217
- const config = {
218
- password: oror(parsed.pwd, parsed.password),
219
- database: oror(parsed.database, parsed['initial catalog']),
220
- connectionTimeout: oror(parsed.connectionTimeout, parsed.timeout, parsed['connect timeout'], parsed['connection timeout']),
221
- requestTimeout: oror(parsed.requestTimeout, parsed['request timeout']),
222
- stream: stream === 'true' || stream === 'yes' || stream === '1',
223
- options: {
224
- readOnlyIntent: parsed.applicationintent && parsed.applicationintent.toLowerCase() === 'readonly',
225
- encrypt: encrypt === 'true' || encrypt === 'yes' || encrypt === '1'
226
- }
227
- }
228
-
229
- if (parsed.trustservercertificate && ['true', '1', 'yes'].includes(parsed.trustservercertificate.toLowerCase())) {
230
- Object.assign(config.options, {
231
- trustServerCertificate: true
232
- })
233
- }
234
-
235
- if (parsed.useUTC != null) {
236
- const utc = parsed.useUTC.toLowerCase()
237
- config.options.useUTC = utc === 'true' || utc === 'yes' || utc === '1'
238
- }
239
- if (config.connectionTimeout != null) {
240
- config.connectionTimeout = parseInt(config.connectionTimeout, 10) * 1000
241
- }
242
- if (config.requestTimeout != null) {
243
- config.requestTimeout = parseInt(config.requestTimeout, 10)
244
- }
245
-
246
- if (parsed.multisubnetfailover != null) {
247
- config.options.multiSubnetFailover = parsed.multisubnetfailover.toLowerCase() === 'true'
248
- }
249
-
250
- if (/^(.*)\\(.*)$/.exec(user)) {
251
- config.domain = RegExp.$1
252
- user = RegExp.$2
253
- }
254
-
255
- if (server) {
256
- server = server.trim()
257
-
258
- if (/^np:/i.test(server)) {
259
- throw new Error('Connection via Named Pipes is not supported.')
260
- }
261
-
262
- if (/^tcp:/i.test(server)) {
263
- server = server.substr(4)
264
- }
265
-
266
- if (/^(.*)\\(.*)$/.exec(server)) {
267
- server = RegExp.$1
268
- config.options.instanceName = RegExp.$2
269
- }
270
-
271
- if (/^(.*),(.*)$/.exec(server)) {
272
- server = RegExp.$1.trim()
273
- config.port = parseInt(RegExp.$2.trim(), 10)
274
- }
275
-
276
- if (server === '.' || server === '(.)' || server.toLowerCase() === '(localdb)' || server.toLowerCase() === '(local)') {
277
- server = 'localhost'
278
- }
279
- }
280
-
281
- config.user = user
282
- config.server = server
283
- return config
284
- }
285
-
286
- module.exports = {
287
- parse: parseConnectionString,
288
- resolve: resolveConnectionString
289
- }