Repository: tediousjs/node-mssql
Pull request #1343 (2021-11-25 21:57:42)

Remove duplicated info, improve clarity, add samples
What this does:

Improves clarity in the documentation around global pools, and adds some clarity/examples for using a single app-wide instance for the global pool to fix connection issues/anomalies. Even though there have been some fixes to allow repeated use of `sql.connect()`, it doesn't fix everything and there are still cases where a single instance is necessary/preferred depending on the implementation, environment, etc.

There are many github issues on this that show that this is still an issue and needs to be better represented in the documentation. I was specifically having issues inside Azure Functions and with Azure SQL, but you can see in the newer discussion in #138 that people are also having issues with AWS lambda, heroku, etc.

There was also a lot of duplicated code (there were two different examples and sections for a custom pool implementation) and information in the pools documentation which was confusing and difficult to follow. I cleaned some of this up as well so this section is a lot more concise and easy to follow now.

Related issues:

[#135](https://github.com/tediousjs/node-mssql/issues/135)
[#118](https://github.com/tediousjs/node-mssql/issues/118)
[#138](https://github.com/tediousjs/node-mssql/issues/138#)

Pre/Post merge checklist:

- [ ] Update change log



Patch:
diff --git a/CHANGELOG.txt b/CHANGELOG.txt
index 39e783a..22310a4 100644
--- a/CHANGELOG.txt
+++ b/CHANGELOG.txt
@@ -6,8 +6,6 @@ v8.0.0 (2021-??-??)
 [change] Errors for bad bulk load parameters have slightly different error messages ((#1318)[https://github.com/tediousjs/node-mssql/pull/1318])
 [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])
 [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])
-[change] Update tarn pool dependency ((#1344)[https://github.com/tediousjs/node-mssql/pull/1344])
-[removed] Remove connection string parser helpers ((#1342)[https://github.com/tediousjs/node-mssql/pull/1342])
 
 v7.2.1 (2021-08-19)
 -------------------
diff --git a/README.md b/README.md
index 19b1ecf..a0dddaf 100644
--- a/README.md
+++ b/README.md
@@ -370,264 +370,152 @@ function processRows() {
 }
 ```
 
-## Pool Management
-
-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.
+## Connection Pools
 
-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
+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
 (as would be the case in something like PHP, where one process handles one request).
 
-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.
+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.
+
+### The Global Connection Pool
 
-### The Global Connection (Pool)
+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.
 
-To assist with pool management in your application there is the global `connect()` function that is available for use. As of
-v6 of this library a developer can make repeated calls to this function to obtain the global connection pool. This means you
-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,
-it will resolve to the connected pool. For example:
+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.
 
 ```js
 const sql = require('mssql')
+const config = { ... }
 
 // run a query against the global connection pool
 function runQuery(query) {
   // sql.connect() will return the existing global pool if it exists or create a new one if it doesn't
-  return sql.connect().then((pool) => {
+  return sql.connect(config).then((pool) => {
     return pool.query(query)
   })
 }
-
 ```
 
-Here we obtain the global connection pool by running `sql.connect()` and we then run the query against the pool.
-We also do *not* close the pool after the query is executed and that is because other queries may need to be run against
-this pool and closing it will add an overhead to running the query. We should only ever close the pool when our application
-is finished. For example, if we are running some kind of CLI tool or a CRON job:
-
-```js
-const sql = require('mssql')
-
-(() => {
-  sql.connect().then(pool => {
-    return pool.query('SELECT 1')
-  }).then(result => {
-    // do something with result
-  }).then(() => {
-    return sql.close()
-  })
-})()
-```
+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.
 
-Here the connection will be closed and the node process will exit once the queries and other application logic has completed.
-You should aim to only close the pool once in your application, when it is exiting or you know your application will never make
-another SQL query.
+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.
 
-### Advanced Pool Management
+### Global Pool Single Instance
 
-In some instances you will not want to use the connection pool, you may have multiple databases to connect to or you may have
-one pool for read-only operations and another pool for read-write. In this instance you will need to implement your own pool
-management.
+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.
 
-That could look something like this:
+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.
 
 ```js
-const { ConnectionPool } = require('mssql')
-const POOLS = {}
-
-function createPool(config, name) {
-  if (getPool(name)) {
-    return Promise.reject(new Error('Pool with this name already exists'))
-  }
-  return (new ConnectionPool(config)).connect().then((pool) => {
-    return POOLS[name] = pool
+const express = require('express')
+const sql = require('mssql')
+const config  = {/*...*/}
+//instantiate a connection pool
+const appPool = new sql.ConnectionPool(config)
+//require route handlers and use the same connection pool everywhere
+const route1 = require('./routes/route1')
+const app = express()
+app.get('/path', route1)
+
+//connect the pool and start the web server when done
+appPool.connect().then(function(pool) {
+  app.locals.db = pool;
+  const server = app.listen(3000, function () {
+    const host = server.address().address
+    const port = server.address().port
+    console.log('Example app listening at http://%s:%s', host, port)
   })
-}
-
-function closePool(name) {
-  const pool = getPool(name)
-  if (pool) {
-    delete POOLS[name]
-    return pool.close()
-  }
-  return Promise.resolve()
-}
-
-function getPool(name) {
-  if (Object.prototype.hasOwnProperty.apply(POOLS, name)) {
-    return POOLS[name]
-  }
-}
-
-module.exports = {
-  closePool,
-  createPool,
-  getPool
-}
+}).catch(function(err) {
+  console.error('Error creating connection pool', err)
+});
 ```
 
-This helper file can then be used in your application to create, fetch and close your pools. As with the global pools, you
-should aim to only close a pool when you know it will never be needed by the application again; typically this will be when
-your application is shutting down.
-
-## Connection Pools
-
-Using a single connection pool for your application/service is recommended.
-Instantiating a pool with a callback, or immediately calling `.connect`, is asynchronous to ensure a connection can be
-established before returning. From that point, you're able to acquire connections as normal:  
-
-```javascript
-const sql = require('mssql')
-
-// async/await style:
-const pool1 = new sql.ConnectionPool(config);
-const pool1Connect = pool1.connect();
+Then the route uses the connection pool in the `app.locals` object:
 
-pool1.on('error', err => {
-    // ... error handler
-})
-
-async function messageHandler() {
-    await pool1Connect; // ensures that the pool has been created
-    try {
-        const request = pool1.request(); // or: new sql.Request(pool1)
-        const result = await request.query('select 1 as number')
-        console.dir(result)
-        return result;
-    } catch (err) {
-        console.error('SQL error', err);
+```js
+// ./routes/route1.js
+const sql = require('mssql');
+
+module.exports = function(req, res) {
+  req.locals.db.query('SELECT TOP 10 * FROM table_name', function(err, recordset) {
+    if (err) {
+      console.error(err)
+      res.status(500).send('SERVER ERROR')
+      return
     }
-}
-
-// promise style:
-const pool2 = new sql.ConnectionPool(config)
-const pool2Connect = pool2.connect()
-
-pool2.on('error', err => {
-    // ... error handler
-})
-
-function runStoredProcedure() {
-    return pool2Connect.then((pool) => {
-        pool.request() // or: new sql.Request(pool2)
-        .input('input_parameter', sql.Int, 10)
-        .output('output_parameter', sql.VarChar(50))
-        .execute('procedure_name', (err, result) => {
-            // ... error checks
-            console.dir(result)
-        })
-    }).catch(err => {
-        // ... error handler
-    })
+    res.status(200).json({ message: 'success' })
+  })
 }
 ```
 
-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 operation.
-
-As of v6.1.0 you can make repeat calls to `ConnectionPool.connect()` and `ConnectionPool.close()` without an error being
-thrown, allowing for the safe use of `mssql.connect().then(...)` throughout your code as well as making multiple calls to
-close when your application is shutting down.
-
-The ability to call `connect()` repeatedly is intended to make pool management easier, however it is still recommended
-to follow the example above where `connect()` is called once and using the original resolved connection promise.
-Repeatedly calling `connect()` when running queries risks running into problems when `close()` is called on the pool.
-
-**ES6 Tagged template literals**
-
-```javascript
-new sql.ConnectionPool(config).connect().then(pool => {
-    return pool.query`select * from mytable where id = ${value}`
-}).then(result => {
-    console.dir(result)
-}).catch(err => {
-    // ... error checks
-})
-```
+### Advanced Pool Management
 
-All values are automatically sanitized against sql injection.
+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:
 
-### Managing connection pools
+* Supporting connections to multiple databases
+* Creation of separate pools for read vs read/write operations
 
-Most applications will only need a single `ConnectionPool` that can be shared throughout the code. To aid the sharing
-of a single pool this library exposes a set of functions to access a single global connection. eg:
+The following code is an example of a custom connection pool implementation.
 
 ```js
-// as part of your application's boot process
-
-const sql = require('mssql')
-const poolPromise = sql.connect()
+// pool-manager.js
+const mssql = require('mssql')
+const pools = new Map();
 
-// during your applications runtime
-
-poolPromise.then(() => {
-  return sql.query('SELECT 1')
-}).then(result => {
-  console.dir(result)
-})
-
-// when your application exits
-poolPromise.then(() => {
-  return sql.close()
-})
+module.exports = {
+ /**
+  * Get or create a pool. If a pool doesn't exist the config must be provided.
+  * If the pool does exist the config is ignored (even if it was different to the one provided
+  * when creating the pool)
+  *
+  * @param {string} name
+  * @param {{}} [config]
+  * @return {Promise.<mssql.ConnectionPool>}
+  */
+ get: (name, config) => {
+  if (!pools.has(name)) {
+   if (!config) {
+    throw new Error('Pool does not exist');
+   }
+   const pool = new mssql.ConnectionPool(config);
+   // automatically remove the pool from the cache if `pool.close()` is called
+   const close = pool.close.bind(pool);
+   pool.close = (...args) => {
+    pools.delete(name);
+    return close(...args);
+   }
+   pools.set(name, pool.connect());
+  }
+  return pools.get(name);
+ },
+ /**
+  * Closes all the pools and removes them from the store
+  *
+  * @return {Promise<mssql.ConnectionPool[]>}
+  */
+ closeAll: () => Promise.all(Array.from(pools.values()).map((connect) => {
+  return connect.then((pool) => pool.close());
+ })),
+};
 ```
 
-If you require multiple pools per application (perhaps you have many DBs you need to connect to or you want a read-only
-pool), then you will need to manage your pools yourself. The best way to do this is to create a shared library file that
-can hold references to the pools for you. For example:
+This file can then be used in your application to create, fetch, and close pools.
 
 ```js
-const sql = require('mssql')
+const { get } = require('./pool-manager')
 
-const pools = {}
-
-// manage a set of pools by name (config will be required to create the pool)
-// a pool will be removed when it is closed
-async function getPool(name, config) {
-  if (!Object.prototype.hasOwnProperty.call(pools, name)) {
-    const pool = new sql.ConnectionPool(config)
-    const close = pool.close.bind(pool)
-    pool.close = (...args) => {
-      delete pools[name]
-      return close(...args)
-    }
-    await pool.connect()
-    pools[name] = pool
-  }
-  return pools[name]
-}
-
-// close all pools
-function closeAll() {
-  return Promise.all(Object.values(pools).map((pool) => {
-    return pool.close()
-  }))
-}
-
-module.exports = {
-  closeAll,
-  getPool
+async function example() {
+  const pool = await get('default')
+  return pool.request().query('SELECT 1')
 }
 ```
 
-You can then use this library file in your code to get a connected pool when you need it:
-
-```js
-const { getPool } = require('./path/to/file')
-
-// run a query
-async function runQuery(query, config) {
-  // pool will always be connected when the promise has resolved - may reject if the connection config is invalid
-  const pool = await getPool('default', config)
-  const result = await pool.request().query(query)
-  return result
-}
-```
+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.
 
 ## Configuration
 
+The following is an example configuration object:
+
 ```javascript
 const config = {
     user: '...',
diff --git a/lib/base/connection-pool.js b/lib/base/connection-pool.js
index 3bdb787..65b5aed 100644
--- a/lib/base/connection-pool.js
+++ b/lib/base/connection-pool.js
@@ -7,7 +7,7 @@ const tarn = require('tarn')
 const { IDS } = require('../utils')
 const ConnectionError = require('../error/connection-error')
 const shared = require('../shared')
-const clone = require('rfdc/default')
+const deepclone = require('rfdc/default')
 
 /**
  * Class ConnectionPool.
@@ -44,7 +44,7 @@ class ConnectionPool extends EventEmitter {
 
     if (typeof config === 'string') {
       try {
-        this.config = this.constructor.parseConnectionString(config)
+        this.config = this.parseConnectionString(config)
       } catch (ex) {
         if (typeof callback === 'function') {
           return setImmediate(callback, ex)
@@ -52,7 +52,7 @@ class ConnectionPool extends EventEmitter {
         throw ex
       }
     } else {
-      this.config = clone(config)
+      this.config = deepclone(config)
     }
 
     // set defaults
@@ -85,11 +85,11 @@ class ConnectionPool extends EventEmitter {
     return this._healthy
   }
 
-  static parseConnectionString (connectionString) {
+  parseConnectionString (connectionString) {
     return this._parseConnectionString(connectionString)
   }
 
-  static _parseConnectionString (connectionString) {
+  _parseConnectionString (connectionString) {
     const parsed = parseSqlConnectionString(connectionString, true, true)
     return Object.entries(parsed).reduce((config, [key, value]) => {
       switch (key) {
diff --git a/lib/connectionstring.js b/lib/connectionstring.js
new file mode 100644
index 0000000..a955d5d
--- /dev/null
+++ b/lib/connectionstring.js
@@ -0,0 +1,289 @@
+'use strict'
+
+const URL = require('url').URL
+
+const IGNORE_KEYS = ['stream']
+const oror = function () {
+  for (let i = 0, l = arguments.length; i < l; i++) {
+    if (arguments[i] !== null && arguments[i] !== undefined) {
+      return arguments[i]
+    }
+  }
+}
+
+const parseConnectionURI = function (uri) {
+  const parsed = new URL(uri)
+  let instance
+  let user
+  let password
+
+  const path = parsed.pathname.substr(1).split('/')
+  if (path.length > 1) {
+    instance = path.shift()
+  }
+
+  if (parsed.username) {
+    const auth = [parsed.username, parsed.password]
+    user = decodeURIComponent(auth.shift())
+    password = decodeURIComponent(auth.join(':'))
+  }
+
+  const port = parsed.port ? `,${parsed.port}` : (instance ? `\\${instance}` : '')
+  const object = {
+    server: `${parsed.hostname}${port}`,
+    uid: user || '',
+    pwd: password || '',
+    database: path[0]
+  }
+
+  if (parsed.searchParams) {
+    parsed.searchParams.forEach((value, key) => {
+      if (key === 'domain') {
+        object.uid = `${value}\\${object.uid}`
+      } else {
+        object[key] = value
+      }
+    })
+  }
+
+  Object.defineProperty(object, 'toString', {
+    value () {
+      const out = []
+      for (const key in this) {
+        if (IGNORE_KEYS.indexOf(key) === -1) {
+          out.push(`${key}={${this[key]}}`)
+        }
+      }
+      return out.join(';')
+    }
+  })
+
+  return object
+}
+
+const parseConnectionString = function (string) {
+  let cursor = 0
+  let parsing = 'name'
+  let param = null
+  let buffer = ''
+  let quotes = null
+  const parsed = {}
+  const original = {}
+
+  Object.defineProperty(parsed, '__original__', { value: original })
+  Object.defineProperty(parsed, 'toString', {
+    value () {
+      const out = []
+      for (const key in this) {
+        if (IGNORE_KEYS.indexOf(key) === -1) {
+          const esc = original[key].escape || ['', '']
+          out.push(`${original[key].name}=${esc[0] || ''}${this[key]}${esc[1] || ''}`)
+        }
+      }
+      return out.join(';')
+    }
+  })
+
+  while (cursor < string.length) {
+    const char = string.charAt(cursor)
+    switch (char) {
+      case '=':
+        if (parsing === 'name') {
+          buffer = buffer.trim()
+          param = buffer.toLowerCase()
+          original[param] = { name: buffer }
+          parsing = 'value'
+          buffer = ''
+        } else {
+          buffer += char
+        }
+        break
+
+      case '\'': case '"':
+        if (parsing === 'value') {
+          if (!buffer.trim().length) {
+            // value is wrapped in qotes
+            original[param].escape = [char, char]
+            quotes = char
+            buffer = ''
+          } else if (quotes) {
+            if (char === quotes) {
+              // found same char as used for wrapping quotes
+              if (char === string.charAt(cursor + 1)) {
+                // escaped quote
+                buffer += char
+                cursor++
+              } else {
+                // end of value
+                parsed[param] = buffer
+                param = null
+                parsing = null
+                buffer = ''
+                quotes = null
+              }
+            } else {
+              buffer += char
+            }
+          } else {
+            buffer += char
+          }
+        } else {
+          throw new Error('Invalid connection string.')
+        }
+        break
+
+      case '{':
+        if (parsing === 'value') {
+          if (buffer.trim().length === 0) {
+            // value is wrapped in qotes
+            original[param].escape = ['{', '}']
+            quotes = '{}'
+            buffer = ''
+          } else {
+            buffer += char
+          }
+        } else {
+          throw new Error('Invalid connection string.')
+        }
+        break
+
+      case '}':
+        if (parsing === 'value') {
+          if (quotes === '{}') {
+            // end of value
+            parsed[param] = buffer
+            param = null
+            parsing = null
+            buffer = ''
+            quotes = null
+          } else {
+            buffer += char
+          }
+        } else {
+          throw new Error('Invalid connection string.')
+        }
+        break
+
+      case ';':
+        if (parsing === 'value') {
+          if (quotes) {
+            buffer += char
+          } else {
+            // end of value
+            parsed[param] = buffer
+            param = null
+            parsing = 'name'
+            buffer = ''
+          }
+        } else {
+          buffer = ''
+          parsing = 'name'
+        }
+        break
+
+      default:
+        buffer += char
+    }
+
+    cursor++
+  }
+
+  if (parsing === 'value') {
+    // end of value
+    parsed[param] = buffer
+  }
+
+  return parsed
+}
+
+const resolveConnectionString = function (string, driver) {
+  const parsed = /^(mssql|tedious|msnodesql|tds):\/\//i.test(string) ? parseConnectionURI(string) : parseConnectionString(string)
+  const stream = (parsed.stream || '').toLowerCase()
+  const encrypt = (parsed.encrypt || '').toLowerCase()
+
+  if (driver === 'msnodesqlv8') {
+    parsed.driver = 'SQL Server Native Client 11.0'
+
+    if (parsed.__original__) {
+      parsed.__original__.driver = { name: 'Driver', escape: ['{', '}'] }
+    }
+
+    return { connectionString: parsed.toString() }
+  }
+
+  let user = parsed.uid || parsed.uid || parsed['user id']
+  let server = parsed.server || parsed.address || parsed.addr || parsed['data source'] || parsed['network address']
+
+  const config = {
+    password: oror(parsed.pwd, parsed.password),
+    database: oror(parsed.database, parsed['initial catalog']),
+    connectionTimeout: oror(parsed.connectionTimeout, parsed.timeout, parsed['connect timeout'], parsed['connection timeout']),
+    requestTimeout: oror(parsed.requestTimeout, parsed['request timeout']),
+    stream: stream === 'true' || stream === 'yes' || stream === '1',
+    options: {
+      readOnlyIntent: parsed.applicationintent && parsed.applicationintent.toLowerCase() === 'readonly',
+      encrypt: encrypt === 'true' || encrypt === 'yes' || encrypt === '1'
+    }
+  }
+
+  if (parsed.trustservercertificate && ['true', '1', 'yes'].includes(parsed.trustservercertificate.toLowerCase())) {
+    Object.assign(config.options, {
+      trustServerCertificate: true
+    })
+  }
+
+  if (parsed.useUTC != null) {
+    const utc = parsed.useUTC.toLowerCase()
+    config.options.useUTC = utc === 'true' || utc === 'yes' || utc === '1'
+  }
+  if (config.connectionTimeout != null) {
+    config.connectionTimeout = parseInt(config.connectionTimeout, 10) * 1000
+  }
+  if (config.requestTimeout != null) {
+    config.requestTimeout = parseInt(config.requestTimeout, 10)
+  }
+
+  if (parsed.multisubnetfailover != null) {
+    config.options.multiSubnetFailover = parsed.multisubnetfailover.toLowerCase() === 'true'
+  }
+
+  if (/^(.*)\\(.*)$/.exec(user)) {
+    config.domain = RegExp.$1
+    user = RegExp.$2
+  }
+
+  if (server) {
+    server = server.trim()
+
+    if (/^np:/i.test(server)) {
+      throw new Error('Connection via Named Pipes is not supported.')
+    }
+
+    if (/^tcp:/i.test(server)) {
+      server = server.substr(4)
+    }
+
+    if (/^(.*)\\(.*)$/.exec(server)) {
+      server = RegExp.$1
+      config.options.instanceName = RegExp.$2
+    }
+
+    if (/^(.*),(.*)$/.exec(server)) {
+      server = RegExp.$1.trim()
+      config.port = parseInt(RegExp.$2.trim(), 10)
+    }
+
+    if (server === '.' || server === '(.)' || server.toLowerCase() === '(localdb)' || server.toLowerCase() === '(local)') {
+      server = 'localhost'
+    }
+  }
+
+  config.user = user
+  config.server = server
+  return config
+}
+
+module.exports = {
+  parse: parseConnectionString,
+  resolve: resolveConnectionString
+}
diff --git a/package-lock.json b/package-lock.json
index 3e357f9..eabc706 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -809,9 +809,9 @@
       }
     },
     "debug": {
-      "version": "4.3.3",
-      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz",
-      "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==",
+      "version": "4.3.2",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz",
+      "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==",
       "requires": {
         "ms": "2.1.2"
       }
@@ -2243,23 +2243,6 @@
           "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
           "dev": true
         },
-        "debug": {
-          "version": "4.3.2",
-          "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz",
-          "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==",
-          "dev": true,
-          "requires": {
-            "ms": "2.1.2"
-          },
-          "dependencies": {
-            "ms": {
-              "version": "2.1.2",
-              "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
-              "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
-              "dev": true
-            }
-          }
-        },
         "escape-string-regexp": {
           "version": "4.0.0",
           "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
@@ -3399,9 +3382,9 @@
       }
     },
     "tarn": {
-      "version": "3.0.2",
-      "resolved": "https://registry.npmjs.org/tarn/-/tarn-3.0.2.tgz",
-      "integrity": "sha512-51LAVKUSZSVfI05vjPESNc5vwqqZpbXCsU+/+wxlOrUjk2SnFTt97v9ZgQrD4YmxYW1Px6w2KjaDitCfkvgxMQ=="
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/tarn/-/tarn-3.0.1.tgz",
+      "integrity": "sha512-6usSlV9KyHsspvwu2duKH+FMUhqJnAh6J5J/4MITl8s94iSUQTLkJggdiewKv4RyARQccnigV48Z+khiuVZDJw=="
     },
     "tedious": {
       "version": "14.0.0",
diff --git a/package.json b/package.json
index 3a0032e..7b830ed 100644
--- a/package.json
+++ b/package.json
@@ -27,9 +27,9 @@
   "license": "MIT",
   "dependencies": {
     "@tediousjs/connection-string": "^0.3.0",
-    "debug": "^4.3.3",
+    "debug": "^4.3.2",
     "rfdc": "^1.3.0",
-    "tarn": "^3.0.2",
+    "tarn": "^3.0.1",
     "tedious": "^14.0.0"
   },
   "devDependencies": {


Test patch:
diff --git a/test/common/unit.js b/test/common/unit.js
index 9c18eec..615219e 100644
--- a/test/common/unit.js
+++ b/test/common/unit.js
@@ -4,9 +4,119 @@
 
 const sql = require('../../')
 const assert = require('assert')
+const cs = require('../../lib/connectionstring')
 const udt = require('../../lib/udt')
 const BasePool = require('../../lib/base/connection-pool')
 
+describe('Connection String', () => {
+  it('Connection String #1', done => {
+    const cfg = cs.resolve('Server=192.168.0.1;Database=testdb;User Id=testuser;Password=testpwd')
+
+    assert.strictEqual(cfg.user, 'testuser')
+    assert.strictEqual(cfg.password, 'testpwd')
+    assert.strictEqual(cfg.database, 'testdb')
+    assert.strictEqual(cfg.server, '192.168.0.1')
+    assert.strictEqual(cfg.port, undefined)
+
+    return done()
+  })
+
+  it('Connection String #2', done => {
+    const cfg = cs.resolve('Server=tcp:192.168.0.1,1433;Database=testdb;User Id=testuser;Password=testpwd;TrustServerCertificate=true')
+
+    assert.strictEqual(cfg.user, 'testuser')
+    assert.strictEqual(cfg.password, 'testpwd')
+    assert.strictEqual(cfg.database, 'testdb')
+    assert.strictEqual(cfg.server, '192.168.0.1')
+    assert.strictEqual(cfg.port, 1433)
+    assert.strictEqual(cfg.options.trustServerCertificate, true)
+
+    return done()
+  })
+
+  it('Connection String #3', done => {
+    const cfg = cs.resolve("Server=192.168.0.1;Database=testdb;User Id={testuser};Password='t;estpwd'", 'msnodesqlv8')
+
+    assert.strictEqual(cfg.connectionString, "Server=192.168.0.1;Database=testdb;User Id={testuser};Password='t;estpwd';Driver={SQL Server Native Client 11.0}")
+
+    return done()
+  })
+
+  it('Connection String #4', done => {
+    const cfg = cs.resolve('mssql://username:password@localhost:1433/database?encrypt=true&stream=true&domain=mydomain&requestTimeout=30000')
+
+    assert.strictEqual(cfg.user, 'username')
+    assert.strictEqual(cfg.password, 'password')
+    assert.strictEqual(cfg.database, 'database')
+    assert.strictEqual(cfg.server, 'localhost')
+    assert.strictEqual(cfg.domain, 'mydomain')
+    assert.strictEqual(cfg.port, 1433)
+    assert.strictEqual(cfg.options.encrypt, true)
+    assert.strictEqual(cfg.requestTimeout, 30000)
+
+    return done()
+  })
+
+  it('Connection String #5', done => {
+    const cfg = cs.resolve('mssql://username:password@localhost/INSTANCE/database?encrypt=true&stream=true&domain=mydomain', 'msnodesqlv8')
+
+    assert.strictEqual(cfg.connectionString, 'server={localhost\\INSTANCE};uid={mydomain\\username};pwd={password};database={database};encrypt={true};driver={SQL Server Native Client 11.0}')
+
+    return done()
+  })
+
+  it('Connection String #6 (multiSubnetFailover)', done => {
+    const cfg = cs.resolve('Server=192.168.0.1;Database=testdb;User Id=testuser;Password=testpwd;MultiSubnetFailover=True')
+
+    assert.strictEqual(cfg.options.multiSubnetFailover, true)
+    assert.strictEqual(cfg.user, 'testuser')
+    assert.strictEqual(cfg.password, 'testpwd')
+    assert.strictEqual(cfg.database, 'testdb')
+    assert.strictEqual(cfg.server, '192.168.0.1')
+    assert.strictEqual(cfg.port, undefined)
+
+    return done()
+  })
+
+  it('Connection String #7 (connection timeout)', done => {
+    const cfg = cs.resolve('Server=192.168.0.1;Database=testdb;User Id=testuser;Password=testpwd;Connection Timeout=30')
+    assert.strictEqual(cfg.user, 'testuser')
+    assert.strictEqual(cfg.password, 'testpwd')
+    assert.strictEqual(cfg.database, 'testdb')
+    assert.strictEqual(cfg.server, '192.168.0.1')
+    assert.strictEqual(cfg.port, undefined)
+    assert.strictEqual(cfg.connectionTimeout, 30000)
+
+    return done()
+  })
+
+  it('Connection String #8 (url encoding)', done => {
+    const cfg = cs.resolve('mssql://username:password%23@localhost:1433/database?encrypt=true')
+    assert.strictEqual(cfg.user, 'username')
+    assert.strictEqual(cfg.password, 'password#')
+    assert.strictEqual(cfg.database, 'database')
+    assert.strictEqual(cfg.server, 'localhost')
+    assert.strictEqual(cfg.port, 1433)
+    assert.strictEqual(cfg.options.encrypt, true)
+
+    return done()
+  })
+
+  it('Pulls out read only ApplicationIntent', done => {
+    const cfg = cs.resolve('Server=192.168.0.1;Database=testdb;User Id=testuser;Password=testpwd;Connection Timeout=30;ApplicationIntent=ReadOnly')
+    assert.strictEqual(cfg.options.readOnlyIntent, true)
+
+    return done()
+  })
+
+  it('Pulls out read write ApplicationIntent', done => {
+    const cfg = cs.resolve('Server=192.168.0.1;Database=testdb;User Id=testuser;Password=testpwd;Connection Timeout=30;ApplicationIntent=ReadWrite')
+    assert.strictEqual(cfg.options.readOnlyIntent, false)
+
+    return done()
+  })
+})
+
 describe('Unit', () => {
   it('table', done => {
     let t = new sql.Table('MyTable')