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

Commit 2d480795 by Jan Aagaard Meier

Support for FOR UPDATE and FOR SHARE queries

1 parent 35f34b20
...@@ -956,6 +956,14 @@ module.exports = (function() { ...@@ -956,6 +956,14 @@ module.exports = (function() {
query = mainQueryItems.join('') query = mainQueryItems.join('')
} }
if (this._dialect.supports.rowLocking) {
if (options.forUpdate) {
query += ' ' + this._dialect.supports.forUpdate
} else if (options.forShare) {
query += ' ' + this._dialect.supports.forShare
}
}
query += ";"; query += ";";
return query return query
......
...@@ -7,7 +7,10 @@ var MysqlDialect = function(sequelize) { ...@@ -7,7 +7,10 @@ var MysqlDialect = function(sequelize) {
MysqlDialect.prototype.supports = _.defaults({ MysqlDialect.prototype.supports = _.defaults({
'VALUES ()': true, 'VALUES ()': true,
'LIMIT ON UPDATE':true 'LIMIT ON UPDATE':true,
rowLocking: true,
forUpdate: 'FOR UPDATE',
forShare: 'LOCK IN SHARE MODE'
}, Abstract.prototype.supports) }, Abstract.prototype.supports)
module.exports = MysqlDialect module.exports = MysqlDialect
...@@ -8,7 +8,10 @@ var PostgresDialect = function(sequelize) { ...@@ -8,7 +8,10 @@ var PostgresDialect = function(sequelize) {
PostgresDialect.prototype.supports = _.defaults({ PostgresDialect.prototype.supports = _.defaults({
'RETURNING': true, 'RETURNING': true,
'DEFAULT VALUES': true, 'DEFAULT VALUES': true,
schemas: true schemas: true,
rowLocking: true,
forShare: 'FOR SHARE',
forUpdate: 'FOR UPDATE'
}, Abstract.prototype.supports) }, Abstract.prototype.supports)
module.exports = PostgresDialect module.exports = PostgresDialect
var chai = require('chai') var chai = require('chai')
, expect = chai.expect , expect = chai.expect
, Support = require(__dirname + '/support') , Support = require(__dirname + '/support')
, dialect = Support.getTestDialect()
, Transaction = require(__dirname + '/../lib/transaction') , Transaction = require(__dirname + '/../lib/transaction')
, sinon = require('sinon')
describe(Support.getTestDialectTeaser("Transaction"), function () { describe(Support.getTestDialectTeaser("Transaction"), function () {
this.timeout(4000); this.timeout(4000);
...@@ -71,4 +74,110 @@ describe(Support.getTestDialectTeaser("Transaction"), function () { ...@@ -71,4 +74,110 @@ describe(Support.getTestDialectTeaser("Transaction"), function () {
}) })
}) })
}) })
if (dialect !== 'sqlite') {
describe('row locking', function () {
this.timeout(10000);
it('supports for update', function (done) {
var User = this.sequelize.define('user', {
username: Support.Sequelize.STRING,
awesome: Support.Sequelize.BOOLEAN
})
, self = this
, t1Spy = sinon.spy()
, t2Spy = sinon.spy()
this.sequelize.sync({ force: true }).then(function () {
return User.create({ username: 'jan'})
}).then(function () {
self.sequelize.transaction(function (t1) {
return User.find({
where: {
username: 'jan'
},
forUpdate: true
}, { transaction: t1 }).then(function (t1Jan) {
self.sequelize.transaction({
isolationLevel: Transaction.ISOLATION_LEVELS.READ_COMMITTED
}, function (t2) {
User.find({
where: {
username: 'jan'
},
forUpdate: true
}, { transaction: t2}).then(function () {
t2Spy()
t2.commit().then(function () {
expect(t2Spy).to.have.been.calledAfter(t1Spy) // Find should not succeed before t1 has comitted
done()
})
})
t1Jan.updateAttributes({
awesome: true
}, { transaction: t1}).then(function () {
t1Spy()
setTimeout(t1.commit.bind(t1), 2000)
})
})
})
})
})
})
it('supports for share', function (done) {
var User = this.sequelize.define('user', {
username: Support.Sequelize.STRING,
awesome: Support.Sequelize.BOOLEAN
})
, self = this
, t1Spy = sinon.spy()
, t2FindSpy = sinon.spy()
, t2UpdateSpy = sinon.spy()
this.sequelize.sync({ force: true }).then(function () {
return User.create({ username: 'jan'})
}).then(function () {
self.sequelize.transaction(function (t1) {
return User.find({
where: {
username: 'jan'
},
forShare: true
}, { transaction: t1 }).then(function (t1Jan) {
self.sequelize.transaction({
isolationLevel: Transaction.ISOLATION_LEVELS.READ_COMMITTED
}, function (t2) {
User.find({
where: {
username: 'jan'
}
}, { transaction: t2}).then(function (t2Jan) {
t2FindSpy()
t2Jan.updateAttributes({
awesome: false
}, { transaction: t2 }).then(function () {
t2UpdateSpy()
t2.commit().then(function () {
expect(t2FindSpy).to.have.been.calledBefore(t1Spy) // The find call should have returned
expect(t2UpdateSpy).to.have.been.calledAfter(t1Spy) // But the update call should not happen before the first transaction has committed
done()
})
})
})
t1Jan.updateAttributes({
awesome: true
}, { transaction: t1}).then(function () {
t1Spy()
setTimeout(t1.commit.bind(t1), 2000)
})
})
})
})
})
})
})
}
}) })
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!