const Transaction = require('../../execution/transaction'); const debug = require('debug')('knex:tx'); class Transaction_MSSQL extends Transaction { begin(/** @type {import('tedious').Connection} */ conn) { debug('transaction::begin id=%s', this.txid); return new Promise((resolve, reject) => { conn.beginTransaction( (err) => { if (err) { debug( 'transaction::begin error id=%s message=%s', this.txid, err.message ); return reject(err); } resolve(); }, this.outerTx ? this.txid : undefined, nameToIsolationLevelEnum(this.isolationLevel) ); }).then(this._resolver, this._rejecter); } savepoint(conn) { debug('transaction::savepoint id=%s', this.txid); return new Promise((resolve, reject) => { conn.saveTransaction( (err) => { if (err) { debug( 'transaction::savepoint id=%s message=%s', this.txid, err.message ); return reject(err); } this.trxClient.emit('query', { __knexUid: this.trxClient.__knexUid, __knexTxId: this.trxClient.__knexTxId, autogenerated: true, sql: this.outerTx ? `SAVE TRANSACTION [${this.txid}]` : `SAVE TRANSACTION`, }); resolve(); }, this.outerTx ? this.txid : undefined ); }); } commit(conn, value) { debug('transaction::commit id=%s', this.txid); return new Promise((resolve, reject) => { conn.commitTransaction( (err) => { if (err) { debug( 'transaction::commit error id=%s message=%s', this.txid, err.message ); return reject(err); } this._completed = true; resolve(value); }, this.outerTx ? this.txid : undefined ); }).then(() => this._resolver(value), this._rejecter); } release(conn, value) { return this._resolver(value); } rollback(conn, error) { this._completed = true; debug('transaction::rollback id=%s', this.txid); return new Promise((_resolve, reject) => { if (!conn.inTransaction) { return reject( error || new Error('Transaction rejected with non-error: undefined') ); } if (conn.state.name !== 'LoggedIn') { return reject( new Error( "Can't rollback transaction. There is a request in progress" ) ); } conn.rollbackTransaction( (err) => { if (err) { debug( 'transaction::rollback error id=%s message=%s', this.txid, err.message ); } reject( err || error || new Error('Transaction rejected with non-error: undefined') ); }, this.outerTx ? this.txid : undefined ); }).catch((err) => { if (!error && this.doNotRejectOnRollback) { this._resolver(); return; } if (error) { try { err.originalError = error; } catch (_err) { // This is to handle https://github.com/knex/knex/issues/4128 } } this._rejecter(err); }); } rollbackTo(conn, error) { return this.rollback(conn, error).then( () => void this.trxClient.emit('query', { __knexUid: this.trxClient.__knexUid, __knexTxId: this.trxClient.__knexTxId, autogenerated: true, sql: `ROLLBACK TRANSACTION`, }) ); } } module.exports = Transaction_MSSQL; function nameToIsolationLevelEnum(level) { if (!level) return; level = level.toUpperCase().replace(' ', '_'); const knownEnum = isolationEnum[level]; if (!knownEnum) { throw new Error( `Unknown Isolation level, was expecting one of: ${JSON.stringify( humanReadableKeys )}` ); } return knownEnum; } // Based on: https://github.com/tediousjs/node-mssql/blob/master/lib/isolationlevel.js const isolationEnum = { READ_UNCOMMITTED: 0x01, READ_COMMITTED: 0x02, REPEATABLE_READ: 0x03, SERIALIZABLE: 0x04, SNAPSHOT: 0x05, }; const humanReadableKeys = Object.keys(isolationEnum).map((key) => key.toLowerCase().replace('_', ' ') );