229 lines
5.7 KiB
JavaScript
229 lines
5.7 KiB
JavaScript
|
|
'use strict';
|
||
|
|
|
||
|
|
var Utils = require('./utils')
|
||
|
|
, uuid = require('node-uuid');
|
||
|
|
|
||
|
|
/**
|
||
|
|
* The transaction object is used to identify a running transaction. It is created by calling `Sequelize.transaction()`.
|
||
|
|
*
|
||
|
|
* To run a query under a transaction, you should pass the transaction in the options object.
|
||
|
|
* @class Transaction
|
||
|
|
* @constructor
|
||
|
|
*
|
||
|
|
* @param {Sequelize} sequelize A configured sequelize Instance
|
||
|
|
* @param {Object} options An object with options
|
||
|
|
* @param {Boolean} options.autocommit=true Sets the autocommit property of the transaction.
|
||
|
|
* @param {String} options.isolationLevel=true Sets the isolation level of the transaction.
|
||
|
|
* @param {String} options.deferrable Sets the constraints to be deferred or immediately checked.
|
||
|
|
*/
|
||
|
|
var Transaction = module.exports = function(sequelize, options) {
|
||
|
|
this.sequelize = sequelize;
|
||
|
|
this.savepoints = [];
|
||
|
|
this.options = Utils._.extend({
|
||
|
|
autocommit: true,
|
||
|
|
isolationLevel: sequelize.options.isolationLevel
|
||
|
|
}, options || {});
|
||
|
|
this.id = this.options.transaction ? this.options.transaction.id : uuid.v4();
|
||
|
|
|
||
|
|
if (this.options.transaction) {
|
||
|
|
this.id = this.options.transaction.id;
|
||
|
|
this.options.transaction.savepoints.push(this);
|
||
|
|
this.name = this.id + '-savepoint-' + this.options.transaction.savepoints.length;
|
||
|
|
this.parent = this.options.transaction;
|
||
|
|
} else {
|
||
|
|
this.id = this.name = uuid.v4();
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Isolations levels can be set per-transaction by passing `options.isolationLevel` to `sequelize.transaction`.
|
||
|
|
* Default to `REPEATABLE_READ` but you can override the default isolation level by passing `options.isolationLevel` in `new Sequelize`.
|
||
|
|
*
|
||
|
|
* The possible isolations levels to use when starting a transaction:
|
||
|
|
*
|
||
|
|
* ```js
|
||
|
|
* {
|
||
|
|
* READ_UNCOMMITTED: "READ UNCOMMITTED",
|
||
|
|
* READ_COMMITTED: "READ COMMITTED",
|
||
|
|
* REPEATABLE_READ: "REPEATABLE READ",
|
||
|
|
* SERIALIZABLE: "SERIALIZABLE"
|
||
|
|
* }
|
||
|
|
* ```
|
||
|
|
*
|
||
|
|
* Pass in the desired level as the first argument:
|
||
|
|
*
|
||
|
|
* ```js
|
||
|
|
* return sequelize.transaction({
|
||
|
|
* isolationLevel: Sequelize.Transaction.SERIALIZABLE
|
||
|
|
* }, function (t) {
|
||
|
|
*
|
||
|
|
* // your transactions
|
||
|
|
*
|
||
|
|
* }).then(function(result) {
|
||
|
|
* // transaction has been committed. Do something after the commit if required.
|
||
|
|
* }).catch(function(err) {
|
||
|
|
* // do something with the err.
|
||
|
|
* });
|
||
|
|
* ```
|
||
|
|
*
|
||
|
|
* @property ISOLATION_LEVELS
|
||
|
|
*/
|
||
|
|
Transaction.ISOLATION_LEVELS = {
|
||
|
|
READ_UNCOMMITTED: 'READ UNCOMMITTED',
|
||
|
|
READ_COMMITTED: 'READ COMMITTED',
|
||
|
|
REPEATABLE_READ: 'REPEATABLE READ',
|
||
|
|
SERIALIZABLE: 'SERIALIZABLE'
|
||
|
|
};
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Possible options for row locking. Used in conjuction with `find` calls:
|
||
|
|
*
|
||
|
|
* ```js
|
||
|
|
* t1 // is a transaction
|
||
|
|
* t1.LOCK.UPDATE,
|
||
|
|
* t1.LOCK.SHARE,
|
||
|
|
* t1.LOCK.KEY_SHARE, // Postgres 9.3+ only
|
||
|
|
* t1.LOCK.NO_KEY_UPDATE // Postgres 9.3+ only
|
||
|
|
* ```
|
||
|
|
*
|
||
|
|
* Usage:
|
||
|
|
* ```js
|
||
|
|
* t1 // is a transaction
|
||
|
|
* Model.findAll({
|
||
|
|
* where: ...,
|
||
|
|
* transaction: t1,
|
||
|
|
* lock: t1.LOCK...
|
||
|
|
* });
|
||
|
|
* ```
|
||
|
|
*
|
||
|
|
* Postgres also supports specific locks while eager loading by using OF:
|
||
|
|
* ```js
|
||
|
|
* UserModel.findAll({
|
||
|
|
* where: ...,
|
||
|
|
* include: [TaskModel, ...],
|
||
|
|
* transaction: t1,
|
||
|
|
* lock: {
|
||
|
|
* level: t1.LOCK...,
|
||
|
|
* of: UserModel
|
||
|
|
* }
|
||
|
|
* });
|
||
|
|
* ```
|
||
|
|
* UserModel will be locked but TaskModel won't!
|
||
|
|
*
|
||
|
|
* @property LOCK
|
||
|
|
*/
|
||
|
|
Transaction.LOCK = Transaction.prototype.LOCK = {
|
||
|
|
UPDATE: 'UPDATE',
|
||
|
|
SHARE: 'SHARE',
|
||
|
|
KEY_SHARE: 'KEY SHARE',
|
||
|
|
NO_KEY_UPDATE: 'NO KEY UPDATE'
|
||
|
|
};
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Commit the transaction
|
||
|
|
*
|
||
|
|
* @return {this}
|
||
|
|
*/
|
||
|
|
Transaction.prototype.commit = function() {
|
||
|
|
var self = this;
|
||
|
|
|
||
|
|
return this
|
||
|
|
.sequelize
|
||
|
|
.getQueryInterface()
|
||
|
|
.commitTransaction(this, this.options)
|
||
|
|
.finally(function() {
|
||
|
|
self.finished = 'commit';
|
||
|
|
if (!self.options.transaction) {
|
||
|
|
self.cleanup();
|
||
|
|
}
|
||
|
|
});
|
||
|
|
};
|
||
|
|
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Rollback (abort) the transaction
|
||
|
|
*
|
||
|
|
* @return {this}
|
||
|
|
*/
|
||
|
|
Transaction.prototype.rollback = function() {
|
||
|
|
var self = this;
|
||
|
|
|
||
|
|
return this
|
||
|
|
.sequelize
|
||
|
|
.getQueryInterface()
|
||
|
|
.rollbackTransaction(this, this.options)
|
||
|
|
.finally(function() {
|
||
|
|
self.finished = 'rollback';
|
||
|
|
if (!self.options.transaction) {
|
||
|
|
self.cleanup();
|
||
|
|
}
|
||
|
|
});
|
||
|
|
};
|
||
|
|
|
||
|
|
Transaction.prototype.prepareEnvironment = function() {
|
||
|
|
var self = this;
|
||
|
|
|
||
|
|
return Utils.Promise.resolve(
|
||
|
|
self.options.transaction ? self.options.transaction.connection : self.sequelize.connectionManager.getConnection({ uuid: self.id })
|
||
|
|
).then(function (connection) {
|
||
|
|
self.connection = connection;
|
||
|
|
self.connection.uuid = self.id;
|
||
|
|
|
||
|
|
if (self.sequelize.constructor.cls) {
|
||
|
|
self.sequelize.constructor.cls.set('transaction', self);
|
||
|
|
}
|
||
|
|
}).then(function () {
|
||
|
|
return self.begin();
|
||
|
|
}).then(function () {
|
||
|
|
return self.setDeferrable();
|
||
|
|
}).then(function () {
|
||
|
|
return self.setIsolationLevel();
|
||
|
|
}).then(function () {
|
||
|
|
return self.setAutocommit();
|
||
|
|
});
|
||
|
|
};
|
||
|
|
|
||
|
|
Transaction.prototype.begin = function() {
|
||
|
|
return this
|
||
|
|
.sequelize
|
||
|
|
.getQueryInterface()
|
||
|
|
.startTransaction(this, this.options);
|
||
|
|
};
|
||
|
|
|
||
|
|
Transaction.prototype.setDeferrable = function () {
|
||
|
|
if (this.options.deferrable) {
|
||
|
|
return this
|
||
|
|
.sequelize
|
||
|
|
.getQueryInterface()
|
||
|
|
.deferConstraints(this, this.options);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
Transaction.prototype.setAutocommit = function() {
|
||
|
|
return this
|
||
|
|
.sequelize
|
||
|
|
.getQueryInterface()
|
||
|
|
.setAutocommit(this, this.options.autocommit, this.options);
|
||
|
|
};
|
||
|
|
|
||
|
|
Transaction.prototype.setIsolationLevel = function() {
|
||
|
|
return this
|
||
|
|
.sequelize
|
||
|
|
.getQueryInterface()
|
||
|
|
.setIsolationLevel(this, this.options.isolationLevel, this.options);
|
||
|
|
};
|
||
|
|
|
||
|
|
Transaction.prototype.cleanup = function() {
|
||
|
|
var cls = this.sequelize.constructor.cls;
|
||
|
|
|
||
|
|
this.connection.uuid = undefined;
|
||
|
|
|
||
|
|
if (cls) {
|
||
|
|
if (cls.get('transaction') === this) {
|
||
|
|
cls.set('transaction', null);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return this.sequelize.connectionManager.releaseConnection(this.connection);
|
||
|
|
};
|