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

Commit f98bd7ed by Hugo Denizart Committed by GitHub

feat(add-constraint): add deferrable option (#13096)

Co-authored-by: Pedro Augusto de Paula Barbosa <papb1996@gmail.com>
1 parent 48225948
...@@ -684,6 +684,11 @@ class QueryGenerator { ...@@ -684,6 +684,11 @@ class QueryGenerator {
break; break;
default: throw new Error(`${options.type} is invalid.`); default: throw new Error(`${options.type} is invalid.`);
} }
if (options.deferrable && ['UNIQUE', 'PRIMARY KEY', 'FOREIGN KEY'].includes(options.type.toUpperCase())) {
constraintSnippet += ` ${this.deferConstraintsQuery(options)}`;
}
return constraintSnippet; return constraintSnippet;
} }
......
...@@ -703,6 +703,7 @@ class QueryInterface { ...@@ -703,6 +703,7 @@ class QueryInterface {
* @param {string} [options.references.table] Target table name * @param {string} [options.references.table] Target table name
* @param {string} [options.references.field] Target column name * @param {string} [options.references.field] Target column name
* @param {string} [options.references.fields] Target column names for a composite primary key. Must match the order of fields in options.fields. * @param {string} [options.references.fields] Target column names for a composite primary key. Must match the order of fields in options.fields.
* @param {string} [options.deferrable] Sets the constraint to be deferred or immediately checked. See Sequelize.Deferrable. PostgreSQL Only
* *
* @returns {Promise} * @returns {Promise}
*/ */
......
...@@ -13,92 +13,136 @@ if (!Support.sequelize.dialect.supports.deferrableConstraints) { ...@@ -13,92 +13,136 @@ if (!Support.sequelize.dialect.supports.deferrableConstraints) {
describe(Support.getTestDialectTeaser('Sequelize'), () => { describe(Support.getTestDialectTeaser('Sequelize'), () => {
describe('Deferrable', () => { describe('Deferrable', () => {
beforeEach(function() { const describeDeferrableTest = (title, defineModels) => {
this.run = async function(deferrable, options) { describe(title, () => {
options = options || {}; beforeEach(function() {
this.run = async function(deferrable, options) {
const taskTableName = options.taskTableName || `tasks_${config.rand()}`; options = options || {};
const transactionOptions = { deferrable: Sequelize.Deferrable.SET_DEFERRED, ...options };
const userTableName = `users_${config.rand()}`; const taskTableName = options.taskTableName || `tasks_${config.rand()}`;
const transactionOptions = { deferrable: Sequelize.Deferrable.SET_DEFERRED, ...options };
const User = this.sequelize.define( const userTableName = `users_${config.rand()}`;
'User', { name: Sequelize.STRING }, { tableName: userTableName }
); const { Task, User } = await defineModels({ sequelize: this.sequelize, userTableName, deferrable, taskTableName });
const Task = this.sequelize.define( return this.sequelize.transaction(transactionOptions, async t => {
'Task', { const task0 = await Task
title: Sequelize.STRING, .create({ title: 'a task', user_id: -1 }, { transaction: t });
user_id: {
allowNull: false, const [task, user] = await Promise.all([task0, User.create({}, { transaction: t })]);
type: Sequelize.INTEGER, task.user_id = user.id;
references: { return task.save({ transaction: t });
model: userTableName, });
key: 'id', };
deferrable });
}
}
}, {
tableName: taskTableName
}
);
await User.sync({ force: true });
await Task.sync({ force: true });
return this.sequelize.transaction(transactionOptions, async t => {
const task0 = await Task
.create({ title: 'a task', user_id: -1 }, { transaction: t });
const [task, user] = await Promise.all([task0, User.create({}, { transaction: t })]); describe('NOT', () => {
task.user_id = user.id; it('does not allow the violation of the foreign key constraint', async function() {
return task.save({ transaction: t }); await expect(this.run(Sequelize.Deferrable.NOT)).to.eventually.be.rejectedWith(Sequelize.ForeignKeyConstraintError);
});
}); });
};
});
describe('NOT', () => { describe('INITIALLY_IMMEDIATE', () => {
it('does not allow the violation of the foreign key constraint', async function() { it('allows the violation of the foreign key constraint if the transaction is deferred', async function() {
await expect(this.run(Sequelize.Deferrable.NOT)).to.eventually.be.rejectedWith(Sequelize.ForeignKeyConstraintError); const task = await this
}); .run(Sequelize.Deferrable.INITIALLY_IMMEDIATE);
});
describe('INITIALLY_IMMEDIATE', () => { expect(task.title).to.equal('a task');
it('allows the violation of the foreign key constraint if the transaction is deferred', async function() { expect(task.user_id).to.equal(1);
const task = await this });
.run(Sequelize.Deferrable.INITIALLY_IMMEDIATE);
expect(task.title).to.equal('a task'); it('does not allow the violation of the foreign key constraint if the transaction is not deffered', async function() {
expect(task.user_id).to.equal(1); await expect(this.run(Sequelize.Deferrable.INITIALLY_IMMEDIATE, {
}); deferrable: undefined
})).to.eventually.be.rejectedWith(Sequelize.ForeignKeyConstraintError);
});
it('does not allow the violation of the foreign key constraint if the transaction is not deffered', async function() { it('allows the violation of the foreign key constraint if the transaction deferres only the foreign key constraint', async function() {
await expect(this.run(Sequelize.Deferrable.INITIALLY_IMMEDIATE, { const taskTableName = `tasks_${config.rand()}`;
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', async function() { const task = await this
const taskTableName = `tasks_${config.rand()}`; .run(Sequelize.Deferrable.INITIALLY_IMMEDIATE, {
deferrable: Sequelize.Deferrable.SET_DEFERRED([`${taskTableName}_user_id_fkey`]),
taskTableName
});
const task = await this expect(task.title).to.equal('a task');
.run(Sequelize.Deferrable.INITIALLY_IMMEDIATE, { expect(task.user_id).to.equal(1);
deferrable: Sequelize.Deferrable.SET_DEFERRED([`${taskTableName}_user_id_fkey`]),
taskTableName
}); });
});
describe('INITIALLY_DEFERRED', () => {
it('allows the violation of the foreign key constraint', async function() {
const task = await this
.run(Sequelize.Deferrable.INITIALLY_DEFERRED);
expect(task.title).to.equal('a task'); expect(task.title).to.equal('a task');
expect(task.user_id).to.equal(1); expect(task.user_id).to.equal(1);
});
});
}); });
}); };
describeDeferrableTest('set in define', async ({ sequelize, userTableName, deferrable, taskTableName }) => {
const User = sequelize.define(
'User', { name: Sequelize.STRING }, { tableName: userTableName }
);
const Task = sequelize.define(
'Task', {
title: Sequelize.STRING,
user_id: {
allowNull: false,
type: Sequelize.INTEGER,
references: {
model: userTableName,
key: 'id',
deferrable
}
}
}, {
tableName: taskTableName
}
);
await User.sync({ force: true });
await Task.sync({ force: true });
describe('INITIALLY_DEFERRED', () => { return { Task, User };
it('allows the violation of the foreign key constraint', async function() { });
const task = await this
.run(Sequelize.Deferrable.INITIALLY_DEFERRED);
expect(task.title).to.equal('a task'); describeDeferrableTest('set in addConstraint', async ({ sequelize, userTableName, deferrable, taskTableName }) => {
expect(task.user_id).to.equal(1); const User = sequelize.define(
'User', { name: Sequelize.STRING }, { tableName: userTableName }
);
const Task = sequelize.define(
'Task', {
title: Sequelize.STRING,
user_id: {
allowNull: false,
type: Sequelize.INTEGER
}
}, {
tableName: taskTableName
}
);
await User.sync({ force: true });
await Task.sync({ force: true });
await sequelize.getQueryInterface().addConstraint(taskTableName, {
fields: ['user_id'],
type: 'foreign key',
name: `${taskTableName}_user_id_fkey`,
deferrable,
references: {
table: userTableName,
field: 'id'
}
}); });
return { Task, User };
}); });
}); });
}); });
...@@ -15,6 +15,7 @@ import { Sequelize, RetryOptions } from './sequelize'; ...@@ -15,6 +15,7 @@ import { Sequelize, RetryOptions } from './sequelize';
import { Transaction } from './transaction'; import { Transaction } from './transaction';
import { SetRequired } from './../type-helpers/set-required'; import { SetRequired } from './../type-helpers/set-required';
import { Fn, Literal } from './utils'; import { Fn, Literal } from './utils';
import { Deferrable } from './deferrable';
type BindOrReplacements = { [key: string]: unknown } | unknown[]; type BindOrReplacements = { [key: string]: unknown } | unknown[];
type FieldMap = { [key: string]: string }; type FieldMap = { [key: string]: string };
...@@ -216,6 +217,7 @@ export interface BaseConstraintOptions { ...@@ -216,6 +217,7 @@ export interface BaseConstraintOptions {
export interface AddUniqueConstraintOptions extends BaseConstraintOptions { export interface AddUniqueConstraintOptions extends BaseConstraintOptions {
type: 'unique'; type: 'unique';
deferrable?: Deferrable;
} }
export interface AddDefaultConstraintOptions extends BaseConstraintOptions { export interface AddDefaultConstraintOptions extends BaseConstraintOptions {
...@@ -230,6 +232,7 @@ export interface AddCheckConstraintOptions extends BaseConstraintOptions { ...@@ -230,6 +232,7 @@ export interface AddCheckConstraintOptions extends BaseConstraintOptions {
export interface AddPrimaryKeyConstraintOptions extends BaseConstraintOptions { export interface AddPrimaryKeyConstraintOptions extends BaseConstraintOptions {
type: 'primary key'; type: 'primary key';
deferrable?: Deferrable;
} }
export interface AddForeignKeyConstraintOptions extends BaseConstraintOptions { export interface AddForeignKeyConstraintOptions extends BaseConstraintOptions {
...@@ -240,6 +243,7 @@ export interface AddForeignKeyConstraintOptions extends BaseConstraintOptions { ...@@ -240,6 +243,7 @@ export interface AddForeignKeyConstraintOptions extends BaseConstraintOptions {
}; };
onDelete: string; onDelete: string;
onUpdate: string; onUpdate: string;
deferrable?: Deferrable;
} }
export type AddConstraintOptions = export type AddConstraintOptions =
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!