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

Commit abe7dfba by Sascha Depold

Add possibility to defer constraints in PostgreSQL

1 parent 341b0172
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
- [FIXED] `include.attributes = []` will no longer force the inclusion of the primary key, making it possible to write aggregates with includes. - [FIXED] `include.attributes = []` will no longer force the inclusion of the primary key, making it possible to write aggregates with includes.
- [CHANGED] The `references` property of model attributes has been transformed to an object: `{type: Sequelize.INTEGER, references: { model: SomeModel, key: 'some_key' }}`. The former format (`references` and `referecesKey`) still exists but is deprecated and will be removed in 4.0. - [CHANGED] The `references` property of model attributes has been transformed to an object: `{type: Sequelize.INTEGER, references: { model: SomeModel, key: 'some_key' }}`. The former format (`references` and `referecesKey`) still exists but is deprecated and will be removed in 4.0.
- [ADDED] It is now possible to defer constraints in PostgreSQL by added a property `deferrable` to the `references` object of a field.
# 3.0.0 # 3.0.0
......
<a name="deferrable"></a>
## `Deferrable()` -> `object`
[View code](https://github.com/sequelize/sequelize/blob/8cacc120955e369f681e589f78b986bf8dc36463/lib/deferrable.js#L37)
A collection of properties related to deferrable constraints. It can be used to
make foreign key constraints deferrable and to set the constaints within a
transaction. This is only supported in PostgreSQL.
The foreign keys can be configured like this. It will create a foreign key
that will check the constraints immediately when the data was inserted.
```js
sequelize.define('Model', {
foreign_id: {
type: Sequelize.INTEGER,
references: OtherModel,
referencesKey: 'id',
referencesDeferred: Sequelize.Deferrable.INITIALLY_IMMEDIATE
}
});
```
The constraints can be configured in a transaction like this. It will
trigger a query once the transaction has been started and set the constraints
to be checked at the very end of the transaction.
```js
sequelize.transaction({
deferred: Sequelize.Deferrable.SET_DEFERRED
});
```
***
<a name="initially_deferred"></a>
## `INITIALLY_DEFERRED()`
[View code](https://github.com/sequelize/sequelize/blob/8cacc120955e369f681e589f78b986bf8dc36463/lib/deferrable.js#L57)
A property that will defer constraints checks to the end of transactions.
***
<a name="initially_immediate"></a>
## `INITIALLY_IMMEDIATE()`
[View code](https://github.com/sequelize/sequelize/blob/8cacc120955e369f681e589f78b986bf8dc36463/lib/deferrable.js#L74)
A property that will trigger the constraint checks immediately
***
<a name="not"></a>
## `NOT()`
[View code](https://github.com/sequelize/sequelize/blob/8cacc120955e369f681e589f78b986bf8dc36463/lib/deferrable.js#L93)
A property that will set the constraints to not deferred. This is
the default in PostgreSQL and it make it impossible to dynamically
defer the constraints within a transaction.
***
<a name="set_deferred"></a>
## `SET_DEFERRED(constraints)`
[View code](https://github.com/sequelize/sequelize/blob/8cacc120955e369f681e589f78b986bf8dc36463/lib/deferrable.js#L112)
A property that will trigger an additional query at the beginning of a
transaction which sets the constraints to deferred.
**Params:**
| Name | Type | Description |
| ---- | ---- | ----------- |
| constraints | Array | An array of constraint names. Will defer all constraints by default. |
***
<a name="set_immediate"></a>
## `SET_IMMEDIATE(constraints)`
[View code](https://github.com/sequelize/sequelize/blob/8cacc120955e369f681e589f78b986bf8dc36463/lib/deferrable.js#L133)
A property that will trigger an additional query at the beginning of a
transaction which sets the constraints to immediately.
**Params:**
| Name | Type | Description |
| ---- | ---- | ----------- |
| constraints | Array | An array of constraint names. Will defer all constraints by default. |
***
_This document is automatically generated based on source code comments. Please do not edit it directly, as your changes will be ignored. Please write on <a href="irc://irc.freenode.net/#sequelizejs">IRC</a>, open an issue or a create a pull request if you feel something can be improved. For help on how to write source code documentation see [JSDoc](http://usejsdoc.org) and [dox](https://github.com/tj/dox)_
\ No newline at end of file
<a name="transaction"></a> <a name="transaction"></a>
# Class Transaction # Class Transaction
[View code](https://github.com/sequelize/sequelize/blob/716cb2d2aae3a4cd7fdaace13411cf4161e6deb6/lib/transaction.js#L11) [View code](https://github.com/sequelize/sequelize/blob/8cacc120955e369f681e589f78b986bf8dc36463/lib/transaction.js#L18)
The transaction object is used to identify a running transaction. It is created by calling `Sequelize.transaction()`. The transaction object is used to identify a running transaction. It is created by calling `Sequelize.transaction()`.
To run a query under a transaction, you should pass the transaction in the options object. To run a query under a transaction, you should pass the transaction in the options object.
**Params:**
| Name | Type | Description |
| ---- | ---- | ----------- |
| sequelize | Sequelize | A configured sequelize Instance |
| options | Object | An object with options |
| options.autocommit=true | Boolean | Sets the autocommit property of the transaction. |
| options.isolationLevel=true | String | Sets the isolation level of the transaction. |
| options.deferred | String | Sets the constraints to be deferred or immediately checked. |
*** ***
<a name="isolation_levels"></a> <a name="isolation_levels"></a>
## `ISOLATION_LEVELS` ## `ISOLATION_LEVELS`
[View code](https://github.com/sequelize/sequelize/blob/716cb2d2aae3a4cd7fdaace13411cf4161e6deb6/lib/transaction.js#L46) [View code](https://github.com/sequelize/sequelize/blob/8cacc120955e369f681e589f78b986bf8dc36463/lib/transaction.js#L53)
The possible isolations levels to use when starting a transaction. The possible isolations levels to use when starting a transaction.
Can be set per-transaction by passing `options.isolationLevel` to `sequelize.transaction`. Can be set per-transaction by passing `options.isolationLevel` to `sequelize.transaction`.
Default to `REPEATABLE_READ` but you can override the default isolation level by passing `options.isolationLevel` in `new Sequelize`. Default to `REPEATABLE_READ` but you can override the default isolation level by passing `options.isolationLevel` in `new Sequelize`.
...@@ -28,7 +39,7 @@ Default to `REPEATABLE_READ` but you can override the default isolation level by ...@@ -28,7 +39,7 @@ Default to `REPEATABLE_READ` but you can override the default isolation level by
<a name="lock"></a> <a name="lock"></a>
## `LOCK` ## `LOCK`
[View code](https://github.com/sequelize/sequelize/blob/716cb2d2aae3a4cd7fdaace13411cf4161e6deb6/lib/transaction.js#L90) [View code](https://github.com/sequelize/sequelize/blob/8cacc120955e369f681e589f78b986bf8dc36463/lib/transaction.js#L77)
Possible options for row locking. Used in conjuction with `find` calls: Possible options for row locking. Used in conjuction with `find` calls:
```js ```js
...@@ -45,30 +56,18 @@ t1 // is a transaction ...@@ -45,30 +56,18 @@ t1 // is a transaction
Model.findAll({ Model.findAll({
where: ..., where: ...,
transaction: t1, transaction: t1,
lock: t1.LOCK...
});
```
Postgres also supports specific locks while eager loading by using OF:
```js
UserModel.findAll({
where: ...,
include: [TaskModel, ...],
transaction: t1,
lock: { lock: {
level: t1.LOCK..., level: t1.LOCK...,
of: UserModel of: UserModel
} }
}); });
``` ```
UserModel will be locked but TaskModel won't!
*** ***
<a name="commit"></a> <a name="commit"></a>
## `commit()` -> `this` ## `commit()` -> `this`
[View code](https://github.com/sequelize/sequelize/blob/716cb2d2aae3a4cd7fdaace13411cf4161e6deb6/lib/transaction.js#L102) [View code](https://github.com/sequelize/sequelize/blob/8cacc120955e369f681e589f78b986bf8dc36463/lib/transaction.js#L89)
Commit the transaction Commit the transaction
...@@ -76,10 +75,10 @@ Commit the transaction ...@@ -76,10 +75,10 @@ Commit the transaction
<a name="rollback"></a> <a name="rollback"></a>
## `rollback()` -> `this` ## `rollback()` -> `this`
[View code](https://github.com/sequelize/sequelize/blob/716cb2d2aae3a4cd7fdaace13411cf4161e6deb6/lib/transaction.js#L123) [View code](https://github.com/sequelize/sequelize/blob/8cacc120955e369f681e589f78b986bf8dc36463/lib/transaction.js#L110)
Rollback (abort) the transaction Rollback (abort) the transaction
*** ***
_This document is automatically generated based on source code comments. Please do not edit it directly, as your changes will be ignored. Please write on <a href="irc://irc.freenode.net/#sequelizejs">IRC</a>, open an issue or a create a pull request if you feel something can be improved. For help on how to write source code documentation see [JSDoc](http://usejsdoc.org) and [dox](https://github.com/tj/dox)_ _This document is automatically generated based on source code comments. Please do not edit it directly, as your changes will be ignored. Please write on <a href="irc://irc.freenode.net/#sequelizejs">IRC</a>, open an issue or a create a pull request if you feel something can be improved. For help on how to write source code documentation see [JSDoc](http://usejsdoc.org) and [dox](https://github.com/tj/dox)_
\ No newline at end of file
...@@ -27,7 +27,8 @@ if (program.file) { ...@@ -27,7 +27,8 @@ if (program.file) {
{file:'lib/associations/mixin.js', output: 'associations'}, {file:'lib/associations/mixin.js', output: 'associations'},
{file:'lib/promise.js', output: 'promise'}, {file:'lib/promise.js', output: 'promise'},
{file:'lib/transaction.js', output: 'transaction'}, {file:'lib/transaction.js', output: 'transaction'},
{file:'lib/data-types.js', output: 'datatypes'} {file:'lib/data-types.js', output: 'datatypes'},
{file:'lib/deferrable.js', output: 'deferrable'}
]; ];
} }
......
...@@ -48,7 +48,21 @@ var Foo = sequelize.define('Foo', { ...@@ -48,7 +48,21 @@ var Foo = sequelize.define('Foo', {
hasComment: { type: Sequelize.INTEGER, comment: "I'm a comment!" }, hasComment: { type: Sequelize.INTEGER, comment: "I'm a comment!" },
// You can specify a custom field name via the "field" attribute: // You can specify a custom field name via the "field" attribute:
fieldWithUnderscores: { type: Sequelize.STRING, field: "field_with_underscores" } fieldWithUnderscores: { type: Sequelize.STRING, field: "field_with_underscores" },
// It is possible to create foreign keys:
bar_id: {
type: Sequelize.INTEGER,
// This is a reference to another model
references: Bar,
// This is the column name of the referenced model
referencesKey: 'id',
// This declares when to check the foreign key constraint. PostgreSQL only.
referencesDeferrable: Sequelize.Deferrable.INITIALLY_IMMEDIATE
}
}) })
``` ```
...@@ -134,6 +148,25 @@ sequelize.define('model', { ...@@ -134,6 +148,25 @@ sequelize.define('model', {
}) })
``` ```
## Deferrable
When you specify a foreign key column it is optionally possible to declare the deferrable
type in PostgreSQL. The following options are available:
```js
// Defer all foreign key constraint check to the end of a transaction
Sequelize.Deferrable.INITIALLY_DEFERRED
// Immediately check the foreign key constraints
Sequelize.Deferrable.INITIALLY_IMMEDIATE
// Don't defer the checks at all
Sequelize.Deferrable.NOT
```
The last option is the default in PostgreSQL and won't allow you to dynamically change
the rule in a transaction. See [the transaction section](docs/transactions/#options) for further information.
## Getters & setters ## Getters & setters
It is possible to define 'object-property' getters and setter functions on your models, these can be used both for 'protecting' properties that map to database fields and for defining 'pseudo' properties. It is possible to define 'object-property' getters and setter functions on your models, these can be used both for 'protecting' properties that map to database fields and for defining 'pseudo' properties.
......
...@@ -24,7 +24,7 @@ return sequelize.transaction(function (t) { ...@@ -24,7 +24,7 @@ return sequelize.transaction(function (t) {
}); });
}).then(function (result) { }).then(function (result) {
// Transaction has been committed // Transaction has been committed
// result is whatever the result of the promise chain returned to the transaction callback // result is whatever the result of the promise chain returned to the transaction callback
}).catch(function (err) { }).catch(function (err) {
// Transaction has been rolled back // Transaction has been rolled back
// err is whatever rejected the promise chain returned to the transaction callback // err is whatever rejected the promise chain returned to the transaction callback
...@@ -101,7 +101,7 @@ sequelize.transaction(function (t1) { ...@@ -101,7 +101,7 @@ sequelize.transaction(function (t1) {
# Unmanaged transaction (then-callback) # Unmanaged transaction (then-callback)
Unmanaged transactions force you to manually rollback or commit the transaction. If you don't do that, the transaction will hang until it times out. To start an unmanaged transaction, call `sequelize.transaction()` without a callback (you can still pass an options object) and call `then` on the returned promise. Unmanaged transactions force you to manually rollback or commit the transaction. If you don't do that, the transaction will hang until it times out. To start an unmanaged transaction, call `sequelize.transaction()` without a callback (you can still pass an options object) and call `then` on the returned promise.
```js ```js
return sequelize.transaction().then(function (t) { return sequelize.transaction().then(function (t) {
return User.create({ return User.create({
firstName: 'Homer', firstName: 'Homer',
...@@ -110,7 +110,7 @@ return sequelize.transaction().then(function (t) { ...@@ -110,7 +110,7 @@ return sequelize.transaction().then(function (t) {
return user.addSibling({ return user.addSibling({
firstName: 'Lisa', firstName: 'Lisa',
lastName: 'Simpson' lastName: 'Simpson'
}, {transction: t}); }, {transaction: t});
}).then(function () { }).then(function () {
t.commit(); t.commit();
}).catch(function (err) { }).catch(function (err) {
...@@ -119,7 +119,57 @@ return sequelize.transaction().then(function (t) { ...@@ -119,7 +119,57 @@ return sequelize.transaction().then(function (t) {
}); });
``` ```
# Using transactions with other sequelize methods # Options
The `transaction` method can be called with an options object as the first argument, that
allows the configuration of the transaction.
```js
return sequelize.transaction({ /* options */ });
```
The following options (with it's default values) are available:
```js
{
autocommit: true,
isolationLevel: 'REPEATABLE_READ',
deferrable: 'NOT DEFERRABLE' // implicit default of postgres
}
```
The `isolationLevel` can either be set globally when initializing the Sequelize instance or
locally for every transaction:
```js
// globally
new Sequelize('db', 'user', 'pw', {
isolationLevel: Sequelize.Transaction.ISOLATION_LEVELS.SERIALIZABLE
});
// locally
sequelize.transaction({
isolationLevel: Sequelize.Transaction.ISOLATION_LEVELS.SERIALIZABLE
});
```
The `deferrable` option triggers an additional query after the transaction start
that optionally set the constraint checks to be deferred or immediate. Please note
that this is only supported in PostgreSQL.
```js
sequelize.transaction({
// to defer all constraints:
deferrable: Sequelize.Deferrable.SET_DEFERRED,
// to defer a specific constraint:
deferrable: Sequelize.Deferrable.SET_DEFERRED(['some_constraint']),
// to not defer constraints:
deferrable: Sequelize.Deferrable.SET_IMMEDIATE
})
```
# Usage with other sequelize methods
The `transaction` option goes with most other options, which are usually the first argument of a method. The `transaction` option goes with most other options, which are usually the first argument of a method.
For methods that take values, like `.create`, `.update()`, `.updateAttributes()` etc. `transaction` should be passed to the option in the second argument. For methods that take values, like `.create`, `.update()`, `.updateAttributes()` etc. `transaction` should be passed to the option in the second argument.
......
'use strict';
var util = require('util');
/**
* A collection of properties related to deferrable constraints. It can be used to
* make foreign key constraints deferrable and to set the constaints within a
* transaction. This is only supported in PostgreSQL.
*
* The foreign keys can be configured like this. It will create a foreign key
* that will check the constraints immediately when the data was inserted.
*
* ```js
* sequelize.define('Model', {
* foreign_id: {
* type: Sequelize.INTEGER,
* references: {
* model: OtherModel,
* key: 'id',
* deferrable: Sequelize.Deferrable.INITIALLY_IMMEDIATE
* }
* }
* });
* ```
*
* The constraints can be configured in a transaction like this. It will
* trigger a query once the transaction has been started and set the constraints
* to be checked at the very end of the transaction.
*
* ```js
* sequelize.transaction({
* deferrable: Sequelize.Deferrable.SET_DEFERRED
* });
* ```
*
* @return {object}
*/
var Deferrable = module.exports = {
INITIALLY_DEFERRED: INITIALLY_DEFERRED,
INITIALLY_IMMEDIATE: INITIALLY_IMMEDIATE,
NOT: NOT,
SET_DEFERRED: SET_DEFERRED,
SET_IMMEDIATE: SET_IMMEDIATE
};
function ABSTRACT () {}
ABSTRACT.prototype.toString = function () {
return this.toSql.apply(this, arguments);
};
/**
* A property that will defer constraints checks to the end of transactions.
*
* @property INITIALLY_DEFERRED
*/
function INITIALLY_DEFERRED () {
if (!(this instanceof INITIALLY_DEFERRED)) {
return new INITIALLY_DEFERRED();
}
}
util.inherits(INITIALLY_DEFERRED, ABSTRACT);
INITIALLY_DEFERRED.prototype.toSql = function () {
return 'DEFERRABLE INITIALLY DEFERRED';
};
/**
* A property that will trigger the constraint checks immediately
*
* @property INITIALLY_IMMEDIATE
*/
function INITIALLY_IMMEDIATE () {
if (!(this instanceof INITIALLY_IMMEDIATE)) {
return new INITIALLY_IMMEDIATE();
}
}
util.inherits(INITIALLY_IMMEDIATE, ABSTRACT);
INITIALLY_IMMEDIATE.prototype.toSql = function () {
return 'DEFERRABLE INITIALLY IMMEDIATE';
};
/**
* A property that will set the constraints to not deferred. This is
* the default in PostgreSQL and it make it impossible to dynamically
* defer the constraints within a transaction.
*
* @property NOT
*/
function NOT () {
if (!(this instanceof NOT)) {
return new NOT();
}
}
util.inherits(NOT, ABSTRACT);
NOT.prototype.toSql = function () {
return 'NOT DEFERRABLE';
};
/**
* A property that will trigger an additional query at the beginning of a
* transaction which sets the constraints to deferred.
*
* @param {Array} constraints An array of constraint names. Will defer all constraints by default.
* @property SET_DEFERRED
*/
function SET_DEFERRED (constraints) {
if (!(this instanceof SET_DEFERRED)) {
return new SET_DEFERRED(constraints);
}
this.constraints = constraints;
}
util.inherits(SET_DEFERRED, ABSTRACT);
SET_DEFERRED.prototype.toSql = function (queryGenerator) {
return queryGenerator.setDeferredQuery(this.constraints);
};
/**
* A property that will trigger an additional query at the beginning of a
* transaction which sets the constraints to immediately.
*
* @param {Array} constraints An array of constraint names. Will defer all constraints by default.
* @property SET_IMMEDIATE
*/
function SET_IMMEDIATE (constraints) {
if (!(this instanceof SET_IMMEDIATE)) {
return new SET_IMMEDIATE(constraints);
}
this.constraints = constraints;
}
util.inherits(SET_IMMEDIATE, ABSTRACT);
SET_IMMEDIATE.prototype.toSql = function (queryGenerator) {
return queryGenerator.setImmediateQuery(this.constraints);
};
Object.keys(Deferrable).forEach(function (key) {
var DeferrableType = Deferrable[key];
DeferrableType.toString = function () {
var instance = new DeferrableType();
return instance.toString.apply(instance, arguments);
};
});
...@@ -48,6 +48,7 @@ AbstractDialect.prototype.supports = { ...@@ -48,6 +48,7 @@ AbstractDialect.prototype.supports = {
joinTableDependent: true, joinTableDependent: true,
indexViaAlter: false, indexViaAlter: false,
JSON: false, JSON: false,
deferrableConstraints: false
}; };
module.exports = AbstractDialect; module.exports = AbstractDialect;
...@@ -1476,6 +1476,19 @@ module.exports = (function() { ...@@ -1476,6 +1476,19 @@ module.exports = (function() {
}, },
/** /**
* Returns a query that defers the constraints. Only works for postgres.
*
* @param {Transaction} transaction
* @param {Object} options An object with options.
* @return {String} The generated sql query.
*/
deferConstraintsQuery: function () {},
setConstraintQuery: function () {},
setDeferredQuery: function () {},
setImmediateQuery: function () {},
/**
* Returns a query that commits a transaction. * Returns a query that commits a transaction.
* *
* @param {Object} options An object with options. * @param {Object} options An object with options.
......
...@@ -44,7 +44,8 @@ PostgresDialect.prototype.supports = _.merge(_.cloneDeep(Abstract.prototype.supp ...@@ -44,7 +44,8 @@ PostgresDialect.prototype.supports = _.merge(_.cloneDeep(Abstract.prototype.supp
NUMERIC: true, NUMERIC: true,
ARRAY: true, ARRAY: true,
JSON: true, JSON: true,
JSONB: true JSONB: true,
deferrableConstraints: true
}); });
PostgresDialect.prototype.Query = Query; PostgresDialect.prototype.Query = Query;
......
...@@ -529,11 +529,40 @@ module.exports = (function() { ...@@ -529,11 +529,40 @@ module.exports = (function() {
template += ' ON UPDATE <%= onUpdateAction %>'; template += ' ON UPDATE <%= onUpdateAction %>';
replacements.onUpdateAction = attribute.onUpdate.toUpperCase(); replacements.onUpdateAction = attribute.onUpdate.toUpperCase();
} }
if (attribute.references.deferrable) {
template += ' <%= deferrable %>';
replacements.deferrable = attribute.references.deferrable.toString(this);
}
} }
return Utils._.template(template)(replacements); return Utils._.template(template)(replacements);
}, },
deferConstraintsQuery: function (options) {
return options.deferrable.toString(this);
},
setConstraintQuery: function (columns, type) {
var columnFragment = 'ALL';
if (columns) {
columnFragment = columns.map(function (column) {
return this.quoteIdentifier(column);
}.bind(this)).join(', ');
}
return 'SET CONSTRAINTS ' + columnFragment + ' ' + type;
},
setDeferredQuery: function (columns) {
return this.setConstraintQuery(columns, 'DEFERRED');
},
setImmediateQuery: function (columns) {
return this.setConstraintQuery(columns, 'IMMEDIATE');
},
attributesToSQL: function(attributes, options) { attributesToSQL: function(attributes, options) {
var result = {} var result = {}
, key , key
......
...@@ -880,6 +880,21 @@ module.exports = (function() { ...@@ -880,6 +880,21 @@ module.exports = (function() {
return this.sequelize.query(sql, options); return this.sequelize.query(sql, options);
}; };
QueryInterface.prototype.deferConstraints = function (transaction, options) {
options = Utils._.extend({
transaction: transaction,
parent: options.transaction
}, options || {});
var sql = this.QueryGenerator.deferConstraintsQuery(options);
if (sql) {
return this.sequelize.query(sql, options);
}
return Promise.resolve();
};
QueryInterface.prototype.commitTransaction = function(transaction, options) { QueryInterface.prototype.commitTransaction = function(transaction, options) {
if (!transaction || !(transaction instanceof Transaction)) { if (!transaction || !(transaction instanceof Transaction)) {
throw new Error('Unable to commit a transaction without transaction object!'); throw new Error('Unable to commit a transaction without transaction object!');
......
...@@ -5,6 +5,7 @@ var url = require('url') ...@@ -5,6 +5,7 @@ var url = require('url')
, Utils = require('./utils') , Utils = require('./utils')
, Model = require('./model') , Model = require('./model')
, DataTypes = require('./data-types') , DataTypes = require('./data-types')
, Deferrable = require('./deferrable')
, ModelManager = require('./model-manager') , ModelManager = require('./model-manager')
, QueryInterface = require('./query-interface') , QueryInterface = require('./query-interface')
, Transaction = require('./transaction') , Transaction = require('./transaction')
...@@ -263,6 +264,14 @@ module.exports = (function() { ...@@ -263,6 +264,14 @@ module.exports = (function() {
Sequelize.prototype.Transaction = Sequelize.Transaction = Transaction; Sequelize.prototype.Transaction = Sequelize.Transaction = Transaction;
/** /**
* A reference to the deferrable collection. Use this to access the different deferrable options.
* @property Deferrable
* @see {Deferrable}
* @see {Sequelize#transaction}
*/
Sequelize.prototype.Deferrable = Sequelize.Deferrable = Deferrable;
/**
* A reference to the sequelize instance class. * A reference to the sequelize instance class.
* @property Instance * @property Instance
* @see {Instance} * @see {Instance}
......
...@@ -7,6 +7,13 @@ var Utils = require('./utils'); ...@@ -7,6 +7,13 @@ var Utils = require('./utils');
* *
* To run a query under a transaction, you should pass the transaction in the options object. * To run a query under a transaction, you should pass the transaction in the options object.
* @class Transaction * @class Transaction
* @constructor
*
* @param {Sequelize} sequelize A configured sequelize Instance
* @param {Object} options An object with options
* @param {Boolean} options.autocommit=true Sets the autocommit property of the transaction.
* @param {String} options.isolationLevel=true Sets the isolation level of the transaction.
* @param {String} options.deferrable Sets the constraints to be deferred or immediately checked.
*/ */
var Transaction = module.exports = function(sequelize, options) { var Transaction = module.exports = function(sequelize, options) {
this.sequelize = sequelize; this.sequelize = sequelize;
...@@ -150,6 +157,8 @@ Transaction.prototype.prepareEnvironment = function() { ...@@ -150,6 +157,8 @@ Transaction.prototype.prepareEnvironment = function() {
}).then(function () { }).then(function () {
return self.begin(); return self.begin();
}).then(function () { }).then(function () {
return self.setDeferrable();
}).then(function () {
return self.setIsolationLevel(); return self.setIsolationLevel();
}).then(function () { }).then(function () {
return self.setAutocommit(); return self.setAutocommit();
...@@ -163,6 +172,15 @@ Transaction.prototype.begin = function() { ...@@ -163,6 +172,15 @@ Transaction.prototype.begin = function() {
.startTransaction(this, this.options); .startTransaction(this, this.options);
}; };
Transaction.prototype.setDeferrable = function () {
if (this.options.deferrable) {
return this
.sequelize
.getQueryInterface()
.deferConstraints(this, this.options);
}
};
Transaction.prototype.setAutocommit = function() { Transaction.prototype.setAutocommit = function() {
return this return this
.sequelize .sequelize
......
...@@ -406,8 +406,9 @@ var Utils = module.exports = { ...@@ -406,8 +406,9 @@ var Utils = module.exports = {
formatReferences: function (obj) { formatReferences: function (obj) {
if (!_.isPlainObject(obj.references)) { if (!_.isPlainObject(obj.references)) {
deprecate('Non-object references property found. Support for that will be removed in version 4. Expected { references: { model: "value", key: "key" } } instead of { references: "value", referencesKey: "key" }.'); deprecate('Non-object references property found. Support for that will be removed in version 4. Expected { references: { model: "value", key: "key" } } instead of { references: "value", referencesKey: "key" }.');
obj.references = { model: obj.references, key: obj.referencesKey }; obj.references = { model: obj.references, key: obj.referencesKey, deferrable: obj.referencesDeferrable };
obj.referencesKey = undefined; obj.referencesKey = undefined;
obj.referencesDeferrable = undefined;
} }
return obj; return obj;
......
...@@ -36,8 +36,8 @@ pages: ...@@ -36,8 +36,8 @@ pages:
- ['api/promise.md', 'API', 'Promise'] - ['api/promise.md', 'API', 'Promise']
- ['api/transaction.md', 'API', 'Transaction'] - ['api/transaction.md', 'API', 'Transaction']
- ['api/datatypes.md', 'API', 'Datatypes'] - ['api/datatypes.md', 'API', 'Datatypes']
- ['api/deferrable.md', 'API', 'Deferrable']
- ['api/errors.md', 'API', 'Errors'] - ['api/errors.md', 'API', 'Errors']
- ['changelog.md', 'Misc', 'Changelog'] - ['changelog.md', 'Misc', 'Changelog']
- ['imprint.md', 'Misc', 'Imprint'] - ['imprint.md', 'Misc', 'Imprint']
'use strict';
/* jshint -W030 */
/* jshint -W110 */
var _ = require('lodash')
, chai = require('chai')
, expect = chai.expect
, Support = require(__dirname + '/../support')
, Sequelize = require(__dirname + '/../../../index')
, config = require(__dirname + '/../../config/config')
;
if (!Support.sequelize.dialect.supports.deferrableConstraints) {
return;
}
describe(Support.getTestDialectTeaser('Sequelize'), function() {
beforeEach(function () {
this.run = function (deferrable, options) {
options = options || {};
var taskTableName = options.taskTableName || 'tasks_' + config.rand();
var transactionOptions = _.extend({ deferrable: Sequelize.Deferrable.SET_DEFERRED }, options);
var userTableName = 'users_' + config.rand();
var User = this.sequelize.define(
'User', { name: Sequelize.STRING }, { tableName: userTableName }
);
var Task = this.sequelize.define(
'Task', {
title: Sequelize.STRING,
user_id: {
allowNull: false,
type: Sequelize.INTEGER,
references: {
model: userTableName,
key: 'id',
deferrable: deferrable
}
}
}, {
tableName: taskTableName
}
);
return User.sync({ force: true }).bind(this).then(function () {
return Task.sync({ force: true });
}).then(function () {
return this.sequelize.transaction(transactionOptions, function (t) {
return Task
.create({ title: 'a task', user_id: -1 }, { transaction: t })
.then(function (task) {
return [task, User.create({}, { transaction: t })];
})
.spread(function (task, user) {
task.user_id = user.id;
return task.save({ transaction: t });
});
});
});
};
});
describe('Deferrable', function () {
describe('NOT', function () {
it('does not allow the violation of the foreign key constraint', function () {
return expect(this.run(Sequelize.Deferrable.NOT)).to.eventually.be.rejectedWith(Sequelize.ForeignKeyConstraintError);
});
});
describe('INITIALLY_IMMEDIATE', function () {
it('allows the violation of the foreign key constraint if the transaction is deferred', function () {
return this
.run(Sequelize.Deferrable.INITIALLY_IMMEDIATE)
.then(function (task) {
expect(task.title).to.equal('a task');
expect(task.user_id).to.equal(1);
});
});
it('does not allow the violation of the foreign key constraint if the transaction is not deffered', function () {
return expect(this.run(Sequelize.Deferrable.INITIALLY_IMMEDIATE, {
deferrable: undefined
})).to.eventually.be.rejectedWith(Sequelize.ForeignKeyConstraintError);
});
it('allows the violation of the foreign key constraint if the transaction deferres only the foreign key constraint', function () {
var taskTableName = 'tasks_' + config.rand();
return this
.run(Sequelize.Deferrable.INITIALLY_IMMEDIATE, {
deferrable: Sequelize.Deferrable.SET_DEFERRED([taskTableName + '_user_id_fkey']),
taskTableName: taskTableName
})
.then(function (task) {
expect(task.title).to.equal('a task');
expect(task.user_id).to.equal(1);
});
});
});
describe('INITIALLY_DEFERRED', function () {
it('allows the violation of the foreign key constraint', function () {
return this
.run(Sequelize.Deferrable.INITIALLY_DEFERRED)
.then(function (task) {
expect(task.title).to.equal('a task');
expect(task.user_id).to.equal(1);
});
});
});
});
});
...@@ -10,10 +10,11 @@ var chai = require('chai') ...@@ -10,10 +10,11 @@ var chai = require('chai')
describe(Support.getTestDialectTeaser('Utils'), function() { describe(Support.getTestDialectTeaser('Utils'), function() {
describe('formatReferences', function () { describe('formatReferences', function () {
([ ([
[{referencesKey: 1}, {references: {model: undefined, key: 1}, referencesKey: undefined}], [{referencesKey: 1}, {references: {model: undefined, key: 1, deferrable: undefined}, referencesKey: undefined, referencesDeferrable: undefined}],
[{references: 'a'}, {references: {model: 'a', key: undefined}, referencesKey: undefined}], [{references: 'a'}, {references: {model: 'a', key: undefined, deferrable: undefined}, referencesKey: undefined, referencesDeferrable: undefined}],
[{references: 'a', referencesKey: 1}, {references: {model: 'a', key: 1}, referencesKey: undefined}], [{references: 'a', referencesKey: 1}, {references: {model: 'a', key: 1, deferrable: undefined}, referencesKey: undefined, referencesDeferrable: undefined}],
[{references: {model: 1}}, {references: {model: 1}}] [{references: {model: 1}}, {references: {model: 1}}],
[{references: 1, referencesKey: 2, referencesDeferrable: 3}, {references: {model: 1, key: 2, deferrable: 3}, referencesKey: undefined, referencesDeferrable: undefined}]
]).forEach(function (test) { ]).forEach(function (test) {
var input = test[0]; var input = test[0];
var output = test[1]; var output = test[1];
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!