@jovercao/mssql
Version:
Microsoft SQL Server client for Node.js.
262 lines (218 loc) • 6.92 kB
JavaScript
'use strict'
const debug = require('debug')('mssql:base')
const { EventEmitter } = require('events')
const { IDS } = require('../utils')
const globalConnection = require('../global-connection')
const { TransactionError } = require('../error')
const shared = require('../shared')
const ISOLATION_LEVEL = require('../isolationlevel')
/**
* Class Transaction.
*
* @property {Number} isolationLevel Controls the locking and row versioning behavior of TSQL statements issued by a connection. READ_COMMITTED by default.
* @property {String} name Transaction name. Empty string by default.
*
* @fires Transaction#begin
* @fires Transaction#commit
* @fires Transaction#rollback
*/
class Transaction extends EventEmitter {
/**
* Create new Transaction.
*
* @param {Connection} [parent] If ommited, global connection is used instead.
*/
constructor (parent) {
super()
IDS.add(this, 'Transaction')
debug('transaction(%d): created', IDS.get(this))
this.parent = parent || globalConnection.pool
this.isolationLevel = Transaction.defaultIsolationLevel
this.name = ''
}
get connected () {
return this.parent.connected
}
/**
* Acquire connection from connection pool.
*
* @param {Request} request Request.
* @param {ConnectionPool~acquireCallback} [callback] A callback which is called after connection has established, or an error has occurred. If omited, method returns Promise.
* @return {Transaction|Promise}
*/
acquire (request, callback) {
if (!this._acquiredConnection) {
setImmediate(callback, new TransactionError('Transaction has not begun. Call begin() first.', 'ENOTBEGUN'))
return this
}
if (this._activeRequest) {
setImmediate(callback, new TransactionError("Can't acquire connection for the request. There is another request in progress.", 'EREQINPROG'))
return this
}
this._activeRequest = request
setImmediate(callback, null, this._acquiredConnection, this._acquiredConfig)
return this
}
/**
* Release connection back to the pool.
*
* @param {Connection} connection Previously acquired connection.
* @return {Transaction}
*/
release (connection) {
if (connection === this._acquiredConnection) {
this._activeRequest = null
}
return this
}
/**
* Begin a transaction.
*
* @param {Number} [isolationLevel] Controls the locking and row versioning behavior of TSQL statements issued by a connection.
* @param {basicCallback} [callback] A callback which is called after transaction has began, or an error has occurred. If omited, method returns Promise.
* @return {Transaction|Promise}
*/
begin (isolationLevel, callback) {
if (isolationLevel instanceof Function) {
callback = isolationLevel
isolationLevel = undefined
}
if (typeof callback === 'function') {
this._begin(isolationLevel, err => {
if (!err) {
this.emit('begin')
}
callback(err)
})
return this
}
return new shared.Promise((resolve, reject) => {
this._begin(isolationLevel, err => {
if (err) return reject(err)
this.emit('begin')
resolve(this)
})
})
}
/**
* @private
* @param {Number} [isolationLevel]
* @param {basicCallback} [callback]
* @return {Transaction}
*/
_begin (isolationLevel, callback) {
if (this._acquiredConnection) {
return setImmediate(callback, new TransactionError('Transaction has already begun.', 'EALREADYBEGUN'))
}
this._aborted = false
this._rollbackRequested = false
if (isolationLevel) {
if (Object.keys(ISOLATION_LEVEL).some(key => {
return ISOLATION_LEVEL[key] === isolationLevel
})) {
this.isolationLevel = isolationLevel
} else {
throw new TransactionError('Invalid isolation level.')
}
}
setImmediate(callback)
}
/**
* Commit a transaction.
*
* @param {basicCallback} [callback] A callback which is called after transaction has commited, or an error has occurred. If omited, method returns Promise.
* @return {Transaction|Promise}
*/
commit (callback) {
if (typeof callback === 'function') {
this._commit(err => {
if (!err) {
this.emit('commit')
}
callback(err)
})
return this
}
return new shared.Promise((resolve, reject) => {
this._commit(err => {
if (err) return reject(err)
this.emit('commit')
resolve()
})
})
}
/**
* @private
* @param {basicCallback} [callback]
* @return {Transaction}
*/
_commit (callback) {
if (this._aborted) {
return setImmediate(callback, new TransactionError('Transaction has been aborted.', 'EABORT'))
}
if (!this._acquiredConnection) {
return setImmediate(callback, new TransactionError('Transaction has not begun. Call begin() first.', 'ENOTBEGUN'))
}
if (this._activeRequest) {
return setImmediate(callback, new TransactionError("Can't commit transaction. There is a request in progress.", 'EREQINPROG'))
}
setImmediate(callback)
}
/**
* Returns new request using this transaction.
*
* @return {Request}
*/
request () {
return new shared.driver.Request(this)
}
/**
* Rollback a transaction.
*
* @param {basicCallback} [callback] A callback which is called after transaction has rolled back, or an error has occurred. If omited, method returns Promise.
* @return {Transaction|Promise}
*/
rollback (callback) {
if (typeof callback === 'function') {
this._rollback(err => {
if (!err) {
this.emit('rollback', this._aborted)
}
callback(err)
})
return this
}
return new shared.Promise((resolve, reject) => {
return this._rollback(err => {
if (err) return reject(err)
this.emit('rollback', this._aborted)
resolve()
})
})
}
/**
* @private
* @param {basicCallback} [callback]
* @return {Transaction}
*/
_rollback (callback) {
if (this._aborted) {
return setImmediate(callback, new TransactionError('Transaction has been aborted.', 'EABORT'))
}
if (!this._acquiredConnection) {
return setImmediate(callback, new TransactionError('Transaction has not begun. Call begin() first.', 'ENOTBEGUN'))
}
if (this._activeRequest) {
return setImmediate(callback, new TransactionError("Can't rollback transaction. There is a request in progress.", 'EREQINPROG'))
}
this._rollbackRequested = true
setImmediate(callback)
}
}
/**
* Default isolation level used for any transactions that don't explicitly specify an isolation level.
*
* @type {number}
*/
Transaction.defaultIsolationLevel = ISOLATION_LEVEL.READ_COMMITTED
module.exports = Transaction