transaction.js 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. 'use strict'
  2. const debug = require('debug')('mssql:base')
  3. const { EventEmitter } = require('events')
  4. const { IDS } = require('../utils')
  5. const globalConnection = require('../global-connection')
  6. const { TransactionError } = require('../error')
  7. const shared = require('../shared')
  8. const ISOLATION_LEVEL = require('../isolationlevel')
  9. /**
  10. * Class Transaction.
  11. *
  12. * @property {Number} isolationLevel Controls the locking and row versioning behavior of TSQL statements issued by a connection. READ_COMMITTED by default.
  13. * @property {String} name Transaction name. Empty string by default.
  14. *
  15. * @fires Transaction#begin
  16. * @fires Transaction#commit
  17. * @fires Transaction#rollback
  18. */
  19. class Transaction extends EventEmitter {
  20. /**
  21. * Create new Transaction.
  22. *
  23. * @param {Connection} [parent] If ommited, global connection is used instead.
  24. */
  25. constructor (parent) {
  26. super()
  27. IDS.add(this, 'Transaction')
  28. debug('transaction(%d): created', IDS.get(this))
  29. this.parent = parent || globalConnection.pool
  30. this.isolationLevel = ISOLATION_LEVEL.READ_COMMITTED
  31. this.name = ''
  32. }
  33. get connected () {
  34. return this.parent.connected
  35. }
  36. /**
  37. * Acquire connection from connection pool.
  38. *
  39. * @param {Request} request Request.
  40. * @param {ConnectionPool~acquireCallback} [callback] A callback which is called after connection has established, or an error has occurred. If omited, method returns Promise.
  41. * @return {Transaction|Promise}
  42. */
  43. acquire (request, callback) {
  44. if (!this._acquiredConnection) {
  45. setImmediate(callback, new TransactionError('Transaction has not begun. Call begin() first.', 'ENOTBEGUN'))
  46. return this
  47. }
  48. if (this._activeRequest) {
  49. setImmediate(callback, new TransactionError("Can't acquire connection for the request. There is another request in progress.", 'EREQINPROG'))
  50. return this
  51. }
  52. this._activeRequest = request
  53. setImmediate(callback, null, this._acquiredConnection, this._acquiredConfig)
  54. return this
  55. }
  56. /**
  57. * Release connection back to the pool.
  58. *
  59. * @param {Connection} connection Previously acquired connection.
  60. * @return {Transaction}
  61. */
  62. release (connection) {
  63. if (connection === this._acquiredConnection) {
  64. this._activeRequest = null
  65. }
  66. return this
  67. }
  68. /**
  69. * Begin a transaction.
  70. *
  71. * @param {Number} [isolationLevel] Controls the locking and row versioning behavior of TSQL statements issued by a connection.
  72. * @param {basicCallback} [callback] A callback which is called after transaction has began, or an error has occurred. If omited, method returns Promise.
  73. * @return {Transaction|Promise}
  74. */
  75. begin (isolationLevel, callback) {
  76. if (isolationLevel instanceof Function) {
  77. callback = isolationLevel
  78. isolationLevel = undefined
  79. }
  80. if (typeof callback === 'function') {
  81. this._begin(isolationLevel, err => {
  82. if (!err) {
  83. this.emit('begin')
  84. }
  85. callback(err)
  86. })
  87. return this
  88. }
  89. return new shared.Promise((resolve, reject) => {
  90. this._begin(isolationLevel, err => {
  91. if (err) return reject(err)
  92. this.emit('begin')
  93. resolve(this)
  94. })
  95. })
  96. }
  97. /**
  98. * @private
  99. * @param {Number} [isolationLevel]
  100. * @param {basicCallback} [callback]
  101. * @return {Transaction}
  102. */
  103. _begin (isolationLevel, callback) {
  104. if (this._acquiredConnection) {
  105. return setImmediate(callback, new TransactionError('Transaction has already begun.', 'EALREADYBEGUN'))
  106. }
  107. this._aborted = false
  108. this._rollbackRequested = false
  109. if (isolationLevel) {
  110. if (Object.keys(ISOLATION_LEVEL).some(key => {
  111. return ISOLATION_LEVEL[key] === isolationLevel
  112. })) {
  113. this.isolationLevel = isolationLevel
  114. } else {
  115. throw new TransactionError('Invalid isolation level.')
  116. }
  117. }
  118. setImmediate(callback)
  119. }
  120. /**
  121. * Commit a transaction.
  122. *
  123. * @param {basicCallback} [callback] A callback which is called after transaction has commited, or an error has occurred. If omited, method returns Promise.
  124. * @return {Transaction|Promise}
  125. */
  126. commit (callback) {
  127. if (typeof callback === 'function') {
  128. this._commit(err => {
  129. if (!err) {
  130. this.emit('commit')
  131. }
  132. callback(err)
  133. })
  134. return this
  135. }
  136. return new shared.Promise((resolve, reject) => {
  137. this._commit(err => {
  138. if (err) return reject(err)
  139. this.emit('commit')
  140. resolve()
  141. })
  142. })
  143. }
  144. /**
  145. * @private
  146. * @param {basicCallback} [callback]
  147. * @return {Transaction}
  148. */
  149. _commit (callback) {
  150. if (this._aborted) {
  151. return setImmediate(callback, new TransactionError('Transaction has been aborted.', 'EABORT'))
  152. }
  153. if (!this._acquiredConnection) {
  154. return setImmediate(callback, new TransactionError('Transaction has not begun. Call begin() first.', 'ENOTBEGUN'))
  155. }
  156. if (this._activeRequest) {
  157. return setImmediate(callback, new TransactionError("Can't commit transaction. There is a request in progress.", 'EREQINPROG'))
  158. }
  159. setImmediate(callback)
  160. }
  161. /**
  162. * Returns new request using this transaction.
  163. *
  164. * @return {Request}
  165. */
  166. request () {
  167. return new shared.driver.Request(this)
  168. }
  169. /**
  170. * Rollback a transaction.
  171. *
  172. * @param {basicCallback} [callback] A callback which is called after transaction has rolled back, or an error has occurred. If omited, method returns Promise.
  173. * @return {Transaction|Promise}
  174. */
  175. rollback (callback) {
  176. if (typeof callback === 'function') {
  177. this._rollback(err => {
  178. if (!err) {
  179. this.emit('rollback', this._aborted)
  180. }
  181. callback(err)
  182. })
  183. return this
  184. }
  185. return new shared.Promise((resolve, reject) => {
  186. return this._rollback(err => {
  187. if (err) return reject(err)
  188. this.emit('rollback', this._aborted)
  189. resolve()
  190. })
  191. })
  192. }
  193. /**
  194. * @private
  195. * @param {basicCallback} [callback]
  196. * @return {Transaction}
  197. */
  198. _rollback (callback) {
  199. if (this._aborted) {
  200. return setImmediate(callback, new TransactionError('Transaction has been aborted.', 'EABORT'))
  201. }
  202. if (!this._acquiredConnection) {
  203. return setImmediate(callback, new TransactionError('Transaction has not begun. Call begin() first.', 'ENOTBEGUN'))
  204. }
  205. if (this._activeRequest) {
  206. return setImmediate(callback, new TransactionError("Can't rollback transaction. There is a request in progress.", 'EREQINPROG'))
  207. }
  208. this._rollbackRequested = true
  209. setImmediate(callback)
  210. }
  211. }
  212. module.exports = Transaction