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

Commit 040e24c3 by Mick Hansen

Merge pull request #2937 from mbroadst/schema-support

add proper schema support for mssql dialect
2 parents 2980936d 3e58a801
......@@ -22,6 +22,7 @@ MssqlDialect.prototype.supports = _.merge(_.cloneDeep(Abstract.prototype.support
returnValues: {
output: true
},
schemas: true,
autoIncrement: {
identityInsert: true,
defaultValue: false,
......
......@@ -37,7 +37,7 @@ module.exports = (function() {
},
createTableQuery: function(tableName, attributes, options) {
var query = "IF OBJECT_ID('[<%= escapedTable %>]', 'U') IS NULL CREATE TABLE <%= table %> (<%= attributes %>)"
var query = "IF OBJECT_ID('<%= table %>', 'U') IS NULL CREATE TABLE <%= table %> (<%= attributes %>)"
, primaryKeys = []
, foreignKeys = {}
, attrStr = []
......@@ -71,7 +71,6 @@ module.exports = (function() {
}
var values = {
escapedTable: this.quoteTable(tableName).replace(/"/g, ''),
table: this.quoteTable(tableName),
attributes: attrStr.join(', '),
}
......@@ -100,20 +99,24 @@ module.exports = (function() {
},
describeTableQuery: function(tableName, schema, schemaDelimiter) {
var table = tableName;
var sql = [
"SELECT",
"c.COLUMN_NAME AS 'Name',",
"c.DATA_TYPE AS 'Type',",
"c.IS_NULLABLE as 'IsNull',",
"COLUMN_DEFAULT AS 'Default'",
"FROM",
"INFORMATION_SCHEMA.TABLES t",
"INNER JOIN",
"INFORMATION_SCHEMA.COLUMNS c ON t.TABLE_NAME = c.TABLE_NAME",
"WHERE t.TABLE_NAME =", wrapSingleQuote(tableName)
].join(" ");
if (schema) {
table = schema + '.' + tableName;
sql += "AND t.TABLE_SCHEMA =" + wrapSingleQuote(schema);
}
return [
"SELECT c.COLUMN_NAME AS 'Name', c.DATA_TYPE AS 'Type',",
"c.IS_NULLABLE as 'IsNull' , COLUMN_DEFAULT AS 'Default'",
"FROM INFORMATION_SCHEMA.TABLES t ",
"INNER JOIN INFORMATION_SCHEMA.COLUMNS c ON t.TABLE_NAME = c.TABLE_NAME",
"where t.TABLE_NAME =",
wrapSingleQuote(table),
";"
].join(" ");
return sql;
},
renameTableQuery: function(before, after) {
......@@ -125,13 +128,12 @@ module.exports = (function() {
},
showTablesQuery: function () {
return 'SELECT name FROM sys.tables;';
return 'SELECT TABLE_NAME, TABLE_SCHEMA FROM INFORMATION_SCHEMA.TABLES;';
},
dropTableQuery: function(tableName, options) {
var query = "IF OBJECT_ID('[<%= escapedTable %>]', 'U') IS NOT NULL DROP TABLE <%= table %>";
var query = "IF OBJECT_ID('<%= table %>', 'U') IS NOT NULL DROP TABLE <%= table %>";
var values = {
escapedTable: this.quoteTable(tableName).replace(/"/g, ''),
table: this.quoteTable(tableName)
};
......@@ -294,11 +296,9 @@ module.exports = (function() {
},
showIndexesQuery: function(tableName, options) {
// FIXME: temporary until I implement proper schema support
var dequotedTableName = tableName.toString().replace(/['"]+/g, '');
var sql = "EXEC sys.sp_helpindex @objname = N'[<%= tableName %>]';";
var sql = "EXEC sys.sp_helpindex @objname = N'<%= tableName %>';";
return Utils._.template(sql)({
tableName: dequotedTableName
tableName: this.quoteTable(tableName)
});
},
......@@ -464,18 +464,25 @@ module.exports = (function() {
quoteIdentifier: function(identifier, force) {
if (identifier === '*') return identifier;
return Utils.addTicks(identifier, '"');
return '[' + identifier.replace(/[\[\]']+/g,'') + ']';
},
getForeignKeysQuery: function(tableName, schemaName) {
return [
getForeignKeysQuery: function(table, databaseName) {
var tableName = table.tableName || table;
var sql = [
'SELECT',
'constraint_name = C.CONSTRAINT_NAME',
'FROM',
'INFORMATION_SCHEMA.TABLE_CONSTRAINTS C',
"WHERE C.CONSTRAINT_TYPE = 'FOREIGN KEY'",
'AND C.TABLE_NAME =', wrapSingleQuote(tableName)
"AND C.TABLE_NAME =", wrapSingleQuote(tableName)
].join(' ');
if (table.schema) {
sql += " AND C.TABLE_SCHEMA =" + wrapSingleQuote(table.schema);
}
return sql;
},
dropForeignKeyQuery: function(tableName, foreignKey) {
......
......@@ -161,6 +161,15 @@ module.exports = (function() {
return result;
};
Query.prototype.handleShowTablesQuery = function(results) {
return results.map(function(resultSet) {
return {
tableName: resultSet.TABLE_NAME,
schema: resultSet.TABLE_SCHEMA
};
});
};
Query.prototype.formatError = function (err) {
var match;
match = err.message.match(/Violation of UNIQUE KEY constraint '((.|\s)*)'. Cannot insert duplicate key in object '.*'. The duplicate key value is \((.*)\)./);
......
......@@ -217,7 +217,7 @@ module.exports = (function() {
var dropAllTables = function(tableNames) {
return Utils.Promise.reduce(tableNames, function(total, tableName) {
// if tableName is not in the Array of tables names then dont drop it
if (skip.indexOf(tableName) === -1) {
if (skip.indexOf(tableName.tableName || tableName) === -1) {
return self.dropTable(tableName, { cascade: true });
}
}, null);
......@@ -244,7 +244,12 @@ module.exports = (function() {
var promises = [];
tableNames.forEach(function(tableName) {
foreignKeys[tableName].forEach(function(foreignKey) {
var normalizedTableName = tableName;
if (Utils._.isObject(tableName)) {
normalizedTableName = tableName.schema + '.' + tableName.tableName;
}
foreignKeys[normalizedTableName].forEach(function(foreignKey) {
var sql = self.QueryGenerator.dropForeignKeyQuery(tableName, foreignKey);
promises.push(self.sequelize.query(sql));
});
......@@ -445,6 +450,10 @@ module.exports = (function() {
var result = {};
tableNames.forEach(function(tableName, i) {
if (Utils._.isObject(tableName)) {
tableName = tableName.schema + '.' + tableName.tableName;
}
result[tableName] = Utils._.compact(results[i]).map(function(r) {
return r.constraint_name;
});
......
......@@ -9,7 +9,8 @@ var chai = require('chai')
, moment = require('moment')
, sinon = require('sinon')
, Promise = Sequelize.Promise
, current = Support.sequelize;
, current = Support.sequelize
, dialect = Support.getTestDialect();
chai.config.includeStack = true;
......@@ -1424,6 +1425,10 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), function() {
return this.sequelize.sync({force: true}).then(function() {
return self.sequelize.getQueryInterface().showAllTables();
}).then(function(result) {
if (dialect === 'mssql' /* current.dialect.supports.schemas */) {
result = _.pluck(result, 'tableName');
}
expect(result.indexOf('group_user')).not.to.equal(-1);
});
});
......@@ -1440,6 +1445,10 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), function() {
return this.sequelize.sync({force: true}).then(function() {
return self.sequelize.getQueryInterface().showAllTables();
}).then(function(result) {
if (dialect === 'mssql' /* current.dialect.supports.schemas */) {
result = _.pluck(result, 'tableName');
}
expect(result.indexOf('user_groups')).not.to.equal(-1);
});
});
......
......@@ -1431,8 +1431,12 @@ describe(Support.getTestDialectTeaser('HasMany'), function() {
this.Task.create({ id: 15, title: 'task2' })
]).spread(function(user, task1, task2) {
return user.setTasks([task1, task2]).on('sql', spy).on('sql', _.after(2, function(sql) {
var tickChar = (Support.getTestDialect() === 'postgres' || dialect === 'mssql') ? '"' : '`';
expect(sql).to.have.string('INSERT INTO %TasksUsers% (%TaskId%,%UserId%) VALUES (12,1),(15,1)'.replace(/%/g, tickChar));
if (dialect === 'mssql') {
expect(sql).to.have.string('INSERT INTO [TasksUsers] ([TaskId],[UserId]) VALUES (12,1),(15,1)');
} else {
var tickChar = (Support.getTestDialect() === 'postgres') ? '"' : '`';
expect(sql).to.have.string('INSERT INTO %TasksUsers% (%TaskId%,%UserId%) VALUES (12,1),(15,1)'.replace(/%/g, tickChar));
}
}));
}).then(function() {
expect(spy.calledTwice).to.be.ok; // Once for SELECT, once for INSERT
......@@ -2169,6 +2173,10 @@ describe(Support.getTestDialectTeaser('HasMany'), function() {
return this.sequelize.sync({force: true}).then(function() {
return self.sequelize.getQueryInterface().showAllTables();
}).then(function(result) {
if (dialect === 'mssql' /* current.dialect.supports.schemas */) {
result = _.pluck(result, 'tableName');
}
expect(result.indexOf('group_user')).not.to.equal(-1);
});
});
......@@ -2185,6 +2193,10 @@ describe(Support.getTestDialectTeaser('HasMany'), function() {
return this.sequelize.sync({force: true}).then(function() {
return self.sequelize.getQueryInterface().showAllTables();
}).then(function(result) {
if (dialect === 'mssql' /* current.dialect.supports.schemas */) {
result = _.pluck(result, 'tableName');
}
expect(result.indexOf('user_groups')).not.to.equal(-1);
});
});
......
......@@ -1642,11 +1642,10 @@ describe(Support.getTestDialectTeaser('Instance'), function() {
user.destroy().on('sql', function(sql) {
if (dialect === 'postgres' || dialect === 'postgres-native') {
expect(sql).to.equal('DELETE FROM "UserDestroys" WHERE "newId" IN (SELECT "newId" FROM "UserDestroys" WHERE "newId"=\'123ABC\' LIMIT 1)');
}
else if (Support.dialectIsMySQL()) {
} else if (Support.dialectIsMySQL()) {
expect(sql).to.equal("DELETE FROM `UserDestroys` WHERE `newId`='123ABC' LIMIT 1");
} else if (dialect === 'mssql') {
expect(sql).to.equal('DELETE TOP(1) FROM "UserDestroys" WHERE "newId"=\'123ABC\'; SELECT @@ROWCOUNT AS AFFECTEDROWS;');
expect(sql).to.equal('DELETE TOP(1) FROM [UserDestroys] WHERE [newId]=\'123ABC\'; SELECT @@ROWCOUNT AS AFFECTEDROWS;');
}else {
expect(sql).to.equal("DELETE FROM `UserDestroys` WHERE `newId`='123ABC'");
}
......
......@@ -364,7 +364,7 @@ describe(Support.getTestDialectTeaser('Instance'), function() {
}).success(function(user) {
var emitter = user.update({name: 'foobar'});
emitter.on('sql', function(sql) {
expect(sql).to.match(/WHERE [`"]identifier[`"]..identifier./);
expect(sql).to.match(/WHERE [`"\[]identifier[`"\]]..identifier./);
done();
});
});
......
......@@ -313,8 +313,8 @@ describe(Support.getTestDialectTeaser('Model'), function() {
User.sync({ force: true }).on('sql', _.after(2, _.once(function(sql) {
if (dialect === 'mssql') {
expect(sql).to.match(/CONSTRAINT\s*([`"]?user_and_email[`"]?)?\s*UNIQUE\s*\([`"]?username[`"]?, [`"]?email[`"]?\)/);
expect(sql).to.match(/CONSTRAINT\s*([`"]?a_and_b[`"]?)?\s*UNIQUE\s*\([`"]?aCol[`"]?, [`"]?bCol[`"]?\)/);
expect(sql).to.match(/CONSTRAINT\s*([`"\[]?user_and_email[`"\]]?)?\s*UNIQUE\s*\([`"\[]?username[`"\]]?, [`"\[]?email[`"\]]?\)/);
expect(sql).to.match(/CONSTRAINT\s*([`"\[]?a_and_b[`"\]]?)?\s*UNIQUE\s*\([`"\[]?aCol[`"\]]?, [`"\[]?bCol[`"\]]?\)/);
} else {
expect(sql).to.match(/UNIQUE\s*([`"]?user_and_email[`"]?)?\s*\([`"]?username[`"]?, [`"]?email[`"]?\)/);
expect(sql).to.match(/UNIQUE\s*([`"]?a_and_b[`"]?)?\s*\([`"]?aCol[`"]?, [`"]?bCol[`"]?\)/);
......@@ -2046,7 +2046,7 @@ describe(Support.getTestDialectTeaser('Model'), function() {
if (dialect === 'postgres') {
expect(sql).to.match(/REFERENCES\s+"prefix"\."UserPubs" \("id"\)/);
} else if (dialect === 'mssql') {
expect(sql).to.match(/REFERENCES\s+"prefix.UserPubs" \("id"\)/);
expect(sql).to.match(/REFERENCES\s+\[prefix\]\.\[UserPubs\] \(\[id\]\)/);
} else {
expect(sql).to.match(/REFERENCES\s+`prefix\.UserPubs` \(`id`\)/);
}
......@@ -2084,9 +2084,9 @@ describe(Support.getTestDialectTeaser('Model'), function() {
expect(UserSpecial.indexOf('INSERT INTO `special.UserSpecials`')).to.be.above(-1);
expect(UserPublic.indexOf('INSERT INTO `UserPublics`')).to.be.above(-1);
} else if (dialect === 'mssql') {
expect(self.UserSpecialSync.getTableName().toString()).to.equal('"special.UserSpecials"');
expect(UserSpecial.indexOf('INSERT INTO "special.UserSpecials"')).to.be.above(-1);
expect(UserPublic.indexOf('INSERT INTO "UserPublics"')).to.be.above(-1);
expect(self.UserSpecialSync.getTableName().toString()).to.equal('[special].[UserSpecials]');
expect(UserSpecial.indexOf('INSERT INTO [special].[UserSpecials]')).to.be.above(-1);
expect(UserPublic.indexOf('INSERT INTO [UserPublics]')).to.be.above(-1);
} else {
expect(self.UserSpecialSync.getTableName().toString()).to.equal('`special.UserSpecials`');
expect(UserSpecial.indexOf('INSERT INTO `special.UserSpecials`')).to.be.above(-1);
......@@ -2101,7 +2101,7 @@ describe(Support.getTestDialectTeaser('Model'), function() {
if (dialect === 'postgres') {
expect(user.indexOf('UPDATE "special"."UserSpecials"')).to.be.above(-1);
} else if (dialect === 'mssql') {
expect(user.indexOf('UPDATE "special.UserSpecials"')).to.be.above(-1);
expect(user.indexOf('UPDATE [special].[UserSpecials]')).to.be.above(-1);
} else {
expect(user.indexOf('UPDATE `special.UserSpecials`')).to.be.above(-1);
}
......@@ -2151,7 +2151,7 @@ describe(Support.getTestDialectTeaser('Model'), function() {
} else if (Support.dialectIsMySQL()) {
expect(sql).to.match(/FOREIGN KEY \(`authorId`\) REFERENCES `authors` \(`id`\)/);
} else if (dialect === 'mssql') {
expect(sql).to.match(/FOREIGN KEY \("authorId"\) REFERENCES "authors" \("id"\)/);
expect(sql).to.match(/FOREIGN KEY \(\[authorId\]\) REFERENCES \[authors\] \(\[id\]\)/);
} else if (dialect === 'sqlite') {
expect(sql).to.match(/`authorId` INTEGER REFERENCES `authors` \(`id`\)/);
} else {
......@@ -2185,7 +2185,7 @@ describe(Support.getTestDialectTeaser('Model'), function() {
} else if (dialect === 'sqlite') {
expect(sql).to.match(/`authorId` INTEGER REFERENCES `authors` \(`id`\)/);
} else if (dialect === 'mssql') {
expect(sql).to.match(/FOREIGN KEY \("authorId"\) REFERENCES "authors" \("id"\)/);
expect(sql).to.match(/FOREIGN KEY \(\[authorId\]\) REFERENCES \[authors\] \(\[id\]\)/);
} else {
throw new Error('Undefined dialect!');
}
......
......@@ -63,7 +63,7 @@ describe(Support.getTestDialectTeaser('Model'), function() {
}).on('sql', function(sql) {
var expectation = ({
mysql: "WHERE (`User`.`username`='foo' AND `User`.`intVal`=2 " + word + " `User`.`secretValue`='bar')",
mssql: 'WHERE ("User"."username"=\'foo\' AND "User"."intVal"=2 ' + word + ' "User"."secretValue"=\'bar\')',
mssql: 'WHERE ([User].[username]=\'foo\' AND [User].[intVal]=2 ' + word + ' [User].[secretValue]=\'bar\')',
sqlite: "WHERE (`User`.`username`='foo' AND `User`.`intVal`=2 " + word + " `User`.`secretValue`='bar')",
postgres: 'WHERE ("User"."username"=\'foo\' AND "User"."intVal"=2 ' + word + ' "User"."secretValue"=\'bar\')',
mariadb: "WHERE (`User`.`username`='foo' AND `User`.`intVal`=2 " + word + " `User`.`secretValue`='bar')"
......@@ -88,7 +88,7 @@ describe(Support.getTestDialectTeaser('Model'), function() {
mysql: 'WHERE (`User`.`id`=1 ' + word + ' `User`.`id`=2)',
sqlite: 'WHERE (`User`.`id`=1 ' + word + ' `User`.`id`=2)',
postgres: 'WHERE ("User"."id"=1 ' + word + ' "User"."id"=2)',
mssql: 'WHERE ("User"."id"=1 ' + word + ' "User"."id"=2)',
mssql: 'WHERE ([User].[id]=1 ' + word + ' [User].[id]=2)',
mariadb: 'WHERE (`User`.`id`=1 ' + word + ' `User`.`id`=2)'
})[Support.getTestDialect()];
......@@ -128,7 +128,7 @@ describe(Support.getTestDialectTeaser('Model'), function() {
mysql: "WHERE ((`User`.`username` = 'foo' OR `User`.`username` = 'bar') AND (`User`.`id` = 1 OR `User`.`id` = 4)) LIMIT 1",
sqlite: "WHERE ((`User`.`username` = 'foo' OR `User`.`username` = 'bar') AND (`User`.`id` = 1 OR `User`.`id` = 4)) LIMIT 1",
postgres: 'WHERE (("User"."username" = \'foo\' OR "User"."username" = \'bar\') AND ("User"."id" = 1 OR "User"."id" = 4)) LIMIT 1',
mssql: 'WHERE (("User"."username" = \'foo\' OR "User"."username" = \'bar\') AND ("User"."id" = 1 OR "User"."id" = 4))',
mssql: 'WHERE (([User].[username] = \'foo\' OR [User].[username] = \'bar\') AND ([User].[id] = 1 OR [User].[id] = 4))',
mariadb: "WHERE ((`User`.`username` = 'foo' OR `User`.`username` = 'bar') AND (`User`.`id` = 1 OR `User`.`id` = 4)) LIMIT 1"
})[Support.getTestDialect()];
......@@ -164,7 +164,7 @@ describe(Support.getTestDialectTeaser('Model'), function() {
mysql: "WHERE ((`User`.`username` = 'foo' AND `User`.`username` = 'bar') OR (`User`.`id` = 1 AND `User`.`id` = 4)) LIMIT 1",
sqlite: "WHERE ((`User`.`username` = 'foo' AND `User`.`username` = 'bar') OR (`User`.`id` = 1 AND `User`.`id` = 4)) LIMIT 1",
postgres: 'WHERE (("User"."username" = \'foo\' AND "User"."username" = \'bar\') OR ("User"."id" = 1 AND "User"."id" = 4)) LIMIT 1',
mssql: 'WHERE (("User"."username" = \'foo\' AND "User"."username" = \'bar\') OR ("User"."id" = 1 AND "User"."id" = 4))',
mssql: 'WHERE (([User].[username] = \'foo\' AND [User].[username] = \'bar\') OR ([User].[id] = 1 AND [User].[id] = 4))',
mariadb: "WHERE ((`User`.`username` = 'foo' AND `User`.`username` = 'bar') OR (`User`.`id` = 1 AND `User`.`id` = 4)) LIMIT 1"
})[Support.getTestDialect()];
......@@ -206,7 +206,7 @@ describe(Support.getTestDialectTeaser('Model'), function() {
)
]
}).on('sql', function(sql) {
if (Support.getTestDialect() === 'postgres' || dialect === 'mssql') {
if (dialect === 'postgres') {
expect(sql).to.contain(
'WHERE (' + [
'"User"."id"=42 AND 2=2 AND 1=1 AND "User"."username"=\'foo\' AND ',
......@@ -223,6 +223,23 @@ describe(Support.getTestDialectTeaser('Model'), function() {
].join('') +
')'
);
} else if (dialect === 'mssql') {
expect(sql).to.contain(
'WHERE (' + [
'[User].[id]=42 AND 2=2 AND 1=1 AND [User].[username]=\'foo\' AND ',
'(',
'[User].[id]=42 OR 2=2 OR 1=1 OR [User].[username]=\'foo\' OR ',
'([User].[id]=42 AND 2=2 AND 1=1 AND [User].[username]=\'foo\') OR ',
'([User].[id]=42 OR 2=2 OR 1=1 OR [User].[username]=\'foo\')',
') AND ',
'(',
'[User].[id]=42 AND 2=2 AND 1=1 AND [User].[username]=\'foo\' AND ',
'([User].[id]=42 OR 2=2 OR 1=1 OR [User].[username]=\'foo\') AND ',
'([User].[id]=42 AND 2=2 AND 1=1 AND [User].[username]=\'foo\')',
')'
].join('') +
')'
);
} else {
expect(sql).to.contain(
'WHERE (' + [
......
......@@ -59,6 +59,10 @@ describe(Support.getTestDialectTeaser('QueryInterface'), function() {
self.queryInterface.showAllTables().complete(function(err, tableNames) {
expect(err).to.be.null;
if (dialect === 'mssql' /* current.dialect.supports.schemas */) {
tableNames = _.pluck(tableNames, 'tableName');
}
expect(tableNames).to.contain('skipme');
done();
});
......@@ -421,7 +425,9 @@ describe(Support.getTestDialectTeaser('QueryInterface'), function() {
});
it('should get a list of foreign keys for the table', function(done) {
this.sequelize.query(this.queryInterface.QueryGenerator.getForeignKeysQuery('hosts', this.sequelize.config.database)).complete(function(err, fks) {
var sql =
this.queryInterface.QueryGenerator.getForeignKeysQuery('hosts', this.sequelize.config.database);
this.sequelize.query(sql).complete(function(err, fks) {
expect(err).to.be.null;
expect(fks).to.have.length(3);
var keys = Object.keys(fks[0]),
......
......@@ -589,6 +589,10 @@ describe(Support.getTestDialectTeaser('Sequelize'), function() {
, Photo = this.sequelize.define('Foto', { name: DataTypes.STRING }, { tableName: 'photos' });
Photo.sync({ force: true }).success(function() {
self.sequelize.getQueryInterface().showAllTables().success(function(tableNames) {
if (dialect === 'mssql' /* current.dialect.supports.schemas */) {
tableNames = _.pluck(tableNames, 'tableName');
}
expect(tableNames).to.include('photos');
done();
});
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!