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

Commit aa0dee2f by Jan Aagaard Meier

Merge pull request #2000 from janmeier/multipleSavepoints

Support for multiple savepoints
2 parents 6b257be0 b63d4357
...@@ -1032,12 +1032,13 @@ module.exports = (function() { ...@@ -1032,12 +1032,13 @@ module.exports = (function() {
/** /**
* Returns a query that starts a transaction. * Returns a query that starts a transaction.
* *
* @param {Transaction} transaction
* @param {Object} options An object with options. * @param {Object} options An object with options.
* @return {String} The generated sql query. * @return {String} The generated sql query.
*/ */
startTransactionQuery: function(options) { startTransactionQuery: function(transaction, options) {
if (options.parent) { if (options.parent) {
return 'SAVEPOINT ' + this.quoteIdentifier(options.parent.id) + ';'; return 'SAVEPOINT ' + this.quoteIdentifier(transaction.name) + ';';
} }
return 'START TRANSACTION;'; return 'START TRANSACTION;';
...@@ -1060,12 +1061,13 @@ module.exports = (function() { ...@@ -1060,12 +1061,13 @@ module.exports = (function() {
/** /**
* Returns a query that rollbacks a transaction. * Returns a query that rollbacks a transaction.
* *
* @param {Transaction} transaction
* @param {Object} options An object with options. * @param {Object} options An object with options.
* @return {String} The generated sql query. * @return {String} The generated sql query.
*/ */
rollbackTransactionQuery: function(options) { rollbackTransactionQuery: function(transaction, options) {
if (options.parent) { if (options.parent) {
return 'ROLLBACK TO SAVEPOINT ' + this.quoteIdentifier(options.parent.id) + ';'; return 'ROLLBACK TO SAVEPOINT ' + this.quoteIdentifier(transaction.name) + ';';
} }
return 'ROLLBACK;'; return 'ROLLBACK;';
......
...@@ -374,9 +374,9 @@ module.exports = (function() { ...@@ -374,9 +374,9 @@ module.exports = (function() {
}); });
}, },
startTransactionQuery: function(options) { startTransactionQuery: function(transaction, options) {
if (options.parent) { if (options.parent) {
return 'SAVEPOINT ' + this.quoteIdentifier(options.parent.id) + ';'; return 'SAVEPOINT ' + this.quoteIdentifier(transaction.name) + ';';
} }
return "BEGIN TRANSACTION;"; return "BEGIN TRANSACTION;";
......
...@@ -286,7 +286,7 @@ module.exports = (function() { ...@@ -286,7 +286,7 @@ module.exports = (function() {
} }
var sql = this.QueryGenerator.describeTableQuery(tableName, schema, schemaDelimiter); var sql = this.QueryGenerator.describeTableQuery(tableName, schema, schemaDelimiter);
return this.sequelize.query(sql, null, { raw: true }).then(function(data) { return this.sequelize.query(sql, null, { raw: true }).then(function(data) {
// If no data is returned from the query, then the table name may be wrong. // If no data is returned from the query, then the table name may be wrong.
// Query generators that use information_schema for retrieving table info will just return an empty result set, // Query generators that use information_schema for retrieving table info will just return an empty result set,
...@@ -729,7 +729,7 @@ module.exports = (function() { ...@@ -729,7 +729,7 @@ module.exports = (function() {
parent: options.transaction parent: options.transaction
}, options || {}); }, options || {});
var sql = this.QueryGenerator.startTransactionQuery(options); var sql = this.QueryGenerator.startTransactionQuery(transaction, options);
return this.sequelize.query(sql, null, options); return this.sequelize.query(sql, null, options);
}; };
...@@ -762,7 +762,7 @@ module.exports = (function() { ...@@ -762,7 +762,7 @@ module.exports = (function() {
parent: options.transaction parent: options.transaction
}, options || {}); }, options || {});
var sql = this.QueryGenerator.rollbackTransactionQuery(options); var sql = this.QueryGenerator.rollbackTransactionQuery(transaction, options);
return this.sequelize.query(sql, null, options); return this.sequelize.query(sql, null, options);
}; };
......
...@@ -505,7 +505,7 @@ module.exports = (function() { ...@@ -505,7 +505,7 @@ module.exports = (function() {
* *
* Note,that this is a schema in the [postgres sense of the word](http://www.postgresql.org/docs/9.1/static/ddl-schemas.html), * Note,that this is a schema in the [postgres sense of the word](http://www.postgresql.org/docs/9.1/static/ddl-schemas.html),
* not a database table. In mysql and sqlite, this command will do nothing. * not a database table. In mysql and sqlite, this command will do nothing.
* *
* @see {Model#schema} * @see {Model#schema}
* @param {String} schema Name of the schema * @param {String} schema Name of the schema
* @return {Promise} * @return {Promise}
...@@ -516,7 +516,7 @@ module.exports = (function() { ...@@ -516,7 +516,7 @@ module.exports = (function() {
/** /**
* Show all defined schemas * Show all defined schemas
* *
* Note,that this is a schema in the [postgres sense of the word](http://www.postgresql.org/docs/9.1/static/ddl-schemas.html), * Note,that this is a schema in the [postgres sense of the word](http://www.postgresql.org/docs/9.1/static/ddl-schemas.html),
* not a database table. In mysql and sqlite, this will show all tables. * not a database table. In mysql and sqlite, this will show all tables.
* @return {Promise} * @return {Promise}
...@@ -771,9 +771,7 @@ module.exports = (function() { ...@@ -771,9 +771,7 @@ module.exports = (function() {
var transaction = new Transaction(this, options); var transaction = new Transaction(this, options);
return transaction.prepareEnvironment().then(function() { return transaction.prepareEnvironment().return(transaction);
return transaction;
});
}; };
Sequelize.prototype.log = function() { Sequelize.prototype.log = function() {
......
...@@ -11,11 +11,20 @@ var Utils = require('./utils') ...@@ -11,11 +11,20 @@ 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.savepoints = [];
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(); this.id = this.options.transaction ? this.options.transaction.id : Utils.generateUUID();
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;
} else {
this.id = this.name = Utils.generateUUID();
}
}; };
/** /**
......
...@@ -108,7 +108,7 @@ describe(Support.getTestDialectTeaser("Sequelize"), function () { ...@@ -108,7 +108,7 @@ describe(Support.getTestDialectTeaser("Sequelize"), function () {
} }
done() done()
}) })
}) })
}) })
...@@ -929,6 +929,69 @@ describe(Support.getTestDialectTeaser("Sequelize"), function () { ...@@ -929,6 +929,69 @@ describe(Support.getTestDialectTeaser("Sequelize"), function () {
}) })
}) })
describe('supports rolling back to savepoints', function () {
beforeEach(function () {
this.User = this.sequelizeWithTransaction.define('user', {});
return this.sequelizeWithTransaction.sync({ force: true });
})
it('rolls back to the first savepoint, undoing everything', function () {
return this.sequelizeWithTransaction.transaction().bind(this).then(function(transaction) {
this.transaction = transaction;
return this.sequelizeWithTransaction.transaction({ transaction: transaction });
}).then(function (sp1) {
this.sp1 = sp1;
return this.User.create({}, { transaction: this.transaction });
}).then(function () {
return this.sequelizeWithTransaction.transaction({ transaction: this.transaction });
}).then(function (sp2) {
this.sp2 = sp2;
return this.User.create({}, { transaction: this.transaction });
}).then(function () {
return this.User.findAll({}, { transaction: this.transaction });
}).then(function (users) {
expect(users).to.have.length(2);
return this.sp1.rollback();
}).then(function () {
return this.User.findAll({}, { transaction: this.transaction });
}).then(function (users) {
expect(users).to.have.length(0);
return this.transaction.rollback();
});
});
it('rolls back to the most recent savepoint, only undoing recent changes', function () {
return this.sequelizeWithTransaction.transaction().bind(this).then(function(transaction) {
this.transaction = transaction;
return this.sequelizeWithTransaction.transaction({ transaction: transaction });
}).then(function (sp1) {
this.sp1 = sp1;
return this.User.create({}, { transaction: this.transaction });
}).then(function () {
return this.sequelizeWithTransaction.transaction({ transaction: this.transaction });
}).then(function (sp2) {
this.sp2 = sp2;
return this.User.create({}, { transaction: this.transaction });
}).then(function () {
return this.User.findAll({}, { transaction: this.transaction });
}).then(function (users) {
expect(users).to.have.length(2);
return this.sp2.rollback();
}).then(function () {
return this.User.findAll({}, { transaction: this.transaction });
}).then(function (users) {
expect(users).to.have.length(1);
return this.transaction.rollback();
});
});
});
it('supports rolling back a nested transaction', function(done) { it('supports rolling back a nested transaction', function(done) {
var self = this var self = this
var User = this.sequelize.define('Users', { username: DataTypes.STRING }) var User = this.sequelize.define('Users', { username: DataTypes.STRING })
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!