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

You need to sign in or sign up before continuing.
Commit 3e58a801 by Matt Broadstone

add proper schema support for mssql dialect

Up until now the mssql dialect has been using pseudo schema support
a la the mysql dialect, even though it supports real schemas. This
changeset adds full support for mssql schemas to the dialect, as
well as a few relevant changes (specifically using bracket notation
for object references, instead of quotation marks).
1 parent 7733bd7d
......@@ -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') ? '"' : '`';
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!