query-generator.js 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899
  1. 'use strict';
  2. const _ = require('lodash');
  3. const Utils = require('../../utils');
  4. const DataTypes = require('../../data-types');
  5. const TableHints = require('../../table-hints');
  6. const AbstractQueryGenerator = require('../abstract/query-generator');
  7. const randomBytes = require('crypto').randomBytes;
  8. const semver = require('semver');
  9. const Op = require('../../operators');
  10. /* istanbul ignore next */
  11. const throwMethodUndefined = function(methodName) {
  12. throw new Error(`The method "${methodName}" is not defined! Please add it to your sql dialect.`);
  13. };
  14. class MSSQLQueryGenerator extends AbstractQueryGenerator {
  15. createDatabaseQuery(databaseName, options) {
  16. options = Object.assign({
  17. collate: null
  18. }, options || {});
  19. const collation = options.collate ? `COLLATE ${this.escape(options.collate)}` : '';
  20. return [
  21. 'IF NOT EXISTS (SELECT * FROM sys.databases WHERE name =', wrapSingleQuote(databaseName), ')',
  22. 'BEGIN',
  23. 'CREATE DATABASE', this.quoteIdentifier(databaseName),
  24. `${collation};`,
  25. 'END;'
  26. ].join(' ');
  27. }
  28. dropDatabaseQuery(databaseName) {
  29. return [
  30. 'IF EXISTS (SELECT * FROM sys.databases WHERE name =', wrapSingleQuote(databaseName), ')',
  31. 'BEGIN',
  32. 'DROP DATABASE', this.quoteIdentifier(databaseName), ';',
  33. 'END;'
  34. ].join(' ');
  35. }
  36. createSchema(schema) {
  37. return [
  38. 'IF NOT EXISTS (SELECT schema_name',
  39. 'FROM information_schema.schemata',
  40. 'WHERE schema_name =', wrapSingleQuote(schema), ')',
  41. 'BEGIN',
  42. "EXEC sp_executesql N'CREATE SCHEMA",
  43. this.quoteIdentifier(schema),
  44. ";'",
  45. 'END;'
  46. ].join(' ');
  47. }
  48. dropSchema(schema) {
  49. // Mimics Postgres CASCADE, will drop objects belonging to the schema
  50. const quotedSchema = wrapSingleQuote(schema);
  51. return [
  52. 'IF EXISTS (SELECT schema_name',
  53. 'FROM information_schema.schemata',
  54. 'WHERE schema_name =', quotedSchema, ')',
  55. 'BEGIN',
  56. 'DECLARE @id INT, @ms_sql NVARCHAR(2000);',
  57. 'DECLARE @cascade TABLE (',
  58. 'id INT NOT NULL IDENTITY PRIMARY KEY,',
  59. 'ms_sql NVARCHAR(2000) NOT NULL );',
  60. 'INSERT INTO @cascade ( ms_sql )',
  61. "SELECT CASE WHEN o.type IN ('F','PK')",
  62. "THEN N'ALTER TABLE ['+ s.name + N'].[' + p.name + N'] DROP CONSTRAINT [' + o.name + N']'",
  63. "ELSE N'DROP TABLE ['+ s.name + N'].[' + o.name + N']' END",
  64. 'FROM sys.objects o',
  65. 'JOIN sys.schemas s on o.schema_id = s.schema_id',
  66. 'LEFT OUTER JOIN sys.objects p on o.parent_object_id = p.object_id',
  67. "WHERE o.type IN ('F', 'PK', 'U') AND s.name = ", quotedSchema,
  68. 'ORDER BY o.type ASC;',
  69. 'SELECT TOP 1 @id = id, @ms_sql = ms_sql FROM @cascade ORDER BY id;',
  70. 'WHILE @id IS NOT NULL',
  71. 'BEGIN',
  72. 'BEGIN TRY EXEC sp_executesql @ms_sql; END TRY',
  73. 'BEGIN CATCH BREAK; THROW; END CATCH;',
  74. 'DELETE FROM @cascade WHERE id = @id;',
  75. 'SELECT @id = NULL, @ms_sql = NULL;',
  76. 'SELECT TOP 1 @id = id, @ms_sql = ms_sql FROM @cascade ORDER BY id;',
  77. 'END',
  78. "EXEC sp_executesql N'DROP SCHEMA", this.quoteIdentifier(schema), ";'",
  79. 'END;'
  80. ].join(' ');
  81. }
  82. showSchemasQuery() {
  83. return [
  84. 'SELECT "name" as "schema_name" FROM sys.schemas as s',
  85. 'WHERE "s"."name" NOT IN (',
  86. "'INFORMATION_SCHEMA', 'dbo', 'guest', 'sys', 'archive'",
  87. ')', 'AND', '"s"."name" NOT LIKE', "'db_%'"
  88. ].join(' ');
  89. }
  90. versionQuery() {
  91. // Uses string manipulation to convert the MS Maj.Min.Patch.Build to semver Maj.Min.Patch
  92. return [
  93. 'DECLARE @ms_ver NVARCHAR(20);',
  94. "SET @ms_ver = REVERSE(CONVERT(NVARCHAR(20), SERVERPROPERTY('ProductVersion')));",
  95. "SELECT REVERSE(SUBSTRING(@ms_ver, CHARINDEX('.', @ms_ver)+1, 20)) AS 'version'"
  96. ].join(' ');
  97. }
  98. createTableQuery(tableName, attributes, options) {
  99. const query = (table, attrs) => `IF OBJECT_ID('${table}', 'U') IS NULL CREATE TABLE ${table} (${attrs})`,
  100. primaryKeys = [],
  101. foreignKeys = {},
  102. attrStr = [];
  103. let commentStr = '';
  104. for (const attr in attributes) {
  105. if (Object.prototype.hasOwnProperty.call(attributes, attr)) {
  106. let dataType = attributes[attr];
  107. let match;
  108. if (dataType.includes('COMMENT ')) {
  109. const commentMatch = dataType.match(/^(.+) (COMMENT.*)$/);
  110. const commentText = commentMatch[2].replace('COMMENT', '').trim();
  111. commentStr += this.commentTemplate(commentText, tableName, attr);
  112. // remove comment related substring from dataType
  113. dataType = commentMatch[1];
  114. }
  115. if (dataType.includes('PRIMARY KEY')) {
  116. primaryKeys.push(attr);
  117. if (dataType.includes('REFERENCES')) {
  118. // MSSQL doesn't support inline REFERENCES declarations: move to the end
  119. match = dataType.match(/^(.+) (REFERENCES.*)$/);
  120. attrStr.push(`${this.quoteIdentifier(attr)} ${match[1].replace('PRIMARY KEY', '')}`);
  121. foreignKeys[attr] = match[2];
  122. } else {
  123. attrStr.push(`${this.quoteIdentifier(attr)} ${dataType.replace('PRIMARY KEY', '')}`);
  124. }
  125. } else if (dataType.includes('REFERENCES')) {
  126. // MSSQL doesn't support inline REFERENCES declarations: move to the end
  127. match = dataType.match(/^(.+) (REFERENCES.*)$/);
  128. attrStr.push(`${this.quoteIdentifier(attr)} ${match[1]}`);
  129. foreignKeys[attr] = match[2];
  130. } else {
  131. attrStr.push(`${this.quoteIdentifier(attr)} ${dataType}`);
  132. }
  133. }
  134. }
  135. let attributesClause = attrStr.join(', ');
  136. const pkString = primaryKeys.map(pk => this.quoteIdentifier(pk)).join(', ');
  137. if (options.uniqueKeys) {
  138. _.each(options.uniqueKeys, (columns, indexName) => {
  139. if (columns.customIndex) {
  140. if (typeof indexName !== 'string') {
  141. indexName = `uniq_${tableName}_${columns.fields.join('_')}`;
  142. }
  143. attributesClause += `, CONSTRAINT ${this.quoteIdentifier(indexName)} UNIQUE (${columns.fields.map(field => this.quoteIdentifier(field)).join(', ')})`;
  144. }
  145. });
  146. }
  147. if (pkString.length > 0) {
  148. attributesClause += `, PRIMARY KEY (${pkString})`;
  149. }
  150. for (const fkey in foreignKeys) {
  151. if (Object.prototype.hasOwnProperty.call(foreignKeys, fkey)) {
  152. attributesClause += `, FOREIGN KEY (${this.quoteIdentifier(fkey)}) ${foreignKeys[fkey]}`;
  153. }
  154. }
  155. return `${query(this.quoteTable(tableName), attributesClause)};${commentStr}`;
  156. }
  157. describeTableQuery(tableName, schema) {
  158. let sql = [
  159. 'SELECT',
  160. "c.COLUMN_NAME AS 'Name',",
  161. "c.DATA_TYPE AS 'Type',",
  162. "c.CHARACTER_MAXIMUM_LENGTH AS 'Length',",
  163. "c.IS_NULLABLE as 'IsNull',",
  164. "COLUMN_DEFAULT AS 'Default',",
  165. "pk.CONSTRAINT_TYPE AS 'Constraint',",
  166. "COLUMNPROPERTY(OBJECT_ID(c.TABLE_SCHEMA+'.'+c.TABLE_NAME), c.COLUMN_NAME, 'IsIdentity') as 'IsIdentity',",
  167. "prop.value AS 'Comment'",
  168. 'FROM',
  169. 'INFORMATION_SCHEMA.TABLES t',
  170. 'INNER JOIN',
  171. 'INFORMATION_SCHEMA.COLUMNS c ON t.TABLE_NAME = c.TABLE_NAME AND t.TABLE_SCHEMA = c.TABLE_SCHEMA',
  172. 'LEFT JOIN (SELECT tc.table_schema, tc.table_name, ',
  173. 'cu.column_name, tc.CONSTRAINT_TYPE ',
  174. 'FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc ',
  175. 'JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE cu ',
  176. 'ON tc.table_schema=cu.table_schema and tc.table_name=cu.table_name ',
  177. 'and tc.constraint_name=cu.constraint_name ',
  178. 'and tc.CONSTRAINT_TYPE=\'PRIMARY KEY\') pk ',
  179. 'ON pk.table_schema=c.table_schema ',
  180. 'AND pk.table_name=c.table_name ',
  181. 'AND pk.column_name=c.column_name ',
  182. 'INNER JOIN sys.columns AS sc',
  183. "ON sc.object_id = object_id(t.table_schema + '.' + t.table_name) AND sc.name = c.column_name",
  184. 'LEFT JOIN sys.extended_properties prop ON prop.major_id = sc.object_id',
  185. 'AND prop.minor_id = sc.column_id',
  186. "AND prop.name = 'MS_Description'",
  187. 'WHERE t.TABLE_NAME =', wrapSingleQuote(tableName)
  188. ].join(' ');
  189. if (schema) {
  190. sql += `AND t.TABLE_SCHEMA =${wrapSingleQuote(schema)}`;
  191. }
  192. return sql;
  193. }
  194. renameTableQuery(before, after) {
  195. return `EXEC sp_rename ${this.quoteTable(before)}, ${this.quoteTable(after)};`;
  196. }
  197. showTablesQuery() {
  198. return "SELECT TABLE_NAME, TABLE_SCHEMA FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE';";
  199. }
  200. dropTableQuery(tableName) {
  201. const qouteTbl = this.quoteTable(tableName);
  202. return `IF OBJECT_ID('${qouteTbl}', 'U') IS NOT NULL DROP TABLE ${qouteTbl};`;
  203. }
  204. addColumnQuery(table, key, dataType) {
  205. // FIXME: attributeToSQL SHOULD be using attributes in addColumnQuery
  206. // but instead we need to pass the key along as the field here
  207. dataType.field = key;
  208. let commentStr = '';
  209. if (dataType.comment && _.isString(dataType.comment)) {
  210. commentStr = this.commentTemplate(dataType.comment, table, key);
  211. // attributeToSQL will try to include `COMMENT 'Comment Text'` when it returns if the comment key
  212. // is present. This is needed for createTable statement where that part is extracted with regex.
  213. // Here we can intercept the object and remove comment property since we have the original object.
  214. delete dataType['comment'];
  215. }
  216. const def = this.attributeToSQL(dataType, {
  217. context: 'addColumn'
  218. });
  219. return `ALTER TABLE ${this.quoteTable(table)} ADD ${this.quoteIdentifier(key)} ${def};${commentStr}`;
  220. }
  221. commentTemplate(comment, table, column) {
  222. return ' EXEC sp_addextendedproperty ' +
  223. `@name = N'MS_Description', @value = ${this.escape(comment)}, ` +
  224. '@level0type = N\'Schema\', @level0name = \'dbo\', ' +
  225. `@level1type = N'Table', @level1name = ${this.quoteIdentifier(table)}, ` +
  226. `@level2type = N'Column', @level2name = ${this.quoteIdentifier(column)};`;
  227. }
  228. removeColumnQuery(tableName, attributeName) {
  229. return `ALTER TABLE ${this.quoteTable(tableName)} DROP COLUMN ${this.quoteIdentifier(attributeName)};`;
  230. }
  231. changeColumnQuery(tableName, attributes) {
  232. const attrString = [],
  233. constraintString = [];
  234. let commentString = '';
  235. for (const attributeName in attributes) {
  236. const quotedAttrName = this.quoteIdentifier(attributeName);
  237. let definition = attributes[attributeName];
  238. if (definition.includes('COMMENT ')) {
  239. const commentMatch = definition.match(/^(.+) (COMMENT.*)$/);
  240. const commentText = commentMatch[2].replace('COMMENT', '').trim();
  241. commentString += this.commentTemplate(commentText, tableName, attributeName);
  242. // remove comment related substring from dataType
  243. definition = commentMatch[1];
  244. }
  245. if (definition.includes('REFERENCES')) {
  246. constraintString.push(`FOREIGN KEY (${quotedAttrName}) ${definition.replace(/.+?(?=REFERENCES)/, '')}`);
  247. } else {
  248. attrString.push(`${quotedAttrName} ${definition}`);
  249. }
  250. }
  251. let finalQuery = '';
  252. if (attrString.length) {
  253. finalQuery += `ALTER COLUMN ${attrString.join(', ')}`;
  254. finalQuery += constraintString.length ? ' ' : '';
  255. }
  256. if (constraintString.length) {
  257. finalQuery += `ADD ${constraintString.join(', ')}`;
  258. }
  259. return `ALTER TABLE ${this.quoteTable(tableName)} ${finalQuery};${commentString}`;
  260. }
  261. renameColumnQuery(tableName, attrBefore, attributes) {
  262. const newName = Object.keys(attributes)[0];
  263. return `EXEC sp_rename '${this.quoteTable(tableName)}.${attrBefore}', '${newName}', 'COLUMN';`;
  264. }
  265. bulkInsertQuery(tableName, attrValueHashes, options, attributes) {
  266. const quotedTable = this.quoteTable(tableName);
  267. options = options || {};
  268. attributes = attributes || {};
  269. const tuples = [];
  270. const allAttributes = [];
  271. const allQueries = [];
  272. let needIdentityInsertWrapper = false,
  273. outputFragment = '';
  274. if (options.returning) {
  275. outputFragment = ' OUTPUT INSERTED.*';
  276. }
  277. const emptyQuery = `INSERT INTO ${quotedTable}${outputFragment} DEFAULT VALUES`;
  278. attrValueHashes.forEach(attrValueHash => {
  279. // special case for empty objects with primary keys
  280. const fields = Object.keys(attrValueHash);
  281. const firstAttr = attributes[fields[0]];
  282. if (fields.length === 1 && firstAttr && firstAttr.autoIncrement && attrValueHash[fields[0]] === null) {
  283. allQueries.push(emptyQuery);
  284. return;
  285. }
  286. // normal case
  287. _.forOwn(attrValueHash, (value, key) => {
  288. if (value !== null && attributes[key] && attributes[key].autoIncrement) {
  289. needIdentityInsertWrapper = true;
  290. }
  291. if (!allAttributes.includes(key)) {
  292. if (value === null && attributes[key] && attributes[key].autoIncrement)
  293. return;
  294. allAttributes.push(key);
  295. }
  296. });
  297. });
  298. if (allAttributes.length > 0) {
  299. attrValueHashes.forEach(attrValueHash => {
  300. tuples.push(`(${
  301. allAttributes.map(key =>
  302. this.escape(attrValueHash[key])).join(',')
  303. })`);
  304. });
  305. const quotedAttributes = allAttributes.map(attr => this.quoteIdentifier(attr)).join(',');
  306. allQueries.push(tupleStr => `INSERT INTO ${quotedTable} (${quotedAttributes})${outputFragment} VALUES ${tupleStr};`);
  307. }
  308. const commands = [];
  309. let offset = 0;
  310. const batch = Math.floor(250 / (allAttributes.length + 1)) + 1;
  311. while (offset < Math.max(tuples.length, 1)) {
  312. const tupleStr = tuples.slice(offset, Math.min(tuples.length, offset + batch));
  313. let generatedQuery = allQueries.map(v => typeof v === 'string' ? v : v(tupleStr)).join(';');
  314. if (needIdentityInsertWrapper) {
  315. generatedQuery = `SET IDENTITY_INSERT ${quotedTable} ON; ${generatedQuery}; SET IDENTITY_INSERT ${quotedTable} OFF;`;
  316. }
  317. commands.push(generatedQuery);
  318. offset += batch;
  319. }
  320. return commands.join(';');
  321. }
  322. updateQuery(tableName, attrValueHash, where, options, attributes) {
  323. const sql = super.updateQuery(tableName, attrValueHash, where, options, attributes);
  324. if (options.limit) {
  325. const updateArgs = `UPDATE TOP(${this.escape(options.limit)})`;
  326. sql.query = sql.query.replace('UPDATE', updateArgs);
  327. }
  328. return sql;
  329. }
  330. upsertQuery(tableName, insertValues, updateValues, where, model) {
  331. const targetTableAlias = this.quoteTable(`${tableName}_target`);
  332. const sourceTableAlias = this.quoteTable(`${tableName}_source`);
  333. const primaryKeysAttrs = [];
  334. const identityAttrs = [];
  335. const uniqueAttrs = [];
  336. const tableNameQuoted = this.quoteTable(tableName);
  337. let needIdentityInsertWrapper = false;
  338. //Obtain primaryKeys, uniquekeys and identity attrs from rawAttributes as model is not passed
  339. for (const key in model.rawAttributes) {
  340. if (model.rawAttributes[key].primaryKey) {
  341. primaryKeysAttrs.push(model.rawAttributes[key].field || key);
  342. }
  343. if (model.rawAttributes[key].unique) {
  344. uniqueAttrs.push(model.rawAttributes[key].field || key);
  345. }
  346. if (model.rawAttributes[key].autoIncrement) {
  347. identityAttrs.push(model.rawAttributes[key].field || key);
  348. }
  349. }
  350. //Add unique indexes defined by indexes option to uniqueAttrs
  351. for (const index of model._indexes) {
  352. if (index.unique && index.fields) {
  353. for (const field of index.fields) {
  354. const fieldName = typeof field === 'string' ? field : field.name || field.attribute;
  355. if (!uniqueAttrs.includes(fieldName) && model.rawAttributes[fieldName]) {
  356. uniqueAttrs.push(fieldName);
  357. }
  358. }
  359. }
  360. }
  361. const updateKeys = Object.keys(updateValues);
  362. const insertKeys = Object.keys(insertValues);
  363. const insertKeysQuoted = insertKeys.map(key => this.quoteIdentifier(key)).join(', ');
  364. const insertValuesEscaped = insertKeys.map(key => this.escape(insertValues[key])).join(', ');
  365. const sourceTableQuery = `VALUES(${insertValuesEscaped})`; //Virtual Table
  366. let joinCondition;
  367. //IDENTITY_INSERT Condition
  368. identityAttrs.forEach(key => {
  369. if (updateValues[key] && updateValues[key] !== null) {
  370. needIdentityInsertWrapper = true;
  371. /*
  372. * IDENTITY_INSERT Column Cannot be updated, only inserted
  373. * http://stackoverflow.com/a/30176254/2254360
  374. */
  375. }
  376. });
  377. //Filter NULL Clauses
  378. const clauses = where[Op.or].filter(clause => {
  379. let valid = true;
  380. /*
  381. * Exclude NULL Composite PK/UK. Partial Composite clauses should also be excluded as it doesn't guarantee a single row
  382. */
  383. for (const key in clause) {
  384. if (!clause[key]) {
  385. valid = false;
  386. break;
  387. }
  388. }
  389. return valid;
  390. });
  391. /*
  392. * Generate ON condition using PK(s).
  393. * If not, generate using UK(s). Else throw error
  394. */
  395. const getJoinSnippet = array => {
  396. return array.map(key => {
  397. key = this.quoteIdentifier(key);
  398. return `${targetTableAlias}.${key} = ${sourceTableAlias}.${key}`;
  399. });
  400. };
  401. if (clauses.length === 0) {
  402. throw new Error('Primary Key or Unique key should be passed to upsert query');
  403. } else {
  404. // Search for primary key attribute in clauses -- Model can have two separate unique keys
  405. for (const key in clauses) {
  406. const keys = Object.keys(clauses[key]);
  407. if (primaryKeysAttrs.includes(keys[0])) {
  408. joinCondition = getJoinSnippet(primaryKeysAttrs).join(' AND ');
  409. break;
  410. }
  411. }
  412. if (!joinCondition) {
  413. joinCondition = getJoinSnippet(uniqueAttrs).join(' AND ');
  414. }
  415. }
  416. // Remove the IDENTITY_INSERT Column from update
  417. const updateSnippet = updateKeys.filter(key => !identityAttrs.includes(key))
  418. .map(key => {
  419. const value = this.escape(updateValues[key]);
  420. key = this.quoteIdentifier(key);
  421. return `${targetTableAlias}.${key} = ${value}`;
  422. }).join(', ');
  423. const insertSnippet = `(${insertKeysQuoted}) VALUES(${insertValuesEscaped})`;
  424. let query = `MERGE INTO ${tableNameQuoted} WITH(HOLDLOCK) AS ${targetTableAlias} USING (${sourceTableQuery}) AS ${sourceTableAlias}(${insertKeysQuoted}) ON ${joinCondition}`;
  425. query += ` WHEN MATCHED THEN UPDATE SET ${updateSnippet} WHEN NOT MATCHED THEN INSERT ${insertSnippet} OUTPUT $action, INSERTED.*;`;
  426. if (needIdentityInsertWrapper) {
  427. query = `SET IDENTITY_INSERT ${tableNameQuoted} ON; ${query} SET IDENTITY_INSERT ${tableNameQuoted} OFF;`;
  428. }
  429. return query;
  430. }
  431. truncateTableQuery(tableName) {
  432. return `TRUNCATE TABLE ${this.quoteTable(tableName)}`;
  433. }
  434. deleteQuery(tableName, where, options = {}, model) {
  435. const table = this.quoteTable(tableName);
  436. let whereClause = this.getWhereConditions(where, null, model, options);
  437. let limit = '';
  438. if (options.limit) {
  439. limit = ` TOP(${this.escape(options.limit)})`;
  440. }
  441. if (whereClause) {
  442. whereClause = ` WHERE ${whereClause}`;
  443. }
  444. return `DELETE${limit} FROM ${table}${whereClause}; SELECT @@ROWCOUNT AS AFFECTEDROWS;`;
  445. }
  446. showIndexesQuery(tableName) {
  447. return `EXEC sys.sp_helpindex @objname = N'${this.quoteTable(tableName)}';`;
  448. }
  449. showConstraintsQuery(tableName) {
  450. return `EXEC sp_helpconstraint @objname = ${this.escape(this.quoteTable(tableName))};`;
  451. }
  452. removeIndexQuery(tableName, indexNameOrAttributes) {
  453. let indexName = indexNameOrAttributes;
  454. if (typeof indexName !== 'string') {
  455. indexName = Utils.underscore(`${tableName}_${indexNameOrAttributes.join('_')}`);
  456. }
  457. return `DROP INDEX ${this.quoteIdentifiers(indexName)} ON ${this.quoteIdentifiers(tableName)}`;
  458. }
  459. attributeToSQL(attribute) {
  460. if (!_.isPlainObject(attribute)) {
  461. attribute = {
  462. type: attribute
  463. };
  464. }
  465. // handle self referential constraints
  466. if (attribute.references) {
  467. if (attribute.Model && attribute.Model.tableName === attribute.references.model) {
  468. this.sequelize.log('MSSQL does not support self referencial constraints, '
  469. + 'we will remove it but we recommend restructuring your query');
  470. attribute.onDelete = '';
  471. attribute.onUpdate = '';
  472. }
  473. }
  474. let template;
  475. if (attribute.type instanceof DataTypes.ENUM) {
  476. if (attribute.type.values && !attribute.values) attribute.values = attribute.type.values;
  477. // enums are a special case
  478. template = attribute.type.toSql();
  479. template += ` CHECK (${this.quoteIdentifier(attribute.field)} IN(${attribute.values.map(value => {
  480. return this.escape(value);
  481. }).join(', ') }))`;
  482. return template;
  483. }
  484. template = attribute.type.toString();
  485. if (attribute.allowNull === false) {
  486. template += ' NOT NULL';
  487. } else if (!attribute.primaryKey && !Utils.defaultValueSchemable(attribute.defaultValue)) {
  488. template += ' NULL';
  489. }
  490. if (attribute.autoIncrement) {
  491. template += ' IDENTITY(1,1)';
  492. }
  493. // Blobs/texts cannot have a defaultValue
  494. if (attribute.type !== 'TEXT' && attribute.type._binary !== true &&
  495. Utils.defaultValueSchemable(attribute.defaultValue)) {
  496. template += ` DEFAULT ${this.escape(attribute.defaultValue)}`;
  497. }
  498. if (attribute.unique === true) {
  499. template += ' UNIQUE';
  500. }
  501. if (attribute.primaryKey) {
  502. template += ' PRIMARY KEY';
  503. }
  504. if (attribute.references) {
  505. template += ` REFERENCES ${this.quoteTable(attribute.references.model)}`;
  506. if (attribute.references.key) {
  507. template += ` (${this.quoteIdentifier(attribute.references.key)})`;
  508. } else {
  509. template += ` (${this.quoteIdentifier('id')})`;
  510. }
  511. if (attribute.onDelete) {
  512. template += ` ON DELETE ${attribute.onDelete.toUpperCase()}`;
  513. }
  514. if (attribute.onUpdate) {
  515. template += ` ON UPDATE ${attribute.onUpdate.toUpperCase()}`;
  516. }
  517. }
  518. if (attribute.comment && typeof attribute.comment === 'string') {
  519. template += ` COMMENT ${attribute.comment}`;
  520. }
  521. return template;
  522. }
  523. attributesToSQL(attributes, options) {
  524. const result = {},
  525. existingConstraints = [];
  526. let key,
  527. attribute;
  528. for (key in attributes) {
  529. attribute = attributes[key];
  530. if (attribute.references) {
  531. if (existingConstraints.includes(attribute.references.model.toString())) {
  532. // no cascading constraints to a table more than once
  533. attribute.onDelete = '';
  534. attribute.onUpdate = '';
  535. } else {
  536. existingConstraints.push(attribute.references.model.toString());
  537. // NOTE: this really just disables cascading updates for all
  538. // definitions. Can be made more robust to support the
  539. // few cases where MSSQL actually supports them
  540. attribute.onUpdate = '';
  541. }
  542. }
  543. if (key && !attribute.field) attribute.field = key;
  544. result[attribute.field || key] = this.attributeToSQL(attribute, options);
  545. }
  546. return result;
  547. }
  548. createTrigger() {
  549. throwMethodUndefined('createTrigger');
  550. }
  551. dropTrigger() {
  552. throwMethodUndefined('dropTrigger');
  553. }
  554. renameTrigger() {
  555. throwMethodUndefined('renameTrigger');
  556. }
  557. createFunction() {
  558. throwMethodUndefined('createFunction');
  559. }
  560. dropFunction() {
  561. throwMethodUndefined('dropFunction');
  562. }
  563. renameFunction() {
  564. throwMethodUndefined('renameFunction');
  565. }
  566. /**
  567. * Generate common SQL prefix for ForeignKeysQuery.
  568. *
  569. * @param {string} catalogName
  570. * @returns {string}
  571. */
  572. _getForeignKeysQueryPrefix(catalogName) {
  573. return `${'SELECT ' +
  574. 'constraint_name = OBJ.NAME, ' +
  575. 'constraintName = OBJ.NAME, '}${
  576. catalogName ? `constraintCatalog = '${catalogName}', ` : ''
  577. }constraintSchema = SCHEMA_NAME(OBJ.SCHEMA_ID), ` +
  578. 'tableName = TB.NAME, ' +
  579. `tableSchema = SCHEMA_NAME(TB.SCHEMA_ID), ${
  580. catalogName ? `tableCatalog = '${catalogName}', ` : ''
  581. }columnName = COL.NAME, ` +
  582. `referencedTableSchema = SCHEMA_NAME(RTB.SCHEMA_ID), ${
  583. catalogName ? `referencedCatalog = '${catalogName}', ` : ''
  584. }referencedTableName = RTB.NAME, ` +
  585. 'referencedColumnName = RCOL.NAME ' +
  586. 'FROM sys.foreign_key_columns FKC ' +
  587. 'INNER JOIN sys.objects OBJ ON OBJ.OBJECT_ID = FKC.CONSTRAINT_OBJECT_ID ' +
  588. 'INNER JOIN sys.tables TB ON TB.OBJECT_ID = FKC.PARENT_OBJECT_ID ' +
  589. 'INNER JOIN sys.columns COL ON COL.COLUMN_ID = PARENT_COLUMN_ID AND COL.OBJECT_ID = TB.OBJECT_ID ' +
  590. 'INNER JOIN sys.tables RTB ON RTB.OBJECT_ID = FKC.REFERENCED_OBJECT_ID ' +
  591. 'INNER JOIN sys.columns RCOL ON RCOL.COLUMN_ID = REFERENCED_COLUMN_ID AND RCOL.OBJECT_ID = RTB.OBJECT_ID';
  592. }
  593. /**
  594. * Generates an SQL query that returns all foreign keys details of a table.
  595. *
  596. * @param {string|Object} table
  597. * @param {string} catalogName database name
  598. * @returns {string}
  599. */
  600. getForeignKeysQuery(table, catalogName) {
  601. const tableName = table.tableName || table;
  602. let sql = `${this._getForeignKeysQueryPrefix(catalogName)
  603. } WHERE TB.NAME =${wrapSingleQuote(tableName)}`;
  604. if (table.schema) {
  605. sql += ` AND SCHEMA_NAME(TB.SCHEMA_ID) =${wrapSingleQuote(table.schema)}`;
  606. }
  607. return sql;
  608. }
  609. getForeignKeyQuery(table, attributeName) {
  610. const tableName = table.tableName || table;
  611. let sql = `${this._getForeignKeysQueryPrefix()
  612. } WHERE TB.NAME =${wrapSingleQuote(tableName)
  613. } AND COL.NAME =${wrapSingleQuote(attributeName)}`;
  614. if (table.schema) {
  615. sql += ` AND SCHEMA_NAME(TB.SCHEMA_ID) =${wrapSingleQuote(table.schema)}`;
  616. }
  617. return sql;
  618. }
  619. getPrimaryKeyConstraintQuery(table, attributeName) {
  620. const tableName = wrapSingleQuote(table.tableName || table);
  621. return [
  622. 'SELECT K.TABLE_NAME AS tableName,',
  623. 'K.COLUMN_NAME AS columnName,',
  624. 'K.CONSTRAINT_NAME AS constraintName',
  625. 'FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS C',
  626. 'JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS K',
  627. 'ON C.TABLE_NAME = K.TABLE_NAME',
  628. 'AND C.CONSTRAINT_CATALOG = K.CONSTRAINT_CATALOG',
  629. 'AND C.CONSTRAINT_SCHEMA = K.CONSTRAINT_SCHEMA',
  630. 'AND C.CONSTRAINT_NAME = K.CONSTRAINT_NAME',
  631. 'WHERE C.CONSTRAINT_TYPE = \'PRIMARY KEY\'',
  632. `AND K.COLUMN_NAME = ${wrapSingleQuote(attributeName)}`,
  633. `AND K.TABLE_NAME = ${tableName};`
  634. ].join(' ');
  635. }
  636. dropForeignKeyQuery(tableName, foreignKey) {
  637. return `ALTER TABLE ${this.quoteTable(tableName)} DROP ${this.quoteIdentifier(foreignKey)}`;
  638. }
  639. getDefaultConstraintQuery(tableName, attributeName) {
  640. const quotedTable = this.quoteTable(tableName);
  641. return 'SELECT name FROM sys.default_constraints ' +
  642. `WHERE PARENT_OBJECT_ID = OBJECT_ID('${quotedTable}', 'U') ` +
  643. `AND PARENT_COLUMN_ID = (SELECT column_id FROM sys.columns WHERE NAME = ('${attributeName}') ` +
  644. `AND object_id = OBJECT_ID('${quotedTable}', 'U'));`;
  645. }
  646. dropConstraintQuery(tableName, constraintName) {
  647. return `ALTER TABLE ${this.quoteTable(tableName)} DROP CONSTRAINT ${this.quoteIdentifier(constraintName)};`;
  648. }
  649. setIsolationLevelQuery() {
  650. }
  651. generateTransactionId() {
  652. return randomBytes(10).toString('hex');
  653. }
  654. startTransactionQuery(transaction) {
  655. if (transaction.parent) {
  656. return `SAVE TRANSACTION ${this.quoteIdentifier(transaction.name)};`;
  657. }
  658. return 'BEGIN TRANSACTION;';
  659. }
  660. commitTransactionQuery(transaction) {
  661. if (transaction.parent) {
  662. return;
  663. }
  664. return 'COMMIT TRANSACTION;';
  665. }
  666. rollbackTransactionQuery(transaction) {
  667. if (transaction.parent) {
  668. return `ROLLBACK TRANSACTION ${this.quoteIdentifier(transaction.name)};`;
  669. }
  670. return 'ROLLBACK TRANSACTION;';
  671. }
  672. selectFromTableFragment(options, model, attributes, tables, mainTableAs, where) {
  673. let topFragment = '';
  674. let mainFragment = `SELECT ${attributes.join(', ')} FROM ${tables}`;
  675. // Handle SQL Server 2008 with TOP instead of LIMIT
  676. if (semver.valid(this.sequelize.options.databaseVersion) && semver.lt(this.sequelize.options.databaseVersion, '11.0.0')) {
  677. if (options.limit) {
  678. topFragment = `TOP ${options.limit} `;
  679. }
  680. if (options.offset) {
  681. const offset = options.offset || 0,
  682. isSubQuery = options.hasIncludeWhere || options.hasIncludeRequired || options.hasMultiAssociation;
  683. let orders = { mainQueryOrder: [] };
  684. if (options.order) {
  685. orders = this.getQueryOrders(options, model, isSubQuery);
  686. }
  687. if (!orders.mainQueryOrder.length) {
  688. orders.mainQueryOrder.push(this.quoteIdentifier(model.primaryKeyField));
  689. }
  690. const tmpTable = mainTableAs ? mainTableAs : 'OffsetTable';
  691. const whereFragment = where ? ` WHERE ${where}` : '';
  692. /*
  693. * For earlier versions of SQL server, we need to nest several queries
  694. * in order to emulate the OFFSET behavior.
  695. *
  696. * 1. The outermost query selects all items from the inner query block.
  697. * This is due to a limitation in SQL server with the use of computed
  698. * columns (e.g. SELECT ROW_NUMBER()...AS x) in WHERE clauses.
  699. * 2. The next query handles the LIMIT and OFFSET behavior by getting
  700. * the TOP N rows of the query where the row number is > OFFSET
  701. * 3. The innermost query is the actual set we want information from
  702. */
  703. const fragment = `SELECT TOP 100 PERCENT ${attributes.join(', ')} FROM ` +
  704. `(SELECT ${topFragment}*` +
  705. ` FROM (SELECT ROW_NUMBER() OVER (ORDER BY ${orders.mainQueryOrder.join(', ')}) as row_num, * ` +
  706. ` FROM ${tables} AS ${tmpTable}${whereFragment})` +
  707. ` AS ${tmpTable} WHERE row_num > ${offset})` +
  708. ` AS ${tmpTable}`;
  709. return fragment;
  710. }
  711. mainFragment = `SELECT ${topFragment}${attributes.join(', ')} FROM ${tables}`;
  712. }
  713. if (mainTableAs) {
  714. mainFragment += ` AS ${mainTableAs}`;
  715. }
  716. if (options.tableHint && TableHints[options.tableHint]) {
  717. mainFragment += ` WITH (${TableHints[options.tableHint]})`;
  718. }
  719. return mainFragment;
  720. }
  721. addLimitAndOffset(options, model) {
  722. // Skip handling of limit and offset as postfixes for older SQL Server versions
  723. if (semver.valid(this.sequelize.options.databaseVersion) && semver.lt(this.sequelize.options.databaseVersion, '11.0.0')) {
  724. return '';
  725. }
  726. const offset = options.offset || 0;
  727. const isSubQuery = options.subQuery === undefined
  728. ? options.hasIncludeWhere || options.hasIncludeRequired || options.hasMultiAssociation
  729. : options.subQuery;
  730. let fragment = '';
  731. let orders = {};
  732. if (options.order) {
  733. orders = this.getQueryOrders(options, model, isSubQuery);
  734. }
  735. if (options.limit || options.offset) {
  736. if (!options.order || options.include && !orders.subQueryOrder.length) {
  737. fragment += options.order && !isSubQuery ? ', ' : ' ORDER BY ';
  738. fragment += `${this.quoteTable(options.tableAs || model.name)}.${this.quoteIdentifier(model.primaryKeyField)}`;
  739. }
  740. if (options.offset || options.limit) {
  741. fragment += ` OFFSET ${this.escape(offset)} ROWS`;
  742. }
  743. if (options.limit) {
  744. fragment += ` FETCH NEXT ${this.escape(options.limit)} ROWS ONLY`;
  745. }
  746. }
  747. return fragment;
  748. }
  749. booleanValue(value) {
  750. return value ? 1 : 0;
  751. }
  752. }
  753. // private methods
  754. function wrapSingleQuote(identifier) {
  755. return Utils.addTicks(Utils.removeTicks(identifier, "'"), "'");
  756. }
  757. module.exports = MSSQLQueryGenerator;