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

Commit a64fe80d by Mick Hansen

Merge pull request #1780 from seegno/add-savepoint-support

Add savepoint support for postgres transactions
2 parents 532c05e5 50cc79de
......@@ -1008,7 +1008,18 @@ module.exports = (function() {
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 + ';';
},
......@@ -1019,6 +1030,10 @@ module.exports = (function() {
* @return {String} The generated sql query.
*/
startTransactionQuery: function(options) {
if (options.parent) {
return 'SAVEPOINT ' + this.quoteIdentifier(options.parent.id) + ';';
}
return 'START TRANSACTION;';
},
......@@ -1029,6 +1044,10 @@ module.exports = (function() {
* @return {String} The generated sql query.
*/
commitTransactionQuery: function(options) {
if (options.parent) {
return;
}
return 'COMMIT;';
},
......@@ -1039,6 +1058,10 @@ module.exports = (function() {
* @return {String} The generated sql query.
*/
rollbackTransactionQuery: function(options) {
if (options.parent) {
return 'ROLLBACK TO SAVEPOINT ' + this.quoteIdentifier(options.parent.id) + ';';
}
return 'ROLLBACK;';
},
......
......@@ -27,7 +27,7 @@ module.exports = (function() {
this.sql = sql;
if (this.options.logging !== false) {
this.sequelize.log('Executing (' + this.options.uuid + '): ' + this.sql);
this.sequelize.log('Executing (' + this.client.uuid + '): ' + this.sql);
}
var resultSet = [],
......
......@@ -32,7 +32,7 @@ module.exports = (function() {
, rows = [];
if (this.options.logging !== false) {
this.sequelize.log('Executing (' + this.options.uuid + '): ' + this.sql);
this.sequelize.log('Executing (' + this.client.uuid + '): ' + this.sql);
}
return new Promise(function(resolve, reject) {
......@@ -45,7 +45,7 @@ module.exports = (function() {
query.on('error', function(err) {
receivedError = true;
err.sql = sql;
promise.emit('sql', sql, self.options.uuid);
promise.emit('sql', sql, self.client.uuid);
reject(err);
});
......@@ -54,7 +54,7 @@ module.exports = (function() {
return;
}
promise.emit('sql', self.sql, self.options.uuid);
promise.emit('sql', self.sql, self.client.uuid);
resolve([rows, sql, result]);
});
}).spread(function(rows, sql, result) {
......
......@@ -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;";
},
......
......@@ -30,7 +30,7 @@ module.exports = (function() {
this.sql = sql;
if (this.options.logging !== false) {
this.sequelize.log('Executing (' + this.options.uuid + '): ' + this.sql);
this.sequelize.log('Executing (' + this.database.uuid + '): ' + this.sql);
}
return new Utils.Promise(function(resolve) {
......
......@@ -699,13 +699,22 @@ module.exports = (function() {
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)) {
throw new Error('Unable to set isolation level for a transaction without transaction object!');
}
var sql = this.QueryGenerator.setIsolationLevelQuery(value);
options = Utils._.extend({
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) {
......@@ -714,7 +723,8 @@ module.exports = (function() {
}
options = Utils._.extend({
transaction: transaction
transaction: transaction,
parent: options.transaction
}, options || {});
var sql = this.QueryGenerator.startTransactionQuery(options);
......@@ -727,11 +737,17 @@ module.exports = (function() {
}
options = Utils._.extend({
transaction: transaction
transaction: transaction,
parent: options.transaction
}, options || {});
var sql = this.QueryGenerator.commitTransactionQuery(options);
if (sql) {
return this.sequelize.query(sql, null, options);
} else {
return Utils.Promise.resolve();
}
};
QueryInterface.prototype.rollbackTransaction = function(transaction, options) {
......@@ -740,7 +756,8 @@ module.exports = (function() {
}
options = Utils._.extend({
transaction: transaction
transaction: transaction,
parent: options.transaction
}, options || {});
var sql = this.QueryGenerator.rollbackTransactionQuery(options);
......
......@@ -11,11 +11,11 @@ var Utils = require('./utils')
*/
var Transaction = module.exports = function(sequelize, options) {
this.sequelize = sequelize;
this.id = Utils.generateUUID();
this.options = Utils._.extend({
autocommit: true,
isolationLevel: Transaction.ISOLATION_LEVELS.REPEATABLE_READ
}, options || {});
this.id = this.options.transaction ? this.options.transaction.id : Utils.generateUUID();
};
/**
......@@ -65,11 +65,17 @@ Transaction.LOCK = Transaction.prototype.LOCK = {
* @return {this}
*/
Transaction.prototype.commit = function() {
var self = this;
return this
.sequelize
.getQueryInterface()
.commitTransaction(this, {})
.finally(this.cleanup.bind(this));
.commitTransaction(this, this.options)
.finally(function() {
if (!self.options.transaction) {
self.cleanup();
}
});
};
......@@ -79,19 +85,25 @@ Transaction.prototype.commit = function() {
* @return {this}
*/
Transaction.prototype.rollback = function() {
var self = this;
return this
.sequelize
.getQueryInterface()
.rollbackTransaction(this, {})
.finally(this.cleanup.bind(this));
.rollbackTransaction(this, this.options)
.finally(function() {
if (!self.options.transaction) {
self.cleanup();
}
});
};
Transaction.prototype.prepareEnvironment = function() {
var self = this;
return this.sequelize.connectionManager.getConnection({
uuid: self.id
}).then(function (connection) {
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;
}).then(function () {
......@@ -102,11 +114,12 @@ Transaction.prototype.prepareEnvironment = function() {
return self.setAutocommit();
});
};
Transaction.prototype.begin = function() {
return this
.sequelize
.getQueryInterface()
.startTransaction(this, {});
.startTransaction(this, this.options);
};
Transaction.prototype.setAutocommit = function() {
......@@ -120,7 +133,7 @@ Transaction.prototype.setIsolationLevel = function() {
return this
.sequelize
.getQueryInterface()
.setIsolationLevel(this, this.options.isolationLevel);
.setIsolationLevel(this, this.options.isolationLevel, this.options);
};
Transaction.prototype.cleanup = 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!