connection-pool.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372
  1. 'use strict'
  2. const { EventEmitter } = require('events')
  3. const debug = require('debug')('mssql:base')
  4. const tarn = require('tarn')
  5. const { IDS } = require('../utils')
  6. const ConnectionString = require('../connectionstring')
  7. const ConnectionError = require('../error/connection-error')
  8. const shared = require('../shared')
  9. /**
  10. * Class ConnectionPool.
  11. *
  12. * Internally, each `Connection` instance is a separate pool of TDS connections. Once you create a new `Request`/`Transaction`/`Prepared Statement`, a new TDS connection is acquired from the pool and reserved for desired action. Once the action is complete, connection is released back to the pool.
  13. *
  14. * @property {Boolean} connected If true, connection is established.
  15. * @property {Boolean} connecting If true, connection is being established.
  16. *
  17. * @fires ConnectionPool#connect
  18. * @fires ConnectionPool#close
  19. */
  20. class ConnectionPool extends EventEmitter {
  21. /**
  22. * Create new Connection.
  23. *
  24. * @param {Object|String} config Connection configuration object or connection string.
  25. * @param {basicCallback} [callback] A callback which is called after connection has established, or an error has occurred.
  26. */
  27. constructor (config, callback) {
  28. super()
  29. IDS.add(this, 'ConnectionPool')
  30. debug('pool(%d): created', IDS.get(this))
  31. this._connected = false
  32. this._connecting = false
  33. this._healthy = false
  34. if (typeof config === 'string') {
  35. try {
  36. this.config = ConnectionString.resolve(config, shared.driver.name)
  37. } catch (ex) {
  38. if (typeof callback === 'function') {
  39. return setImmediate(callback, ex)
  40. }
  41. throw ex
  42. }
  43. } else {
  44. this.config = Object.assign({}, config)
  45. }
  46. // set defaults
  47. this.config.port = this.config.port || 1433
  48. this.config.options = this.config.options || {}
  49. this.config.stream = this.config.stream || false
  50. this.config.parseJSON = this.config.parseJSON || false
  51. if (/^(.*)\\(.*)$/.exec(this.config.server)) {
  52. this.config.server = RegExp.$1
  53. this.config.options.instanceName = RegExp.$2
  54. }
  55. if (typeof callback === 'function') {
  56. this.connect(callback)
  57. }
  58. }
  59. get connected () {
  60. return this._connected
  61. }
  62. get connecting () {
  63. return this._connecting
  64. }
  65. get healthy () {
  66. return this._healthy
  67. }
  68. /**
  69. * Acquire connection from this connection pool.
  70. *
  71. * @param {ConnectionPool|Transaction|PreparedStatement} requester Requester.
  72. * @param {acquireCallback} [callback] A callback which is called after connection has been acquired, or an error has occurred. If omited, method returns Promise.
  73. * @return {ConnectionPool|Promise}
  74. */
  75. acquire (requester, callback) {
  76. const acquirePromise = shared.Promise.resolve(this._acquire().promise).catch(err => {
  77. this.emit('error', err)
  78. throw err
  79. })
  80. if (typeof callback === 'function') {
  81. acquirePromise.then(connection => callback(null, connection, this.config)).catch(callback)
  82. return this
  83. }
  84. return acquirePromise
  85. }
  86. _acquire () {
  87. if (!this.pool) {
  88. return shared.Promise.reject(new ConnectionError('Connection not yet open.', 'ENOTOPEN'))
  89. }
  90. return this.pool.acquire()
  91. }
  92. /**
  93. * Release connection back to the pool.
  94. *
  95. * @param {Connection} connection Previously acquired connection.
  96. * @return {ConnectionPool}
  97. */
  98. release (connection) {
  99. debug('connection(%d): released', IDS.get(connection))
  100. if (this.pool) {
  101. this.pool.release(connection)
  102. }
  103. return this
  104. }
  105. /**
  106. * Creates a new connection pool with one active connection. This one initial connection serves as a probe to find out whether the configuration is valid.
  107. *
  108. * @param {basicCallback} [callback] A callback which is called after connection has established, or an error has occurred. If omited, method returns Promise.
  109. * @return {ConnectionPool|Promise}
  110. */
  111. connect (callback) {
  112. if (typeof callback === 'function') {
  113. this._connect(callback)
  114. return this
  115. }
  116. return new shared.Promise((resolve, reject) => {
  117. return this._connect(err => {
  118. if (err) return reject(err)
  119. resolve(this)
  120. })
  121. })
  122. }
  123. /**
  124. * @private
  125. * @param {basicCallback} callback
  126. */
  127. _connect (callback) {
  128. if (this._connected) {
  129. return setImmediate(callback, new ConnectionError('Database is already connected! Call close before connecting to different database.', 'EALREADYCONNECTED'))
  130. }
  131. if (this._connecting) {
  132. return setImmediate(callback, new ConnectionError('Already connecting to database! Call close before connecting to different database.', 'EALREADYCONNECTING'))
  133. }
  134. this._connecting = true
  135. debug('pool(%d): connecting', IDS.get(this))
  136. // create one test connection to check if everything is ok
  137. this._poolCreate().then((connection) => {
  138. debug('pool(%d): connected', IDS.get(this))
  139. this._healthy = true
  140. this._poolDestroy(connection).then(() => {
  141. if (!this._connecting) {
  142. debug('pool(%d): not connecting, exiting silently (was close called before connection established?)', IDS.get(this))
  143. return
  144. }
  145. // prepare pool
  146. this.pool = new tarn.Pool(
  147. Object.assign({
  148. create: () => this._poolCreate()
  149. .then(connection => {
  150. this._healthy = true
  151. return connection
  152. })
  153. .catch(err => {
  154. if (this.pool.numUsed() + this.pool.numFree() <= 0) {
  155. this._healthy = false
  156. }
  157. throw err
  158. }),
  159. validate: this._poolValidate.bind(this),
  160. destroy: this._poolDestroy.bind(this),
  161. max: 10,
  162. min: 0,
  163. idleTimeoutMillis: 30000,
  164. propagateCreateError: true
  165. }, this.config.pool)
  166. )
  167. const self = this
  168. Object.defineProperties(this.pool, {
  169. size: {
  170. get: () => {
  171. const message = 'the `size` property on pool is deprecated, access it directly on the `ConnectionPool`'
  172. self.emit('debug', message)
  173. process.emitWarning(message)
  174. return self.size
  175. }
  176. },
  177. available: {
  178. get: () => {
  179. const message = 'the `available` property on pool is deprecated, access it directly on the `ConnectionPool`'
  180. self.emit('debug', message)
  181. process.emitWarning(message)
  182. return self.available
  183. }
  184. },
  185. pending: {
  186. get: () => {
  187. const message = 'the `pending` property on pool is deprecate, access it directly on the `ConnectionPool`'
  188. self.emit('debug', message)
  189. process.emitWarning(message)
  190. return self.pending
  191. }
  192. },
  193. borrowed: {
  194. get: () => {
  195. const message = 'the `borrowed` property on pool is deprecated, access it directly on the `ConnectionPool`'
  196. self.emit('debug', message)
  197. process.emitWarning(message)
  198. return self.borrowed
  199. }
  200. }
  201. })
  202. this._connecting = false
  203. this._connected = true
  204. callback(null)
  205. })
  206. }).catch(err => {
  207. this._connecting = false
  208. callback(err)
  209. })
  210. }
  211. get size () {
  212. return this.pool.numFree() + this.pool.numUsed() + this.pool.numPendingCreates()
  213. }
  214. get available () {
  215. return this.pool.numFree()
  216. }
  217. get pending () {
  218. return this.pool.numPendingAcquires()
  219. }
  220. get borrowed () {
  221. return this.pool.numUsed()
  222. }
  223. /**
  224. * Close all active connections in the pool.
  225. *
  226. * @param {basicCallback} [callback] A callback which is called after connection has closed, or an error has occurred. If omited, method returns Promise.
  227. * @return {ConnectionPool|Promise}
  228. */
  229. close (callback) {
  230. if (typeof callback === 'function') {
  231. this._close(callback)
  232. return this
  233. }
  234. return new shared.Promise((resolve, reject) => {
  235. this._close(err => {
  236. if (err) return reject(err)
  237. resolve(this)
  238. })
  239. })
  240. }
  241. /**
  242. * @private
  243. * @param {basicCallback} callback
  244. */
  245. _close (callback) {
  246. this._connecting = this._connected = this._healthy = false
  247. if (!this.pool) return setImmediate(callback, null)
  248. this.pool.destroy()
  249. this.pool = null
  250. callback(null)
  251. }
  252. /**
  253. * Returns new request using this connection.
  254. *
  255. * @return {Request}
  256. */
  257. request () {
  258. return new shared.driver.Request(this)
  259. }
  260. /**
  261. * Returns new transaction using this connection.
  262. *
  263. * @return {Transaction}
  264. */
  265. transaction () {
  266. return new shared.driver.Transaction(this)
  267. }
  268. /**
  269. * Creates a new query using this connection from a tagged template string.
  270. *
  271. * @variation 1
  272. * @param {Array} strings Array of string literals.
  273. * @param {...*} keys Values.
  274. * @return {Request}
  275. */
  276. /**
  277. * Execute the SQL command.
  278. *
  279. * @variation 2
  280. * @param {String} command T-SQL command to be executed.
  281. * @param {Request~requestCallback} [callback] A callback which is called after execution has completed, or an error has occurred. If omited, method returns Promise.
  282. * @return {Request|Promise}
  283. */
  284. query () {
  285. if (typeof arguments[0] === 'string') { return new shared.driver.Request(this).query(arguments[0], arguments[1]) }
  286. const values = Array.prototype.slice.call(arguments)
  287. const strings = values.shift()
  288. return new shared.driver.Request(this)._template(strings, values, 'query')
  289. }
  290. /**
  291. * Creates a new batch using this connection from a tagged template string.
  292. *
  293. * @variation 1
  294. * @param {Array} strings Array of string literals.
  295. * @param {...*} keys Values.
  296. * @return {Request}
  297. */
  298. /**
  299. * Execute the SQL command.
  300. *
  301. * @variation 2
  302. * @param {String} command T-SQL command to be executed.
  303. * @param {Request~requestCallback} [callback] A callback which is called after execution has completed, or an error has occurred. If omited, method returns Promise.
  304. * @return {Request|Promise}
  305. */
  306. batch () {
  307. if (typeof arguments[0] === 'string') { return new shared.driver.Request(this).batch(arguments[0], arguments[1]) }
  308. const values = Array.prototype.slice.call(arguments)
  309. const strings = values.shift()
  310. return new shared.driver.Request(this)._template(strings, values, 'batch')
  311. }
  312. }
  313. module.exports = ConnectionPool