query-generator.js 32 KB

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