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

Commit 6730bc00 by Harshith Kashyap Committed by Sushant

addConstraint, removeConstraint (#7108)

* Added add, remove & show constraints feat

* Added showConstraintsQuery for postgres & mysql

* [ci skip] Added docs for constraints usage

* Added removeIndex support for mysql

* Added altering constraints to sqlite dialect

* Refactor onDelete, onUpdate to work in sqlite dialect

* Throw non existent constraints in removeConstraint query as UnknownConstraintError, improve test coverage

* Fix failing tests

* Removed dialect naming check in favor of dialect.supports feature check

* Improve code coverage, eslint fixes

* Review fixes

* Review fixes

* Renamed model, key to table, field for foreign key contraint definition
1 parent 2f7ac2d8
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
"prefer-const": "error", "prefer-const": "error",
"semi": ["error", "always"], "semi": ["error", "always"],
"space-before-function-paren": ["warn", "never"], "space-before-function-paren": ["warn", "never"],
"keyword-spacing": ["warn"],
"prefer-arrow-callback": "error", "prefer-arrow-callback": "error",
"comma-style": ["warn", "last"], "comma-style": ["warn", "last"],
"no-bitwise": "off", "no-bitwise": "off",
......
# Future # Future
- [FEATURE] `addConstraint`, `removeConstraint`, `showConstraint` [#7108](https://github.com/sequelize/sequelize/pull/7108)
- [FIXED] `changeColumn` generates incorrect query with ENUM type [#7455](https://github.com/sequelize/sequelize/pull/7455) - [FIXED] `changeColumn` generates incorrect query with ENUM type [#7455](https://github.com/sequelize/sequelize/pull/7455)
- [ADDED] `options.alter` to sequelize.sync() to alter existing tables.[#537](https://github.com/sequelize/sequelize/issues/537) - [ADDED] `options.alter` to sequelize.sync() to alter existing tables.[#537](https://github.com/sequelize/sequelize/issues/537)
- [ADDED] Ability to run transactions on a read-replica by marking transactions as read only [#7323](https://github.com/sequelize/sequelize/issues/7323) - [ADDED] Ability to run transactions on a read-replica by marking transactions as read only [#7323](https://github.com/sequelize/sequelize/issues/7323)
......
...@@ -306,6 +306,84 @@ queryInterface.removeIndex('Person', 'SuperDuperIndex') ...@@ -306,6 +306,84 @@ queryInterface.removeIndex('Person', 'SuperDuperIndex')
queryInterface.removeIndex('Person', ['firstname', 'lastname']) queryInterface.removeIndex('Person', ['firstname', 'lastname'])
``` ```
### addConstraint(tableName, attributes, options)
This method adds a new constraint of the specified type.
- tableName - Name of the table to add the constraint on
- attributes - Array of column names to apply the constraint over
- options - An object to define the constraint name, type etc.
Avalable options:
- type - Type of constraint. One of the values in available constraints(case insensitive)
- name - Name of the constraint. If not specifed, sequelize automatically creates a named constraint based on constraint type, table & column names
- defaultValue - The value for the default constraint
- where - Where clause/expression for the CHECK constraint
- references - Object specifying target table, column name to create foreign key constraint
- references.table - Target table name or table
- references.field - Target column name
Available constraints:
- UNIQUE
- DEFAULT (MSSQL only)
- CHECK (MySQL - Ignored by the database engine )
- FOREIGN KEY
- PRIMARY KEY
```js
//UNIQUE
queryInterface.addConstraint('Users', ['email'], {
type: 'unique',
name: 'custom_unique_constraint_name'
});
//CHECK
queryInterface.addConstraint('Users', ['roles'], {
type: 'check',
where: {
roles: ['user', 'admin', 'moderator', 'guest']
}
});
//Default - MSSQL only
queryInterface.addConstraint('Users', ['roles'], {
type: 'default',
defaultValue: 'guest'
});
//Primary Key
queryInterface.addConstraint('Users', ['username'], {
type: 'primary key',
name: 'custom_primary_constraint_name'
});
//Foreign Key
queryInterface.addConstraint('Posts', ['username'], {
type: 'FOREIGN KEY',
references: { //Required field
table: 'target_table_name',
field: 'target_column_name'
},
onDelete: 'cascade',
onUpdate: 'cascade'
});
```
### removeConstraint(tableName, constraintName, options)
This method deletes an existing constraint of a table
```js
queryInterface.removeConstraint('Users', 'my_constraint_name');
```
### showConstraint(tableName, options)
Lists all the constraints on the given table.
```js
queryInterface.showConstraint('Users');
// Returns array of objects/constraints
```
## Programmatic use ## Programmatic use
Sequelize has a [sister library](https://github.com/sequelize/umzug) for programmatically handling execution and logging of migration tasks. Sequelize has a [sister library](https://github.com/sequelize/umzug) for programmatically handling execution and logging of migration tasks.
......
...@@ -42,7 +42,14 @@ AbstractDialect.prototype.supports = { ...@@ -42,7 +42,14 @@ AbstractDialect.prototype.supports = {
migrations: true, migrations: true,
upserts: true, upserts: true,
constraints: { constraints: {
restrict: true restrict: true,
addConstraint: true,
dropConstraint: true,
unique: true,
default: false,
check: true,
foreignKey: true,
primaryKey: true
}, },
index: { index: {
collate: true, collate: true,
......
...@@ -562,6 +562,97 @@ const QueryGenerator = { ...@@ -562,6 +562,97 @@ const QueryGenerator = {
return _.compact(ind).join(' '); return _.compact(ind).join(' ');
}, },
addConstraintQuery(tableName, options) {
options = options || {};
const constraintSnippet = this.getConstraintSnippet(tableName, options);
if (typeof tableName === 'string') {
tableName = this.quoteIdentifiers(tableName);
} else {
tableName = this.quoteTable(tableName);
}
return `ALTER TABLE ${tableName} ADD ${constraintSnippet};`;
},
getConstraintSnippet(tableName, options) {
let constraintSnippet, constraintName;
const fieldsSql = options.fields.map(field => {
if (typeof field === 'string') {
return this.quoteIdentifier(field);
} else if (field._isSequelizeMethod) {
return this.handleSequelizeMethod(field);
} else {
let result = '';
if (field.attribute) {
field.name = field.attribute;
}
if (!field.name) {
throw new Error('The following index field has no name: ' + field);
}
result += this.quoteIdentifier(field.name);
return result;
}
});
const fieldsSqlQuotedString = fieldsSql.join(', ');
const fieldsSqlString = fieldsSql.join('_');
switch (options.type.toUpperCase()) {
case 'UNIQUE':
constraintName = this.quoteIdentifier(options.name || `${tableName}_${fieldsSqlString}_uk`);
constraintSnippet = `CONSTRAINT ${constraintName} UNIQUE (${fieldsSqlQuotedString})`;
break;
case 'CHECK':
options.where = this.whereItemsQuery(options.where);
constraintName = this.quoteIdentifier(options.name || `${tableName}_${fieldsSqlString}_ck`);
constraintSnippet = `CONSTRAINT ${constraintName} CHECK (${options.where})`;
break;
case 'DEFAULT':
if (options.defaultValue === undefined) {
throw new Error('Default value must be specifed for DEFAULT CONSTRAINT');
}
if (this._dialect.name !== 'mssql') {
throw new Error('Default constraints are supported only for MSSQL dialect.');
}
constraintName = this.quoteIdentifier(options.name || `${tableName}_${fieldsSqlString}_df`);
constraintSnippet = `CONSTRAINT ${constraintName} DEFAULT (${this.escape(options.defaultValue)}) FOR ${fieldsSql[0]}`;
break;
case 'PRIMARY KEY':
constraintName = this.quoteIdentifier(options.name || `${tableName}_${fieldsSqlString}_pk`);
constraintSnippet = `CONSTRAINT ${constraintName} PRIMARY KEY (${fieldsSqlQuotedString})`;
break;
case 'FOREIGN KEY':
const references = options.references;
if (!references || !references.table || !references.field) {
throw new Error('references object with table and field must be specified');
}
constraintName = this.quoteIdentifier(options.name || `${tableName}_${fieldsSqlString}_${references.table}_fk`);
const referencesSnippet = `${this.quoteTable(references.table)} (${this.quoteIdentifier(references.field)})`;
constraintSnippet = `CONSTRAINT ${constraintName} `;
constraintSnippet += `FOREIGN KEY (${fieldsSqlQuotedString}) REFERENCES ${referencesSnippet}`;
if (options.onUpdate) {
constraintSnippet += ` ON UPDATE ${options.onUpdate.toUpperCase()}`;
}
if (options.onDelete) {
constraintSnippet += ` ON DELETE ${options.onDelete.toUpperCase()}`;
}
break;
default: throw new Error(`${options.type} is invalid.`);
}
return constraintSnippet;
},
removeConstraintQuery(tableName, constraintName) {
return `ALTER TABLE ${this.quoteIdentifiers(tableName)} DROP CONSTRAINT ${this.quoteIdentifiers(constraintName)}`;
},
quoteTable(param, as) { quoteTable(param, as) {
let table = ''; let table = '';
......
...@@ -220,6 +220,10 @@ class AbstractQuery { ...@@ -220,6 +220,10 @@ class AbstractQuery {
return this.options.type === QueryTypes.SHOWINDEXES; return this.options.type === QueryTypes.SHOWINDEXES;
} }
isShowConstraintsQuery() {
return this.options.type === QueryTypes.SHOWCONSTRAINTS;
}
isDescribeQuery() { isDescribeQuery() {
return this.options.type === QueryTypes.DESCRIBE; return this.options.type === QueryTypes.DESCRIBE;
} }
......
...@@ -39,7 +39,8 @@ MssqlDialect.prototype.supports = _.merge(_.cloneDeep(AbstractDialect.prototype. ...@@ -39,7 +39,8 @@ MssqlDialect.prototype.supports = _.merge(_.cloneDeep(AbstractDialect.prototype.
update: false update: false
}, },
constraints: { constraints: {
restrict: false restrict: false,
default: true
}, },
index: { index: {
collate: false, collate: false,
......
...@@ -470,6 +470,10 @@ var QueryGenerator = { ...@@ -470,6 +470,10 @@ var QueryGenerator = {
}); });
}, },
showConstraintsQuery(tableName) {
return `EXEC sp_helpconstraint @objname = ${this.escape(this.quoteTable(tableName))};`;
},
removeIndexQuery(tableName, indexNameOrAttributes) { removeIndexQuery(tableName, indexNameOrAttributes) {
var sql = 'DROP INDEX <%= indexName %> ON <%= tableName %>' var sql = 'DROP INDEX <%= indexName %> ON <%= tableName %>'
, indexName = indexNameOrAttributes; , indexName = indexNameOrAttributes;
......
...@@ -194,6 +194,8 @@ class Query extends AbstractQuery { ...@@ -194,6 +194,8 @@ class Query extends AbstractQuery {
result = data; result = data;
} else if (this.isInsertQuery() || this.isUpdateQuery()) { } else if (this.isInsertQuery() || this.isUpdateQuery()) {
result = [result, rowCount]; result = [result, rowCount];
} else if (this.isShowConstraintsQuery()) {
result = this.handleShowConstraintsQuery(data);
} else if (this.isRawQuery()) { } else if (this.isRawQuery()) {
// MSSQL returns row data and metadata (affected rows etc) in a single object - let's standarize it, sorta // MSSQL returns row data and metadata (affected rows etc) in a single object - let's standarize it, sorta
result = [data, data]; result = [data, data];
...@@ -211,6 +213,17 @@ class Query extends AbstractQuery { ...@@ -211,6 +213,17 @@ class Query extends AbstractQuery {
}); });
} }
handleShowConstraintsQuery(data) {
//Convert snake_case keys to camelCase as its generated by stored procedure
return data.slice(1).map(result => {
const constraint = {};
for (const key in result) {
constraint[_.camelCase(key)] = result[key];
}
return constraint;
});
}
formatError(err) { formatError(err) {
let match; let match;
match = err.message.match(/Violation of UNIQUE KEY constraint '((.|\s)*)'. Cannot insert duplicate key in object '.*'.(:? The duplicate key value is \((.*)\).)?/); match = err.message.match(/Violation of UNIQUE KEY constraint '((.|\s)*)'. Cannot insert duplicate key in object '.*'.(:? The duplicate key value is \((.*)\).)?/);
...@@ -256,6 +269,12 @@ class Query extends AbstractQuery { ...@@ -256,6 +269,12 @@ class Query extends AbstractQuery {
}); });
} }
match = err.message.match(/Could not drop constraint. See previous errors./);
if (match && match.length > 0) {
return new sequelizeErrors.UnknownConstraintError(match[1]);
}
return new sequelizeErrors.DatabaseError(err); return new sequelizeErrors.DatabaseError(err);
} }
......
...@@ -33,6 +33,10 @@ MysqlDialect.prototype.supports = _.merge(_.cloneDeep(AbstractDialect.prototype. ...@@ -33,6 +33,10 @@ MysqlDialect.prototype.supports = _.merge(_.cloneDeep(AbstractDialect.prototype.
type: true, type: true,
using: 1 using: 1
}, },
constraints: {
dropConstraint: false,
check: false
},
ignoreDuplicates: ' IGNORE', ignoreDuplicates: ' IGNORE',
updateOnDuplicate: true, updateOnDuplicate: true,
indexViaAlter: true, indexViaAlter: true,
......
...@@ -193,6 +193,25 @@ const QueryGenerator = { ...@@ -193,6 +193,25 @@ const QueryGenerator = {
return 'SHOW INDEX FROM ' + this.quoteTable(tableName) + ((options || {}).database ? ' FROM `' + options.database + '`' : ''); return 'SHOW INDEX FROM ' + this.quoteTable(tableName) + ((options || {}).database ? ' FROM `' + options.database + '`' : '');
}, },
showConstraintsQuery(tableName, constraintName) {
let sql = [
'SELECT CONSTRAINT_CATALOG AS constraintCatalog,',
'CONSTRAINT_NAME AS constraintName,',
'CONSTRAINT_SCHEMA AS constraintSchema,',
'CONSTRAINT_TYPE AS constraintType,',
'TABLE_NAME AS tableName,',
'TABLE_SCHEMA AS tableSchema',
'from INFORMATION_SCHEMA.TABLE_CONSTRAINTS',
`WHERE table_name='${tableName}'`
].join(' ');
if (constraintName) {
sql += ` AND constraint_name = '${constraintName}'`;
}
return sql + ';';
},
removeIndexQuery(tableName, indexNameOrAttributes) { removeIndexQuery(tableName, indexNameOrAttributes) {
let indexName = indexNameOrAttributes; let indexName = indexNameOrAttributes;
......
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
*/ */
const _ = require('lodash'); const _ = require('lodash');
const UnknownConstraintError = require('../../errors').UnknownConstraintError;
/** /**
A wrapper that fixes MySQL's inability to cleanly remove columns from existing tables if they have a foreign key constraint. A wrapper that fixes MySQL's inability to cleanly remove columns from existing tables if they have a foreign key constraint.
...@@ -45,4 +46,29 @@ function removeColumn(tableName, columnName, options) { ...@@ -45,4 +46,29 @@ function removeColumn(tableName, columnName, options) {
_.assign({ raw: true }, options) _.assign({ raw: true }, options)
)); ));
} }
function removeConstraint(tableName, constraintName, options) {
/* jshint validthis:true */
const sql = this.QueryGenerator.showConstraintsQuery(tableName, constraintName);
return this.sequelize.query(sql, Object.assign({}, options, { type: this.sequelize.QueryTypes.SHOWCONSTRAINTS }))
.then(constraints => {
const constraint = constraints[0];
let query;
if (constraint && constraint.constraintType) {
if (constraint.constraintType === 'FOREIGN KEY') {
query = this.QueryGenerator.dropForeignKeyQuery(tableName, constraintName);
} else {
query = this.QueryGenerator.removeIndexQuery(constraint.tableName, constraint.constraintName);
}
} else {
throw new UnknownConstraintError(`Constraint ${constraintName} on table ${tableName} does not exist`);
}
return this.sequelize.query(query, options);
});
}
exports.removeConstraint = removeConstraint;
exports.removeColumn = removeColumn; exports.removeColumn = removeColumn;
...@@ -141,6 +141,8 @@ class Query extends AbstractQuery { ...@@ -141,6 +141,8 @@ class Query extends AbstractQuery {
result = data; result = data;
} else if (this.isInsertQuery() || this.isUpdateQuery()) { } else if (this.isInsertQuery() || this.isUpdateQuery()) {
result = [result, data.affectedRows]; result = [result, data.affectedRows];
} else if (this.isShowConstraintsQuery()) {
result = data;
} else if (this.isRawQuery()) { } else if (this.isRawQuery()) {
// MySQL returns row data and metadata (affected rows etc) in a single object - let's standarize it, sorta // MySQL returns row data and metadata (affected rows etc) in a single object - let's standarize it, sorta
result = [data, data]; result = [data, data];
......
...@@ -443,6 +443,23 @@ const QueryGenerator = { ...@@ -443,6 +443,23 @@ const QueryGenerator = {
'GROUP BY i.relname, ix.indexrelid, ix.indisprimary, ix.indisunique, ix.indkey ORDER BY i.relname;'; 'GROUP BY i.relname, ix.indexrelid, ix.indisprimary, ix.indisunique, ix.indkey ORDER BY i.relname;';
}, },
showConstraintsQuery(tableName) {
//Postgres converts camelCased alias to lowercase unless quoted
return [
'SELECT constraint_catalog AS "constraintCatalog",',
'constraint_schema AS "constraintSchema",',
'constraint_name AS "constraintName",',
'table_catalog AS "tableCatalog",',
'table_schema AS "tableSchema",',
'table_name AS "tableName",',
'constraint_type AS "constraintType",',
'is_deferrable AS "isDeferrable",',
'initially_deferred AS "initiallyDeferred"',
'from INFORMATION_SCHEMA.table_constraints',
`WHERE table_name='${tableName}';`
].join(' ');
},
removeIndexQuery(tableName, indexNameOrAttributes) { removeIndexQuery(tableName, indexNameOrAttributes) {
let indexName = indexNameOrAttributes; let indexName = indexNameOrAttributes;
......
...@@ -32,6 +32,10 @@ SqliteDialect.prototype.supports = _.merge(_.cloneDeep(AbstractDialect.prototype ...@@ -32,6 +32,10 @@ SqliteDialect.prototype.supports = _.merge(_.cloneDeep(AbstractDialect.prototype
type: true, type: true,
autocommit: false autocommit: false
}, },
constraints: {
addConstraint: false,
dropConstraint: false
},
joinTableDependent: false, joinTableDependent: false,
groupedLimit: false, groupedLimit: false,
ignoreDuplicates: ' OR IGNORE', ignoreDuplicates: ' OR IGNORE',
......
...@@ -339,6 +339,16 @@ const QueryGenerator = { ...@@ -339,6 +339,16 @@ const QueryGenerator = {
return `PRAGMA INDEX_LIST(${this.quoteTable(tableName)})`; return `PRAGMA INDEX_LIST(${this.quoteTable(tableName)})`;
}, },
showConstraintsQuery(tableName, constraintName) {
let sql = `SELECT sql FROM sqlite_master WHERE tbl_name='${tableName}'`;
if (constraintName) {
sql += ` AND sql LIKE '%${constraintName}%'`;
}
return sql + ';';
},
removeIndexQuery(tableName, indexNameOrAttributes) { removeIndexQuery(tableName, indexNameOrAttributes) {
let indexName = indexNameOrAttributes; let indexName = indexNameOrAttributes;
...@@ -358,6 +368,10 @@ const QueryGenerator = { ...@@ -358,6 +368,10 @@ const QueryGenerator = {
return `PRAGMA TABLE_INFO(${this.quoteTable(this.addSchema(table))});`; return `PRAGMA TABLE_INFO(${this.quoteTable(this.addSchema(table))});`;
}, },
describeCreateTableQuery(tableName) {
return `SELECT sql FROM sqlite_master WHERE tbl_name='${tableName}';`;
},
removeColumnQuery(tableName, attributes) { removeColumnQuery(tableName, attributes) {
attributes = this.attributesToSQL(attributes); attributes = this.attributesToSQL(attributes);
...@@ -384,6 +398,29 @@ const QueryGenerator = { ...@@ -384,6 +398,29 @@ const QueryGenerator = {
+ `DROP TABLE ${quotedBackupTableName};`; + `DROP TABLE ${quotedBackupTableName};`;
}, },
_alterConstraintQuery(tableName, attributes, createTableSql) {
let backupTableName;
attributes = this.attributesToSQL(attributes);
if (typeof tableName === 'object') {
backupTableName = {
tableName: tableName.tableName + '_backup',
schema: tableName.schema
};
} else {
backupTableName = tableName + '_backup';
}
const quotedTableName = this.quoteTable(tableName);
const quotedBackupTableName = this.quoteTable(backupTableName);
const attributeNames = Object.keys(attributes).join(', ');
return createTableSql.replace(`CREATE TABLE ${quotedTableName}`, `CREATE TABLE ${quotedBackupTableName}`)
+ `INSERT INTO ${quotedBackupTableName} SELECT ${attributeNames} FROM ${quotedTableName};`
+ `DROP TABLE ${quotedTableName};`
+ `ALTER TABLE ${quotedBackupTableName} RENAME TO ${quotedTableName};`;
},
renameColumnQuery(tableName, attrNameBefore, attrNameAfter, attributes) { renameColumnQuery(tableName, attrNameBefore, attrNameAfter, attributes) {
let backupTableName; let backupTableName;
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
const Utils = require('../../utils'); const Utils = require('../../utils');
const Promise = require('../../promise'); const Promise = require('../../promise');
const UnknownConstraintError = require('../../errors').UnknownConstraintError;
/** /**
Returns an object that treats SQLite's inabilities to do certain queries. Returns an object that treats SQLite's inabilities to do certain queries.
...@@ -106,3 +107,66 @@ function renameColumn(tableName, attrNameBefore, attrNameAfter, options) { ...@@ -106,3 +107,66 @@ function renameColumn(tableName, attrNameBefore, attrNameAfter, options) {
}); });
} }
exports.renameColumn = renameColumn; exports.renameColumn = renameColumn;
function removeConstraint(tableName, constraintName, options) {
let createTableSql;
/* jshint validthis:true */
return this.showConstraint(tableName, constraintName)
.then(constraints => {
const constraint = constraints[0];
if (constraint) {
createTableSql = constraint.sql;
constraint.constraintName = this.QueryGenerator.quoteIdentifier(constraint.constraintName);
let constraintSnippet = `, CONSTRAINT ${constraint.constraintName} ${constraint.constraintType} ${constraint.constraintCondition}`;
if (constraint.constraintType === 'FOREIGN KEY') {
const referenceTableName = this.QueryGenerator.quoteTable(constraint.referenceTableName);
constraint.referenceTableKeys = constraint.referenceTableKeys.map(columnName => this.QueryGenerator.quoteIdentifier(columnName));
const referenceTableKeys = constraint.referenceTableKeys.join(', ');
constraintSnippet += ` REFERENCES ${referenceTableName} (${referenceTableKeys})`;
constraintSnippet += ` ON UPDATE ${constraint.updateAction}`;
constraintSnippet += ` ON DELETE ${constraint.deleteAction}`;
}
createTableSql = createTableSql.replace(constraintSnippet, '');
createTableSql += ';';
return this.describeTable(tableName, options);
} else {
throw new UnknownConstraintError(`Constraint ${constraintName} on table ${tableName} does not exist`);
}
})
.then(fields => {
const sql = this.QueryGenerator._alterConstraintQuery(tableName, fields, createTableSql);
const subQueries = sql.split(';').filter(q => q !== '');
return Promise.each(subQueries, subQuery => this.sequelize.query(subQuery + ';', Utils._.assign({raw: true}, options)));
});
}
exports.removeConstraint = removeConstraint;
function addConstraint(tableName, options) {
/* jshint validthis:true */
const constraintSnippet = this.QueryGenerator.getConstraintSnippet(tableName, options);
const describeCreateTableSql = this.QueryGenerator.describeCreateTableQuery(tableName);
let createTableSql;
return this.sequelize.query(describeCreateTableSql, options)
.then(constraints => {
const sql = constraints[0].sql;
const index = sql.length - 1;
//Replace ending ')' with constraint snippet - Simulates String.replaceAt
//http://stackoverflow.com/questions/1431094
createTableSql = sql.substr(0, index) + `, ${constraintSnippet})` + sql.substr(index + 1) + ';';
return this.describeTable(tableName, options);
})
.then(fields => {
const sql = this.QueryGenerator._alterConstraintQuery(tableName, fields, createTableSql);
const subQueries = sql.split(';').filter(q => q !== '');
return Promise.each(subQueries, subQuery => this.sequelize.query(subQuery + ';', Utils._.assign({raw: true}, options)));
});
}
exports.addConstraint = addConstraint;
\ No newline at end of file
...@@ -147,7 +147,14 @@ class Query extends AbstractQuery { ...@@ -147,7 +147,14 @@ class Query extends AbstractQuery {
} }
if (query.sql.indexOf('sqlite_master') !== -1) { if (query.sql.indexOf('sqlite_master') !== -1) {
result = results.map(resultSet => resultSet.name); if (query.sql.indexOf('SELECT sql FROM sqlite_master WHERE tbl_name') !== -1) {
result = results;
if (result && result[0] && result[0].sql.indexOf('CONSTRAINT') !== -1) {
result = query.parseConstraintsFromSql(results[0].sql);
}
} else {
result = results.map(resultSet => resultSet.name);
}
} else if (query.isSelectQuery()) { } else if (query.isSelectQuery()) {
if (!query.options.raw) { if (!query.options.raw) {
// This is a map of prefix strings to models, e.g. user.projects -> Project model // This is a map of prefix strings to models, e.g. user.projects -> Project model
...@@ -295,6 +302,56 @@ class Query extends AbstractQuery { ...@@ -295,6 +302,56 @@ class Query extends AbstractQuery {
}); });
} }
parseConstraintsFromSql(sql) {
let constraints = sql.split('CONSTRAINT ');
let referenceTableName, referenceTableKeys, updateAction, deleteAction;
constraints.splice(0, 1);
constraints = constraints.map(constraintSql => {
//Parse foreign key snippets
if (constraintSql.indexOf('REFERENCES') !== -1) {
//Parse out the constraint condition form sql string
updateAction = constraintSql.match(/ON UPDATE (CASCADE|SET NULL|RESTRICT|NO ACTION|SET DEFAULT){1}/);
deleteAction = constraintSql.match(/ON DELETE (CASCADE|SET NULL|RESTRICT|NO ACTION|SET DEFAULT){1}/);
if (updateAction) {
updateAction = updateAction[1];
}
if (deleteAction) {
deleteAction = deleteAction[1];
}
const referencesRegex = /REFERENCES.+\((?:[^)(]+|\((?:[^)(]+|\([^)(]*\))*\))*\)/;
const referenceConditions = constraintSql.match(referencesRegex)[0].split(' ');
referenceTableName = Utils.removeTicks(referenceConditions[1]);
let columnNames = referenceConditions[2];
columnNames = columnNames.replace(/\(|\)/g, '').split(', ');
referenceTableKeys = columnNames.map(column => Utils.removeTicks(column));
}
const constraintCondition = constraintSql.match(/\((?:[^)(]+|\((?:[^)(]+|\([^)(]*\))*\))*\)/)[0];
constraintSql = constraintSql.replace(/\(.+\)/, '');
const constraint = constraintSql.split(' ');
if (constraint[1] === 'PRIMARY' || constraint[1] === 'FOREIGN') {
constraint[1]+= ' KEY';
}
return {
constraintName: Utils.removeTicks(constraint[0]),
constraintType: constraint[1],
updateAction,
deleteAction,
sql: sql.replace(/\"/g, '\`'), //Sqlite returns double quotes for table name
constraintCondition,
referenceTableName,
referenceTableKeys
};
});
return constraints;
}
applyParsers(type, value) { applyParsers(type, value) {
if (type.indexOf('(') !== -1) { if (type.indexOf('(') !== -1) {
// Remove the length part // Remove the length part
...@@ -371,7 +428,7 @@ class Query extends AbstractQuery { ...@@ -371,7 +428,7 @@ class Query extends AbstractQuery {
item.fields = []; item.fields = [];
item.primary = false; item.primary = false;
item.unique = !!item.unique; item.unique = !!item.unique;
item.constraintName = item.name;
return this.run('PRAGMA INDEX_INFO(`' + item.name + '`)').then(columns => { return this.run('PRAGMA INDEX_INFO(`' + item.name + '`)').then(columns => {
for (const column of columns) { for (const column of columns) {
item.fields[column.seqno] = { item.fields[column.seqno] = {
......
...@@ -214,6 +214,19 @@ class ExclusionConstraintError extends DatabaseError { ...@@ -214,6 +214,19 @@ class ExclusionConstraintError extends DatabaseError {
exports.ExclusionConstraintError = ExclusionConstraintError; exports.ExclusionConstraintError = ExclusionConstraintError;
/** /**
* Thrown when constraint name is not found in the database
*/
class UnknownConstraintError extends DatabaseError {
constructor(message) {
const parent = { message };
super(parent);
this.name = 'SequelizeUnknownConstraintError';
this.message = message || 'The specified constraint does not exist';
}
}
exports.UnknownConstraintError = UnknownConstraintError;
/**
* Validation Error Item * Validation Error Item
* Instances of this class are included in the `ValidationError.errors` property. * Instances of this class are included in the `ValidationError.errors` property.
* *
......
...@@ -466,6 +466,53 @@ class QueryInterface { ...@@ -466,6 +466,53 @@ class QueryInterface {
return this.sequelize.query(sql, options); return this.sequelize.query(sql, options);
} }
addConstraint(tableName, attributes, options, rawTablename) {
if (!Array.isArray(attributes)) {
rawTablename = options;
options = attributes;
attributes = options.fields;
}
if (!options.type) {
throw new Error('Constraint type must be specified through options.type');
}
if (!rawTablename) {
// Map for backwards compat
rawTablename = tableName;
}
options = Utils.cloneDeep(options);
options.fields = attributes;
if (this.sequelize.dialect.name === 'sqlite') {
return SQLiteQueryInterface.addConstraint.call(this, tableName, options, rawTablename);
} else {
const sql = this.QueryGenerator.addConstraintQuery(tableName, options, rawTablename);
return this.sequelize.query(sql, options);
}
}
showConstraint(tableName, options) {
const sql = this.QueryGenerator.showConstraintsQuery(tableName, options);
return this.sequelize.query(sql, Object.assign({}, options, { type: QueryTypes.SHOWCONSTRAINTS }));
}
removeConstraint(tableName, constraintName, options) {
options = options || {};
switch (this.sequelize.options.dialect) {
case 'mysql':
//Mysql does not support DROP CONSTRAINT. Instead DROP PRIMARY, FOREIGN KEY, INDEX should be used
return MySQLQueryInterface.removeConstraint.call(this, tableName, constraintName, options);
case 'sqlite':
return SQLiteQueryInterface.removeConstraint.call(this, tableName, constraintName, options);
default:
const sql = this.QueryGenerator.removeConstraintQuery(tableName, constraintName);
return this.sequelize.query(sql, options);
}
}
insert(instance, tableName, values, options) { insert(instance, tableName, values, options) {
options = Utils.cloneDeep(options); options = Utils.cloneDeep(options);
options.hasTrigger = instance && instance.constructor.options.hasTrigger; options.hasTrigger = instance && instance.constructor.options.hasTrigger;
......
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
* @property DESCRIBE * @property DESCRIBE
* @property RAW * @property RAW
* @property FOREIGNKEYS * @property FOREIGNKEYS
* @property SHOWCONSTRAINTS
*/ */
module.exports = { module.exports = {
SELECT: 'SELECT', SELECT: 'SELECT',
...@@ -32,5 +33,6 @@ module.exports = { ...@@ -32,5 +33,6 @@ module.exports = {
SHOWINDEXES: 'SHOWINDEXES', SHOWINDEXES: 'SHOWINDEXES',
DESCRIBE: 'DESCRIBE', DESCRIBE: 'DESCRIBE',
RAW: 'RAW', RAW: 'RAW',
FOREIGNKEYS: 'FOREIGNKEYS' FOREIGNKEYS: 'FOREIGNKEYS',
SHOWCONSTRAINTS: 'SHOWCONSTRAINTS'
}; };
...@@ -6,6 +6,8 @@ const expect = chai.expect; ...@@ -6,6 +6,8 @@ const expect = chai.expect;
const Support = require(__dirname + '/support'); const Support = require(__dirname + '/support');
const DataTypes = require(__dirname + '/../../lib/data-types'); const DataTypes = require(__dirname + '/../../lib/data-types');
const dialect = Support.getTestDialect(); const dialect = Support.getTestDialect();
const Sequelize = Support.Sequelize;
const current = Support.sequelize;
const _ = require('lodash'); const _ = require('lodash');
let count = 0; let count = 0;
const log = function() { const log = function() {
...@@ -878,4 +880,169 @@ describe(Support.getTestDialectTeaser('QueryInterface'), function() { ...@@ -878,4 +880,169 @@ describe(Support.getTestDialectTeaser('QueryInterface'), function() {
}); });
}); });
}); });
describe('constraints', function() {
beforeEach(function() {
this.User = this.sequelize.define('users', {
username: DataTypes.STRING,
email: DataTypes.STRING,
roles: DataTypes.STRING
});
this.Post = this.sequelize.define('posts', {
username: DataTypes.STRING
});
return this.sequelize.sync({ force: true });
});
describe('unique', function() {
it('should add, read & remove unique constraint', function() {
return this.queryInterface.addConstraint('users', ['email'], {
type: 'unique'
})
.then(() => this.queryInterface.showConstraint('users'))
.then(constraints => {
constraints = constraints.map(constraint => constraint.constraintName);
expect(constraints).to.include('users_email_uk');
return this.queryInterface.removeConstraint('users', 'users_email_uk');
})
.then(() => this.queryInterface.showConstraint('users'))
.then(constraints => {
constraints = constraints.map(constraint => constraint.constraintName);
expect(constraints).to.not.include('users_email_uk');
});
});
});
if (current.dialect.supports.constraints.check) {
describe('check', function() {
it('should add, read & remove check constraint', function() {
return this.queryInterface.addConstraint('users', ['roles'], {
type: 'check',
where: {
roles: ['user', 'admin', 'guest', 'moderator']
},
name: 'check_user_roles'
})
.then(() => this.queryInterface.showConstraint('users'))
.then(constraints => {
constraints = constraints.map(constraint => constraint.constraintName);
expect(constraints).to.include('check_user_roles');
return this.queryInterface.removeConstraint('users', 'check_user_roles');
})
.then(() => this.queryInterface.showConstraint('users'))
.then(constraints => {
constraints = constraints.map(constraint => constraint.constraintName);
expect(constraints).to.not.include('check_user_roles');
});
});
});
}
if (current.dialect.supports.constraints.default) {
describe('default', function() {
it('should add, read & remove default constraint', function() {
return this.queryInterface.addConstraint('users', ['roles'], {
type: 'default',
defaultValue: 'guest'
})
.then(() => this.queryInterface.showConstraint('users'))
.then(constraints => {
constraints = constraints.map(constraint => constraint.constraintName);
expect(constraints).to.include('users_roles_df');
return this.queryInterface.removeConstraint('users', 'users_roles_df');
})
.then(() => this.queryInterface.showConstraint('users'))
.then(constraints => {
constraints = constraints.map(constraint => constraint.constraintName);
expect(constraints).to.not.include('users_roles_df');
});
});
});
}
describe('primary key', function() {
it('should add, read & remove primary key constraint', function() {
return this.queryInterface.removeColumn('users', 'id')
.then(() => {
return this.queryInterface.changeColumn('users', 'username', {
type: DataTypes.STRING,
allowNull: false
});
})
.then(() => {
return this.queryInterface.addConstraint('users', ['username'], {
type: 'PRIMARY KEY'
});
})
.then(() => this.queryInterface.showConstraint('users'))
.then(constraints => {
constraints = constraints.map(constraint => constraint.constraintName);
//The name of primaryKey constraint is always PRIMARY in case of mysql
if (dialect === 'mysql') {
expect(constraints).to.include('PRIMARY');
return this.queryInterface.removeConstraint('users', 'PRIMARY');
} else {
expect(constraints).to.include('users_username_pk');
return this.queryInterface.removeConstraint('users', 'users_username_pk');
}
})
.then(() => this.queryInterface.showConstraint('users'))
.then(constraints => {
constraints = constraints.map(constraint => constraint.constraintName);
expect(constraints).to.not.include('users_username_pk');
});
});
});
describe('foreign key', function() {
it('should add, read & remove foreign key constraint', function() {
return this.queryInterface.removeColumn('users', 'id')
.then(() => {
return this.queryInterface.changeColumn('users', 'username', {
type: DataTypes.STRING,
allowNull: false
});
})
.then(() => {
return this.queryInterface.addConstraint('users', {
type: 'PRIMARY KEY',
fields: ['username']
});
})
.then(() => {
return this.queryInterface.addConstraint('posts', ['username'], {
references: {
table: 'users',
field: 'username'
},
onDelete: 'cascade',
onUpdate: 'cascade',
type: 'foreign key'
});
})
.then(() => this.queryInterface.showConstraint('posts'))
.then(constraints => {
constraints = constraints.map(constraint => constraint.constraintName);
expect(constraints).to.include('posts_username_users_fk');
return this.queryInterface.removeConstraint('posts', 'posts_username_users_fk');
})
.then(() => this.queryInterface.showConstraint('posts'))
.then(constraints => {
constraints = constraints.map(constraint => constraint.constraintName);
expect(constraints).to.not.include('posts_username_users_fk');
});
});
});
describe('error handling', function() {
it('should throw non existent constraints as UnknownConstraintError', function() {
return expect(this.queryInterface.removeConstraint('users', 'unknown__contraint__name', {
type: 'unique'
})).to.eventually.be.rejectedWith(Sequelize.UnknownConstraintError);
});
});
});
}); });
'use strict';
/* jshint -W030, -W110 */
const Support = require(__dirname + '/../support');
const current = Support.sequelize;
const expectsql = Support.expectsql;
const sql = current.dialect.QueryGenerator;
const expect = require('chai').expect;
const sinon = require('sinon');
if (current.dialect.supports.constraints.addConstraint) {
describe(Support.getTestDialectTeaser('SQL'), function() {
describe('addConstraint', function() {
describe('unique', function() {
it('naming', function() {
expectsql(sql.addConstraintQuery('myTable', {
name: 'unique_mytable_mycolumn',
type: 'UNIQUE',
fields: ['myColumn']
}), {
default: 'ALTER TABLE [myTable] ADD CONSTRAINT [unique_mytable_mycolumn] UNIQUE ([myColumn]);'
});
});
it('should create constraint name if not passed', function() {
expectsql(sql.addConstraintQuery('myTable', {
type: 'UNIQUE',
fields: ['myColumn']
}), {
default: 'ALTER TABLE [myTable] ADD CONSTRAINT [myTable_myColumn_uk] UNIQUE ([myColumn]);'
});
});
it('should work with multiple columns', function() {
expectsql(sql.addConstraintQuery('myTable', {
type: 'UNIQUE',
fields: ['myColumn1', 'myColumn2']
}), {
default: 'ALTER TABLE [myTable] ADD CONSTRAINT [myTable_myColumn1_myColumn2_uk] UNIQUE ([myColumn1], [myColumn2]);'
});
});
});
describe('check', function() {
it('naming', function() {
expectsql(sql.addConstraintQuery('myTable', {
type: 'CHECK',
fields: ['myColumn'],
where: {
myColumn: ['value1', 'value2', 'value3']
}
}), {
mssql: "ALTER TABLE [myTable] ADD CONSTRAINT [myTable_myColumn_ck] CHECK ([myColumn] IN (N'value1', N'value2', N'value3'));",
default: "ALTER TABLE [myTable] ADD CONSTRAINT [myTable_myColumn_ck] CHECK ([myColumn] IN ('value1', 'value2', 'value3'));"
});
});
it('where', function() {
expectsql(sql.addConstraintQuery('myTable', {
type: 'CHECK',
fields: ['myColumn'],
name: 'check_mycolumn_where',
where: {
myColumn: {
$and: {
$gt: 50,
$lt: 100
}
}
}
}), {
default: 'ALTER TABLE [myTable] ADD CONSTRAINT [check_mycolumn_where] CHECK (([myColumn] > 50 AND [myColumn] < 100));'
});
});
});
if (current.dialect.supports.constraints.default) {
describe('default', function() {
it('naming', function() {
expectsql(sql.addConstraintQuery('myTable', {
type: 'default',
fields: ['myColumn'],
defaultValue: 0
}), {
mssql: 'ALTER TABLE [myTable] ADD CONSTRAINT [myTable_myColumn_df] DEFAULT (0) FOR [myColumn];'
});
});
it('string', function() {
expectsql(sql.addConstraintQuery('myTable', {
type: 'default',
fields: ['myColumn'],
defaultValue: 'some default value',
name: 'default_mytable_null'
}), {
mssql: "ALTER TABLE [myTable] ADD CONSTRAINT [default_mytable_null] DEFAULT (N'some default value') FOR [myColumn];"
});
});
it('validation', function() {
expect(sql.addConstraintQuery.bind(sql, {
tableName: 'myTable',
schema: 'mySchema'
}, {
type: 'default',
fields: [{
attribute: 'myColumn'
}]
})).to.throw('Default value must be specifed for DEFAULT CONSTRAINT');
});
});
}
describe('primary key', function() {
it('naming', function() {
expectsql(sql.addConstraintQuery('myTable', {
name: 'primary_mytable_mycolumn',
type: 'primary key',
fields: ['myColumn']
}), {
default: 'ALTER TABLE [myTable] ADD CONSTRAINT [primary_mytable_mycolumn] PRIMARY KEY ([myColumn]);'
});
});
it('should create constraint name if not passed', function() {
expectsql(sql.addConstraintQuery('myTable', {
type: 'PRIMARY KEY',
fields: ['myColumn']
}), {
default: 'ALTER TABLE [myTable] ADD CONSTRAINT [myTable_myColumn_pk] PRIMARY KEY ([myColumn]);'
});
});
it('should work with multiple columns', function() {
expectsql(sql.addConstraintQuery('myTable', {
type: 'PRIMARY KEY',
fields: ['myColumn1', 'myColumn2']
}), {
default: 'ALTER TABLE [myTable] ADD CONSTRAINT [myTable_myColumn1_myColumn2_pk] PRIMARY KEY ([myColumn1], [myColumn2]);'
});
});
});
describe('foreign key', function() {
it('naming', function() {
expectsql(sql.addConstraintQuery('myTable', {
name: 'foreignkey_mytable_mycolumn',
type: 'foreign key',
fields: ['myColumn'],
references: {
table: 'myOtherTable',
field: 'id'
}
}), {
default: 'ALTER TABLE [myTable] ADD CONSTRAINT [foreignkey_mytable_mycolumn] FOREIGN KEY ([myColumn]) REFERENCES [myOtherTable] ([id]);'
});
});
it('uses onDelete, onUpdate', function() {
expectsql(sql.addConstraintQuery('myTable', {
type: 'foreign key',
fields: ['myColumn'],
references: {
table: 'myOtherTable',
field: 'id'
},
onUpdate: 'cascade',
onDelete: 'cascade'
}), {
default: 'ALTER TABLE [myTable] ADD CONSTRAINT [myTable_myColumn_myOtherTable_fk] FOREIGN KEY ([myColumn]) REFERENCES [myOtherTable] ([id]) ON UPDATE CASCADE ON DELETE CASCADE;'
});
});
it('errors if references object is not passed', function() {
expect(sql.addConstraintQuery.bind(sql, 'myTable', {
type: 'foreign key',
fields: ['myColumn']
})).to.throw('references object with table and field must be specified');
});
});
describe('validation', function() {
it('throw error on invalid type', function() {
expect(sql.addConstraintQuery.bind(sql, 'myTable', { type: 'some type', fields: [] })).to.throw('some type is invalid');
});
it('calls getConstraintSnippet function', function() {
const options = { type: 'unique', fields: ['myColumn'] };
const addConstraintQuerySpy = sinon.stub(sql, 'addConstraintQuery');
sql.addConstraintQuery('myTable', options);
expect(sql.addConstraintQuery).to.have.been.calledWith('myTable', options);
addConstraintQuerySpy.restore();
});
if (!current.dialect.supports.constraints.default) {
it('should throw error if default constraints are used in other dialects', function() {
expect(sql.addConstraintQuery.bind(sql, 'myTable', { type: 'default', defaultValue: 0, fields: [] })).to.throw('Default constraints are supported only for MSSQL dialect.');
});
}
});
});
});
}
'use strict';
/* jshint -W030, -W110 */
const Support = require(__dirname + '/../support');
const current = Support.sequelize;
const expectsql = Support.expectsql;
const sql = current.dialect.QueryGenerator;
const expect = require('chai').expect;
describe(Support.getTestDialectTeaser('SQL'), function() {
describe('getConstraintSnippet', function() {
describe('unique', function() {
it('naming', function() {
expectsql(sql.getConstraintSnippet('myTable', {
name: 'unique_mytable_mycolumn',
type: 'UNIQUE',
fields: ['myColumn']
}), {
default: 'CONSTRAINT [unique_mytable_mycolumn] UNIQUE ([myColumn])'
});
});
it('should create constraint name if not passed', function() {
expectsql(sql.getConstraintSnippet('myTable', {
type: 'UNIQUE',
fields: ['myColumn']
}), {
default: 'CONSTRAINT [myTable_myColumn_uk] UNIQUE ([myColumn])'
});
});
it('should work with multiple columns', function() {
expectsql(sql.getConstraintSnippet('myTable', {
type: 'UNIQUE',
fields: ['myColumn1', 'myColumn2']
}), {
default: 'CONSTRAINT [myTable_myColumn1_myColumn2_uk] UNIQUE ([myColumn1], [myColumn2])'
});
});
});
describe('check', function() {
it('naming', function() {
expectsql(sql.getConstraintSnippet('myTable', {
type: 'CHECK',
fields: [{
attribute: 'myColumn'
}],
where: {
myColumn: ['value1', 'value2', 'value3']
}
}), {
mssql: "CONSTRAINT [myTable_myColumn_ck] CHECK ([myColumn] IN (N'value1', N'value2', N'value3'))",
default: "CONSTRAINT [myTable_myColumn_ck] CHECK ([myColumn] IN ('value1', 'value2', 'value3'))"
});
});
it('where', function() {
expectsql(sql.getConstraintSnippet('myTable', {
type: 'CHECK',
fields: ['myColumn'],
name: 'check_mycolumn_where',
where: {
myColumn: {
$and: {
$gt: 50,
$lt: 100
}
}
}
}), {
default: 'CONSTRAINT [check_mycolumn_where] CHECK (([myColumn] > 50 AND [myColumn] < 100))'
});
});
});
describe('primary key', function() {
it('naming', function() {
expectsql(sql.getConstraintSnippet('myTable', {
name: 'primary_mytable_mycolumn',
type: 'primary key',
fields: ['myColumn']
}), {
default: 'CONSTRAINT [primary_mytable_mycolumn] PRIMARY KEY ([myColumn])'
});
});
it('should create constraint name if not passed', function() {
expectsql(sql.getConstraintSnippet('myTable', {
type: 'PRIMARY KEY',
fields: ['myColumn']
}), {
default: 'CONSTRAINT [myTable_myColumn_pk] PRIMARY KEY ([myColumn])'
});
});
it('should work with multiple columns', function() {
expectsql(sql.getConstraintSnippet('myTable', {
type: 'PRIMARY KEY',
fields: ['myColumn1', 'myColumn2']
}), {
default: 'CONSTRAINT [myTable_myColumn1_myColumn2_pk] PRIMARY KEY ([myColumn1], [myColumn2])'
});
});
});
describe('foreign key', function() {
it('naming', function() {
expectsql(sql.getConstraintSnippet('myTable', {
name: 'foreignkey_mytable_mycolumn',
type: 'foreign key',
fields: ['myColumn'],
references: {
table: 'myOtherTable',
field: 'id'
}
}), {
default: 'CONSTRAINT [foreignkey_mytable_mycolumn] FOREIGN KEY ([myColumn]) REFERENCES [myOtherTable] ([id])'
});
});
it('uses onDelete, onUpdate', function() {
expectsql(sql.getConstraintSnippet('myTable', {
type: 'foreign key',
fields: ['myColumn'],
references: {
table: 'myOtherTable',
field: 'id'
},
onUpdate: 'cascade',
onDelete: 'cascade'
}), {
default: 'CONSTRAINT [myTable_myColumn_myOtherTable_fk] FOREIGN KEY ([myColumn]) REFERENCES [myOtherTable] ([id]) ON UPDATE CASCADE ON DELETE CASCADE'
});
});
it('errors if references object is not passed', function() {
expect(sql.getConstraintSnippet.bind(sql, 'myTable', {
type: 'foreign key',
fields: ['myColumn']
})).to.throw('references object with table and field must be specified');
});
});
describe('validation', function() {
it('throw error on invalid type', function() {
expect(sql.getConstraintSnippet.bind(sql, 'myTable', { type: 'some type', fields: [] })).to.throw('some type is invalid');
});
});
});
});
\ No newline at end of file
'use strict';
/* jshint -W030, -W110 */
const Support = require(__dirname + '/../support');
const current = Support.sequelize;
const expectsql = Support.expectsql;
const sql = current.dialect.QueryGenerator;
if (current.dialect.supports.constraints.dropConstraint) {
describe(Support.getTestDialectTeaser('SQL'), function() {
describe('removeConstraint', function() {
it('naming', function() {
expectsql(sql.removeConstraintQuery('myTable', 'constraint_name'), {
default: 'ALTER TABLE [myTable] DROP CONSTRAINT [constraint_name]'
});
});
});
});
}
\ No newline at end of file
'use strict';
/* jshint -W030, -W110 */
const Support = require(__dirname + '/../support');
const current = Support.sequelize;
const expectsql = Support.expectsql;
const sql = current.dialect.QueryGenerator;
describe(Support.getTestDialectTeaser('SQL'), function() {
describe('showConstraint', function() {
it('naming', function() {
expectsql(sql.showConstraintsQuery('myTable'), {
mssql: "EXEC sp_helpconstraint @objname = N'[myTable]';",
postgres: 'SELECT constraint_catalog AS "constraintCatalog", constraint_schema AS "constraintSchema", constraint_name AS "constraintName", table_catalog AS "tableCatalog", table_schema AS "tableSchema", table_name AS "tableName", constraint_type AS "constraintType", is_deferrable AS "isDeferrable", initially_deferred AS "initiallyDeferred" from INFORMATION_SCHEMA.table_constraints WHERE table_name=\'myTable\';',
mysql: "SELECT CONSTRAINT_CATALOG AS constraintCatalog, CONSTRAINT_NAME AS constraintName, CONSTRAINT_SCHEMA AS constraintSchema, CONSTRAINT_TYPE AS constraintType, TABLE_NAME AS tableName, TABLE_SCHEMA AS tableSchema from INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE table_name='myTable';",
default: "SELECT sql FROM sqlite_master WHERE tbl_name='myTable';"
});
});
it('should add constraint_name to where clause if passed in case of mysql', function() {
expectsql(sql.showConstraintsQuery('myTable', 'myConstraintName'), {
mssql: "EXEC sp_helpconstraint @objname = N'[myTable]';",
postgres: 'SELECT constraint_catalog AS "constraintCatalog", constraint_schema AS "constraintSchema", constraint_name AS "constraintName", table_catalog AS "tableCatalog", table_schema AS "tableSchema", table_name AS "tableName", constraint_type AS "constraintType", is_deferrable AS "isDeferrable", initially_deferred AS "initiallyDeferred" from INFORMATION_SCHEMA.table_constraints WHERE table_name=\'myTable\';',
mysql: "SELECT CONSTRAINT_CATALOG AS constraintCatalog, CONSTRAINT_NAME AS constraintName, CONSTRAINT_SCHEMA AS constraintSchema, CONSTRAINT_TYPE AS constraintType, TABLE_NAME AS tableName, TABLE_SCHEMA AS tableSchema from INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE table_name='myTable' AND constraint_name = 'myConstraintName';",
default: "SELECT sql FROM sqlite_master WHERE tbl_name='myTable' AND sql LIKE '%myConstraintName%';"
});
});
});
});
\ No newline at end of file
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!