不要怂,就是干,撸起袖子干!

Commit 50cc79de by Nuno Sousa

Add savepoint support for transactions

1 parent be208e8c
...@@ -1008,7 +1008,18 @@ module.exports = (function() { ...@@ -1008,7 +1008,18 @@ module.exports = (function() {
return 'SET autocommit = ' + (!!value ? 1 : 0) + ';'; return 'SET autocommit = ' + (!!value ? 1 : 0) + ';';
}, },
setIsolationLevelQuery: function(value) { /**
* Returns a query that sets the transaction isolation level.
*
* @param {String} value The isolation level.
* @param {Object} options An object with options.
* @return {String} The generated sql query.
*/
setIsolationLevelQuery: function(value, options) {
if (options.parent) {
return;
}
return 'SET SESSION TRANSACTION ISOLATION LEVEL ' + value + ';'; return 'SET SESSION TRANSACTION ISOLATION LEVEL ' + value + ';';
}, },
...@@ -1019,6 +1030,10 @@ module.exports = (function() { ...@@ -1019,6 +1030,10 @@ module.exports = (function() {
* @return {String} The generated sql query. * @return {String} The generated sql query.
*/ */
startTransactionQuery: function(options) { startTransactionQuery: function(options) {
if (options.parent) {
return 'SAVEPOINT ' + this.quoteIdentifier(options.parent.id) + ';';
}
return 'START TRANSACTION;'; return 'START TRANSACTION;';
}, },
...@@ -1029,6 +1044,10 @@ module.exports = (function() { ...@@ -1029,6 +1044,10 @@ module.exports = (function() {
* @return {String} The generated sql query. * @return {String} The generated sql query.
*/ */
commitTransactionQuery: function(options) { commitTransactionQuery: function(options) {
if (options.parent) {
return;
}
return 'COMMIT;'; return 'COMMIT;';
}, },
...@@ -1039,6 +1058,10 @@ module.exports = (function() { ...@@ -1039,6 +1058,10 @@ module.exports = (function() {
* @return {String} The generated sql query. * @return {String} The generated sql query.
*/ */
rollbackTransactionQuery: function(options) { rollbackTransactionQuery: function(options) {
if (options.parent) {
return 'ROLLBACK TO SAVEPOINT ' + this.quoteIdentifier(options.parent.id) + ';';
}
return 'ROLLBACK;'; return 'ROLLBACK;';
}, },
......
...@@ -372,7 +372,11 @@ module.exports = (function() { ...@@ -372,7 +372,11 @@ module.exports = (function() {
}); });
}, },
startTransactionQuery: function() { startTransactionQuery: function(options) {
if (options.parent) {
return 'SAVEPOINT ' + this.quoteIdentifier(options.parent.id) + ';';
}
return "BEGIN TRANSACTION;"; return "BEGIN TRANSACTION;";
}, },
......
...@@ -699,13 +699,22 @@ module.exports = (function() { ...@@ -699,13 +699,22 @@ module.exports = (function() {
return this.sequelize.query(sql, null, { transaction: transaction}); return this.sequelize.query(sql, null, { transaction: transaction});
}; };
QueryInterface.prototype.setIsolationLevel = function(transaction, value) { QueryInterface.prototype.setIsolationLevel = function(transaction, value, options) {
if (!transaction || !(transaction instanceof Transaction)) { if (!transaction || !(transaction instanceof Transaction)) {
throw new Error('Unable to set isolation level for a transaction without transaction object!'); throw new Error('Unable to set isolation level for a transaction without transaction object!');
} }
var sql = this.QueryGenerator.setIsolationLevelQuery(value); options = Utils._.extend({
return this.sequelize.query(sql, null, { transaction: transaction }); parent: options.transaction
}, options || {});
var sql = this.QueryGenerator.setIsolationLevelQuery(value, options);
if (sql) {
return this.sequelize.query(sql, null, { transaction: transaction });
} else {
return Utils.Promise.resolve();
}
}; };
QueryInterface.prototype.startTransaction = function(transaction, options) { QueryInterface.prototype.startTransaction = function(transaction, options) {
...@@ -714,7 +723,8 @@ module.exports = (function() { ...@@ -714,7 +723,8 @@ module.exports = (function() {
} }
options = Utils._.extend({ options = Utils._.extend({
transaction: transaction transaction: transaction,
parent: options.transaction
}, options || {}); }, options || {});
var sql = this.QueryGenerator.startTransactionQuery(options); var sql = this.QueryGenerator.startTransactionQuery(options);
...@@ -727,11 +737,17 @@ module.exports = (function() { ...@@ -727,11 +737,17 @@ module.exports = (function() {
} }
options = Utils._.extend({ options = Utils._.extend({
transaction: transaction transaction: transaction,
parent: options.transaction
}, options || {}); }, options || {});
var sql = this.QueryGenerator.commitTransactionQuery(options); var sql = this.QueryGenerator.commitTransactionQuery(options);
return this.sequelize.query(sql, null, options);
if (sql) {
return this.sequelize.query(sql, null, options);
} else {
return Utils.Promise.resolve();
}
}; };
QueryInterface.prototype.rollbackTransaction = function(transaction, options) { QueryInterface.prototype.rollbackTransaction = function(transaction, options) {
...@@ -740,7 +756,8 @@ module.exports = (function() { ...@@ -740,7 +756,8 @@ module.exports = (function() {
} }
options = Utils._.extend({ options = Utils._.extend({
transaction: transaction transaction: transaction,
parent: options.transaction
}, options || {}); }, options || {});
var sql = this.QueryGenerator.rollbackTransactionQuery(options); var sql = this.QueryGenerator.rollbackTransactionQuery(options);
......
...@@ -11,11 +11,11 @@ var Utils = require('./utils') ...@@ -11,11 +11,11 @@ var Utils = require('./utils')
*/ */
var Transaction = module.exports = function(sequelize, options) { var Transaction = module.exports = function(sequelize, options) {
this.sequelize = sequelize; this.sequelize = sequelize;
this.id = Utils.generateUUID();
this.options = Utils._.extend({ this.options = Utils._.extend({
autocommit: true, autocommit: true,
isolationLevel: Transaction.ISOLATION_LEVELS.REPEATABLE_READ isolationLevel: Transaction.ISOLATION_LEVELS.REPEATABLE_READ
}, options || {}); }, options || {});
this.id = this.options.transaction ? this.options.transaction.id : Utils.generateUUID();
}; };
/** /**
...@@ -65,11 +65,17 @@ Transaction.LOCK = Transaction.prototype.LOCK = { ...@@ -65,11 +65,17 @@ Transaction.LOCK = Transaction.prototype.LOCK = {
* @return {this} * @return {this}
*/ */
Transaction.prototype.commit = function() { Transaction.prototype.commit = function() {
var self = this;
return this return this
.sequelize .sequelize
.getQueryInterface() .getQueryInterface()
.commitTransaction(this, {}) .commitTransaction(this, this.options)
.finally(this.cleanup.bind(this)); .finally(function() {
if (!self.options.transaction) {
self.cleanup();
}
});
}; };
...@@ -79,19 +85,25 @@ Transaction.prototype.commit = function() { ...@@ -79,19 +85,25 @@ Transaction.prototype.commit = function() {
* @return {this} * @return {this}
*/ */
Transaction.prototype.rollback = function() { Transaction.prototype.rollback = function() {
var self = this;
return this return this
.sequelize .sequelize
.getQueryInterface() .getQueryInterface()
.rollbackTransaction(this, {}) .rollbackTransaction(this, this.options)
.finally(this.cleanup.bind(this)); .finally(function() {
if (!self.options.transaction) {
self.cleanup();
}
});
}; };
Transaction.prototype.prepareEnvironment = function() { Transaction.prototype.prepareEnvironment = function() {
var self = this; var self = this;
return this.sequelize.connectionManager.getConnection({ return Utils.Promise.resolve(
uuid: self.id self.options.transaction ? self.options.transaction.connection : self.sequelize.connectionManager.getConnection({ uuid: self.id })
}).then(function (connection) { ).then(function (connection) {
self.connection = connection; self.connection = connection;
self.connection.uuid = self.id; self.connection.uuid = self.id;
}).then(function () { }).then(function () {
...@@ -102,11 +114,12 @@ Transaction.prototype.prepareEnvironment = function() { ...@@ -102,11 +114,12 @@ Transaction.prototype.prepareEnvironment = function() {
return self.setAutocommit(); return self.setAutocommit();
}); });
}; };
Transaction.prototype.begin = function() { Transaction.prototype.begin = function() {
return this return this
.sequelize .sequelize
.getQueryInterface() .getQueryInterface()
.startTransaction(this, {}); .startTransaction(this, this.options);
}; };
Transaction.prototype.setAutocommit = function() { Transaction.prototype.setAutocommit = function() {
...@@ -120,7 +133,7 @@ Transaction.prototype.setIsolationLevel = function() { ...@@ -120,7 +133,7 @@ Transaction.prototype.setIsolationLevel = function() {
return this return this
.sequelize .sequelize
.getQueryInterface() .getQueryInterface()
.setIsolationLevel(this, this.options.isolationLevel); .setIsolationLevel(this, this.options.isolationLevel, this.options);
}; };
Transaction.prototype.cleanup = function() { Transaction.prototype.cleanup = function() {
......
...@@ -888,6 +888,77 @@ describe(Support.getTestDialectTeaser("Sequelize"), function () { ...@@ -888,6 +888,77 @@ describe(Support.getTestDialectTeaser("Sequelize"), function () {
}) })
} }
it('supports nested transactions using savepoints', function(done) {
var self = this
var User = this.sequelize.define('Users', { username: DataTypes.STRING })
User.sync({ force: true }).success(function() {
self.sequelizeWithTransaction.transaction(function(t1) {
User.create({ username: 'foo' }, { transaction: t1 }).success(function(user) {
self.sequelizeWithTransaction.transaction({ transaction: t1 }, function(t2) {
user.updateAttributes({ username: 'bar' }, { transaction: t2 }).success(function() {
t2.commit().then(function() {
user.reload({ transaction: t1 }).success(function(newUser) {
expect(newUser.username).to.equal('bar')
t1.commit().then(function() {
done()
});
})
})
})
})
})
})
})
})
it('supports rolling back a nested transaction', function(done) {
var self = this
var User = this.sequelize.define('Users', { username: DataTypes.STRING })
User.sync({ force: true }).success(function() {
self.sequelizeWithTransaction.transaction(function(t1) {
User.create({ username: 'foo' }, { transaction: t1 }).success(function(user) {
self.sequelizeWithTransaction.transaction({ transaction: t1 }, function(t2) {
user.updateAttributes({ username: 'bar' }, { transaction: t2 }).success(function() {
t2.rollback().then(function() {
user.reload({ transaction: t2 }).success(function(newUser) {
expect(newUser.username).to.equal('foo')
t1.commit().then(function() {
done()
});
})
})
})
})
})
})
})
})
it('supports rolling back outermost transaction', function(done) {
var self = this
var User = this.sequelize.define('Users', { username: DataTypes.STRING })
User.sync({ force: true }).success(function() {
self.sequelizeWithTransaction.transaction(function(t1) {
User.create({ username: 'foo' }, { transaction: t1 }).success(function(user) {
self.sequelizeWithTransaction.transaction({ transaction: t1 }, function(t2) {
user.updateAttributes({ username: 'bar' }, { transaction: t2 }).success(function() {
t1.rollback().then(function() {
User.findAll().success(function(users) {
expect(users.length).to.equal(0);
done()
})
})
})
})
})
})
})
})
}) })
}) })
}) })
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!