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

Commit 53a03c7d by Michael Kearns

Columns with default values can be removed in MSSQL

* this fixes Github issue #4943
* previously, calling removeColumn for a column with a defaultValue
  specified would cause an error -- this is because in MSSQL you have
  to drop the constraint before the column. See:
      http://stackoverflow.com/questions/8641954/how-to-drop-column-with-constraint
* also added integration tests for removeColumn function
1 parent 0f498a72
......@@ -532,6 +532,25 @@ var QueryGenerator = {
});
},
getDefaultConstraintQuery: function (tableName, attributeName) {
var sql = "SELECT name FROM SYS.DEFAULT_CONSTRAINTS " +
"WHERE PARENT_OBJECT_ID = OBJECT_ID('<%= table %>', 'U') " +
"AND PARENT_COLUMN_ID = (SELECT column_id FROM sys.columns WHERE NAME = ('<%= column %>') " +
"AND object_id = OBJECT_ID('<%= table %>', 'U'));";
return Utils._.template(sql)({
table: this.quoteTable(tableName),
column: attributeName
});
},
dropConstraintQuery: function (tableName, constraintName) {
var sql = 'ALTER TABLE <%= table %> DROP CONSTRAINT <%= constraint %>;';
return Utils._.template(sql)({
table: this.quoteTable(tableName),
constraint: this.quoteIdentifier(constraintName)
});
},
setAutocommitQuery: function(value) {
return '';
// return 'SET IMPLICIT_TRANSACTIONS ' + (!!value ? 'OFF' : 'ON') + ';';
......
'use strict';
/**
Returns an object that treats MSSQL's inabilities to do certain queries.
@class QueryInterface
@static
*/
/**
A wrapper that fixes MSSQL's inability to cleanly remove columns from existing tables if they have a default constraint.
@method removeColumn
@for QueryInterface
@param {String} tableName The name of the table.
@param {String} attributeName The name of the attribute that we want to remove.
@param {Object} options
@param {Boolean|Function} [options.logging] A function that logs the sql queries, or false for explicitly not logging these queries
*/
var removeColumn = function (tableName, attributeName, options) {
var self = this;
options = options || {};
var findConstraintSql = self.QueryGenerator.getDefaultConstraintQuery(tableName, attributeName);
return self.sequelize.query(findConstraintSql, { raw: true, logging: options.logging})
.spread(function (results) {
if (!results.length) {
// No default constraint found -- we can cleanly remove the column
return;
}
var dropConstraintSql = self.QueryGenerator.dropConstraintQuery(tableName, results[0].name);
return self.sequelize.query(dropConstraintSql, { raw: true, logging: options.logging});
})
.then(function () {
var removeSql = self.QueryGenerator.removeColumnQuery(tableName, attributeName);
return self.sequelize.query(removeSql, { raw: true, logging: options.logging});
});
};
module.exports = {
removeColumn: removeColumn
};
......@@ -4,6 +4,7 @@ var Utils = require('./utils')
, _ = require('lodash')
, DataTypes = require('./data-types')
, SQLiteQueryInterface = require('./dialects/sqlite/query-interface')
, MSSSQLQueryInterface = require('./dialects/mssql/query-interface')
, Transaction = require('./transaction')
, Promise = require('./promise')
, QueryTypes = require('./query-types');
......@@ -354,12 +355,16 @@ QueryInterface.prototype.addColumn = function(table, key, attribute, options) {
QueryInterface.prototype.removeColumn = function(tableName, attributeName, options) {
options = options || {};
if (this.sequelize.options.dialect === 'sqlite') {
// sqlite needs some special treatment as it cannot drop a column
return SQLiteQueryInterface.removeColumn.call(this, tableName, attributeName, options);
} else {
var sql = this.QueryGenerator.removeColumnQuery(tableName, attributeName);
return this.sequelize.query(sql, options);
switch (this.sequelize.options.dialect) {
case 'sqlite':
// sqlite needs some special treatment as it cannot drop a column
return SQLiteQueryInterface.removeColumn.call(this, tableName, attributeName, options);
case 'mssql':
// mssql needs special treatment as it cannot drop a column with a default constraint
return MSSSQLQueryInterface.removeColumn.call(this, tableName, attributeName, options);
default:
var sql = this.QueryGenerator.removeColumnQuery(tableName, attributeName);
return this.sequelize.query(sql, options);
}
};
......
'use strict';
/* jshint -W110 */
var Support = require(__dirname + '/../../support')
, expectsql = Support.expectsql
, current = Support.sequelize
, QueryGenerator = require('../../../../lib/dialects/mssql/query-generator');
if (current.dialect.name === 'mssql') {
suite('[MSSQL Specific] QueryGenerator', function () {
// Dialect would normally be set by the query interface that instantiates the query-generator, but here we specify it explicitly
QueryGenerator._dialect = current.dialect;
test('getDefaultConstraintQuery', function () {
expectsql(QueryGenerator.getDefaultConstraintQuery({tableName: 'myTable', schema: 'mySchema'}, 'myColumn'), {
mssql: "SELECT name FROM SYS.DEFAULT_CONSTRAINTS WHERE PARENT_OBJECT_ID = OBJECT_ID('[mySchema].[myTable]', 'U') AND PARENT_COLUMN_ID = (SELECT column_id FROM sys.columns WHERE NAME = ('myColumn') AND object_id = OBJECT_ID('[mySchema].[myTable]', 'U'));"
});
});
test('dropConstraintQuery', function () {
expectsql(QueryGenerator.dropConstraintQuery({tableName: 'myTable', schema: 'mySchema'}, 'myConstraint'), {
mssql: "ALTER TABLE [mySchema].[myTable] DROP CONSTRAINT [myConstraint];"
});
});
});
}
......@@ -545,6 +545,107 @@ describe(Support.getTestDialectTeaser('QueryInterface'), function() {
});
});
describe('removeColumn', function() {
describe('(without a schema)', function() {
beforeEach(function() {
return this.queryInterface.createTable('users', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
firstName: {
type: DataTypes.STRING,
defaultValue: 'Someone'
},
lastName: {
type: DataTypes.STRING
}
});
});
it('should be able to remove a column with a default value', function() {
return this.queryInterface.removeColumn('users', 'firstName', {
logging: log
}).bind(this).then(function() {
expect(count).to.be.at.least(1);
count = 0;
return this.queryInterface.describeTable('users');
}).then(function(table) {
expect(table).to.not.have.property('firstName');
});
});
it('should be able to remove a column without default value', function() {
return this.queryInterface.removeColumn('users', 'lastName', {
logging: log
}).bind(this).then(function() {
expect(count).to.be.at.least(1);
count = 0;
return this.queryInterface.describeTable('users');
}).then(function(table) {
expect(table).to.not.have.property('lastName');
});
});
});
describe('(with a schema)', function() {
beforeEach(function() {
return this.queryInterface.createTable({
tableName: 'users',
schema: 'archive'
}, {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
firstName: {
type: DataTypes.STRING,
defaultValue: 'Someone'
},
lastName: {
type: DataTypes.STRING
}
});
});
it('should be able to remove a column with a default value', function() {
return this.queryInterface.removeColumn({
tableName: 'users',
schema: 'archive'
}, 'firstName', {logging: log}
).bind(this).then(function() {
expect(count).to.be.at.least(1);
count = 0;
return this.queryInterface.describeTable({
tableName: 'users',
schema: 'archive'
});
}).then(function(table) {
expect(table).to.not.have.property('firstName');
});
});
it('should be able to remove a column without default value', function() {
return this.queryInterface.removeColumn({
tableName: 'users',
schema: 'archive'
}, 'lastName', {logging: log}
).bind(this).then(function() {
expect(count).to.be.at.least(1);
count = 0;
return this.queryInterface.describeTable({
tableName: 'users',
schema: 'archive'
});
}).then(function(table) {
expect(table).to.not.have.property('lastName');
});
});
});
});
describe('describeForeignKeys', function() {
beforeEach(function() {
return this.queryInterface.createTable('users', {
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!