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

Commit abe7dfba by Sascha Depold

Add possibility to defer constraints in PostgreSQL

1 parent 341b0172
......@@ -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.
- [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
......
<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>
# 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()`.
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>
## `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.
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`.
......@@ -28,7 +39,7 @@ Default to `REPEATABLE_READ` but you can override the default isolation level by
<a name="lock"></a>
## `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:
```js
......@@ -45,30 +56,18 @@ t1 // is a transaction
Model.findAll({
where: ...,
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: {
level: t1.LOCK...,
of: UserModel
}
});
```
UserModel will be locked but TaskModel won't!
***
<a name="commit"></a>
## `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
......@@ -76,7 +75,7 @@ Commit the transaction
<a name="rollback"></a>
## `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
......
......@@ -27,7 +27,8 @@ if (program.file) {
{file:'lib/associations/mixin.js', output: 'associations'},
{file:'lib/promise.js', output: 'promise'},
{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', {
hasComment: { type: Sequelize.INTEGER, comment: "I'm a comment!" },
// 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', {
})
```
## 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
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.
......
......@@ -110,7 +110,7 @@ return sequelize.transaction().then(function (t) {
return user.addSibling({
firstName: 'Lisa',
lastName: 'Simpson'
}, {transction: t});
}, {transaction: t});
}).then(function () {
t.commit();
}).catch(function (err) {
......@@ -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.
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 = {
joinTableDependent: true,
indexViaAlter: false,
JSON: false,
deferrableConstraints: false
};
module.exports = AbstractDialect;
......@@ -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.
*
* @param {Object} options An object with options.
......
......@@ -44,7 +44,8 @@ PostgresDialect.prototype.supports = _.merge(_.cloneDeep(Abstract.prototype.supp
NUMERIC: true,
ARRAY: true,
JSON: true,
JSONB: true
JSONB: true,
deferrableConstraints: true
});
PostgresDialect.prototype.Query = Query;
......
......@@ -529,11 +529,40 @@ module.exports = (function() {
template += ' ON UPDATE <%= onUpdateAction %>';
replacements.onUpdateAction = attribute.onUpdate.toUpperCase();
}
if (attribute.references.deferrable) {
template += ' <%= deferrable %>';
replacements.deferrable = attribute.references.deferrable.toString(this);
}
}
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) {
var result = {}
, key
......
......@@ -880,6 +880,21 @@ module.exports = (function() {
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) {
if (!transaction || !(transaction instanceof Transaction)) {
throw new Error('Unable to commit a transaction without transaction object!');
......
......@@ -5,6 +5,7 @@ var url = require('url')
, Utils = require('./utils')
, Model = require('./model')
, DataTypes = require('./data-types')
, Deferrable = require('./deferrable')
, ModelManager = require('./model-manager')
, QueryInterface = require('./query-interface')
, Transaction = require('./transaction')
......@@ -263,6 +264,14 @@ module.exports = (function() {
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.
* @property Instance
* @see {Instance}
......
......@@ -7,6 +7,13 @@ var Utils = require('./utils');
*
* To run a query under a transaction, you should pass the transaction in the options object.
* @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) {
this.sequelize = sequelize;
......@@ -150,6 +157,8 @@ Transaction.prototype.prepareEnvironment = function() {
}).then(function () {
return self.begin();
}).then(function () {
return self.setDeferrable();
}).then(function () {
return self.setIsolationLevel();
}).then(function () {
return self.setAutocommit();
......@@ -163,6 +172,15 @@ Transaction.prototype.begin = function() {
.startTransaction(this, this.options);
};
Transaction.prototype.setDeferrable = function () {
if (this.options.deferrable) {
return this
.sequelize
.getQueryInterface()
.deferConstraints(this, this.options);
}
};
Transaction.prototype.setAutocommit = function() {
return this
.sequelize
......
......@@ -406,8 +406,9 @@ var Utils = module.exports = {
formatReferences: function (obj) {
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" }.');
obj.references = { model: obj.references, key: obj.referencesKey };
obj.references = { model: obj.references, key: obj.referencesKey, deferrable: obj.referencesDeferrable };
obj.referencesKey = undefined;
obj.referencesDeferrable = undefined;
}
return obj;
......
......@@ -36,8 +36,8 @@ pages:
- ['api/promise.md', 'API', 'Promise']
- ['api/transaction.md', 'API', 'Transaction']
- ['api/datatypes.md', 'API', 'Datatypes']
- ['api/deferrable.md', 'API', 'Deferrable']
- ['api/errors.md', 'API', 'Errors']
- ['changelog.md', 'Misc', 'Changelog']
- ['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')
describe(Support.getTestDialectTeaser('Utils'), function() {
describe('formatReferences', function () {
([
[{referencesKey: 1}, {references: {model: undefined, key: 1}, referencesKey: undefined}],
[{references: 'a'}, {references: {model: 'a', key: undefined}, referencesKey: undefined}],
[{references: 'a', referencesKey: 1}, {references: {model: 'a', key: 1}, referencesKey: undefined}],
[{references: {model: 1}}, {references: {model: 1}}]
[{referencesKey: 1}, {references: {model: undefined, key: 1, deferrable: undefined}, referencesKey: undefined, referencesDeferrable: undefined}],
[{references: 'a'}, {references: {model: 'a', key: undefined, deferrable: undefined}, referencesKey: undefined, referencesDeferrable: undefined}],
[{references: 'a', referencesKey: 1}, {references: {model: 'a', key: 1, deferrable: undefined}, referencesKey: undefined, referencesDeferrable: undefined}],
[{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) {
var input = test[0];
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!