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

Commit e7dd435b by Jan Aagaard Meier

Merge

2 parents eef01182 3eff6cea
...@@ -956,6 +956,14 @@ module.exports = (function() { ...@@ -956,6 +956,14 @@ module.exports = (function() {
query = mainQueryItems.join('') query = mainQueryItems.join('')
} }
if (options.lock && this._dialect.supports.lock) {
if (options.lock === 'SHARE') {
query += ' ' + this._dialect.supports.forShare
} else {
query += ' FOR UPDATE'
}
}
query += ";"; query += ";";
return query return query
......
...@@ -7,7 +7,9 @@ var MysqlDialect = function(sequelize) { ...@@ -7,7 +7,9 @@ var MysqlDialect = function(sequelize) {
MysqlDialect.prototype.supports = _.defaults({ MysqlDialect.prototype.supports = _.defaults({
'VALUES ()': true, 'VALUES ()': true,
'LIMIT ON UPDATE':true 'LIMIT ON UPDATE':true,
lock: true,
forShare: 'LOCK IN SHARE MODE'
}, Abstract.prototype.supports) }, Abstract.prototype.supports)
module.exports = MysqlDialect module.exports = MysqlDialect
...@@ -8,7 +8,9 @@ var PostgresDialect = function(sequelize) { ...@@ -8,7 +8,9 @@ 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,
lock: true,
forShare: 'FOR SHARE',
}, Abstract.prototype.supports) }, Abstract.prototype.supports)
module.exports = PostgresDialect module.exports = PostgresDialect
...@@ -552,6 +552,8 @@ module.exports = (function() { ...@@ -552,6 +552,8 @@ module.exports = (function() {
}) })
} }
options.lock = queryOptions.lock
var sql = this.QueryGenerator.selectQuery(tableName, options, factory) var sql = this.QueryGenerator.selectQuery(tableName, options, factory)
queryOptions = Utils._.extend({}, queryOptions, { queryOptions = Utils._.extend({}, queryOptions, {
include: options.include, include: options.include,
......
...@@ -40,6 +40,26 @@ Transaction.ISOLATION_LEVELS = { ...@@ -40,6 +40,26 @@ Transaction.ISOLATION_LEVELS = {
} }
/** /**
* Possible options for row locking. Used in conjuction with `find` calls:
*
* ```js
* t1 // is a transaction
* Model.findAll({
* where: ...
* }, {
* transaction: t1,
* lock: t1.LOCK.UPDATE,
* lock: t1.LOCK.SHARE
* })
* ```
* @property LOCK
*/
Transaction.LOCK = Transaction.prototype.LOCK = {
UPDATE: 'UPDATE',
SHARE: 'SHARE'
}
/**
* Commit the transaction * Commit the transaction
* *
* @return {this} * @return {this}
......
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,120 @@ describe(Support.getTestDialectTeaser("Transaction"), function () { ...@@ -71,4 +74,120 @@ 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'
}
}, {
lock: t1.LOCK.UPDATE,
transaction: t1
}).then(function (t1Jan) {
self.sequelize.transaction({
isolationLevel: Transaction.ISOLATION_LEVELS.READ_COMMITTED
}, function (t2) {
User.find({
where: {
username: 'jan'
},
}, {
lock: t2.LOCK.UPDATE,
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'
}
}, {
lock: t1.LOCK.SHARE,
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!