query-interface.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  1. 'use strict';
  2. const _ = require('lodash');
  3. const Promise = require('../../promise');
  4. const sequelizeErrors = require('../../errors');
  5. const QueryTypes = require('../../query-types');
  6. /**
  7. Returns an object that treats SQLite's inabilities to do certain queries.
  8. @class QueryInterface
  9. @static
  10. @private
  11. */
  12. /**
  13. A wrapper that fixes SQLite's inability to remove columns from existing tables.
  14. It will create a backup of the table, drop the table afterwards and create a
  15. new table with the same name but without the obsolete column.
  16. @param {QueryInterface} qi
  17. @param {string} tableName The name of the table.
  18. @param {string} attributeName The name of the attribute that we want to remove.
  19. @param {Object} options
  20. @param {boolean|Function} [options.logging] A function that logs the sql queries, or false for explicitly not logging these queries
  21. @since 1.6.0
  22. @private
  23. */
  24. function removeColumn(qi, tableName, attributeName, options) {
  25. options = options || {};
  26. return qi.describeTable(tableName, options).then(fields => {
  27. delete fields[attributeName];
  28. const sql = qi.QueryGenerator.removeColumnQuery(tableName, fields);
  29. const subQueries = sql.split(';').filter(q => q !== '');
  30. return Promise.each(subQueries, subQuery => qi.sequelize.query(`${subQuery};`, Object.assign({ raw: true }, options)));
  31. });
  32. }
  33. exports.removeColumn = removeColumn;
  34. /**
  35. A wrapper that fixes SQLite's inability to change columns from existing tables.
  36. It will create a backup of the table, drop the table afterwards and create a
  37. new table with the same name but with a modified version of the respective column.
  38. @param {QueryInterface} qi
  39. @param {string} tableName The name of the table.
  40. @param {Object} attributes An object with the attribute's name as key and its options as value object.
  41. @param {Object} options
  42. @param {boolean|Function} [options.logging] A function that logs the sql queries, or false for explicitly not logging these queries
  43. @since 1.6.0
  44. @private
  45. */
  46. function changeColumn(qi, tableName, attributes, options) {
  47. const attributeName = Object.keys(attributes)[0];
  48. options = options || {};
  49. return qi.describeTable(tableName, options).then(fields => {
  50. Object.assign(fields[attributeName], attributes[attributeName]);
  51. const sql = qi.QueryGenerator.removeColumnQuery(tableName, fields);
  52. const subQueries = sql.split(';').filter(q => q !== '');
  53. return Promise.each(subQueries, subQuery => qi.sequelize.query(`${subQuery};`, Object.assign({ raw: true }, options)));
  54. });
  55. }
  56. exports.changeColumn = changeColumn;
  57. /**
  58. A wrapper that fixes SQLite's inability to rename columns from existing tables.
  59. It will create a backup of the table, drop the table afterwards and create a
  60. new table with the same name but with a renamed version of the respective column.
  61. @param {QueryInterface} qi
  62. @param {string} tableName The name of the table.
  63. @param {string} attrNameBefore The name of the attribute before it was renamed.
  64. @param {string} attrNameAfter The name of the attribute after it was renamed.
  65. @param {Object} options
  66. @param {boolean|Function} [options.logging] A function that logs the sql queries, or false for explicitly not logging these queries
  67. @since 1.6.0
  68. @private
  69. */
  70. function renameColumn(qi, tableName, attrNameBefore, attrNameAfter, options) {
  71. options = options || {};
  72. return qi.describeTable(tableName, options).then(fields => {
  73. fields[attrNameAfter] = _.clone(fields[attrNameBefore]);
  74. delete fields[attrNameBefore];
  75. const sql = qi.QueryGenerator.renameColumnQuery(tableName, attrNameBefore, attrNameAfter, fields);
  76. const subQueries = sql.split(';').filter(q => q !== '');
  77. return Promise.each(subQueries, subQuery => qi.sequelize.query(`${subQuery};`, Object.assign({ raw: true }, options)));
  78. });
  79. }
  80. exports.renameColumn = renameColumn;
  81. /**
  82. * @param {QueryInterface} qi
  83. * @param {string} tableName
  84. * @param {string} constraintName
  85. * @param {Object} options
  86. *
  87. * @private
  88. */
  89. function removeConstraint(qi, tableName, constraintName, options) {
  90. let createTableSql;
  91. return qi.showConstraint(tableName, constraintName)
  92. .then(constraints => {
  93. // sqlite can't show only one constraint, so we find here the one to remove
  94. const constraint = constraints.find(constaint => constaint.constraintName === constraintName);
  95. if (constraint) {
  96. createTableSql = constraint.sql;
  97. constraint.constraintName = qi.QueryGenerator.quoteIdentifier(constraint.constraintName);
  98. let constraintSnippet = `, CONSTRAINT ${constraint.constraintName} ${constraint.constraintType} ${constraint.constraintCondition}`;
  99. if (constraint.constraintType === 'FOREIGN KEY') {
  100. const referenceTableName = qi.QueryGenerator.quoteTable(constraint.referenceTableName);
  101. constraint.referenceTableKeys = constraint.referenceTableKeys.map(columnName => qi.QueryGenerator.quoteIdentifier(columnName));
  102. const referenceTableKeys = constraint.referenceTableKeys.join(', ');
  103. constraintSnippet += ` REFERENCES ${referenceTableName} (${referenceTableKeys})`;
  104. constraintSnippet += ` ON UPDATE ${constraint.updateAction}`;
  105. constraintSnippet += ` ON DELETE ${constraint.deleteAction}`;
  106. }
  107. createTableSql = createTableSql.replace(constraintSnippet, '');
  108. createTableSql += ';';
  109. return qi.describeTable(tableName, options);
  110. }
  111. throw new sequelizeErrors.UnknownConstraintError({
  112. message: `Constraint ${constraintName} on table ${tableName} does not exist`,
  113. constraint: constraintName,
  114. table: tableName
  115. });
  116. })
  117. .then(fields => {
  118. const sql = qi.QueryGenerator._alterConstraintQuery(tableName, fields, createTableSql);
  119. const subQueries = sql.split(';').filter(q => q !== '');
  120. return Promise.each(subQueries, subQuery => qi.sequelize.query(`${subQuery};`, Object.assign({ raw: true }, options)));
  121. });
  122. }
  123. exports.removeConstraint = removeConstraint;
  124. /**
  125. * @param {QueryInterface} qi
  126. * @param {string} tableName
  127. * @param {Object} options
  128. *
  129. * @private
  130. */
  131. function addConstraint(qi, tableName, options) {
  132. const constraintSnippet = qi.QueryGenerator.getConstraintSnippet(tableName, options);
  133. const describeCreateTableSql = qi.QueryGenerator.describeCreateTableQuery(tableName);
  134. let createTableSql;
  135. return qi.sequelize.query(describeCreateTableSql, Object.assign({}, options, { type: QueryTypes.SELECT, raw: true }))
  136. .then(constraints => {
  137. const sql = constraints[0].sql;
  138. const index = sql.length - 1;
  139. //Replace ending ')' with constraint snippet - Simulates String.replaceAt
  140. //http://stackoverflow.com/questions/1431094
  141. createTableSql = `${sql.substr(0, index)}, ${constraintSnippet})${sql.substr(index + 1)};`;
  142. return qi.describeTable(tableName, options);
  143. })
  144. .then(fields => {
  145. const sql = qi.QueryGenerator._alterConstraintQuery(tableName, fields, createTableSql);
  146. const subQueries = sql.split(';').filter(q => q !== '');
  147. return Promise.each(subQueries, subQuery => qi.sequelize.query(`${subQuery};`, Object.assign({ raw: true }, options)));
  148. });
  149. }
  150. exports.addConstraint = addConstraint;
  151. /**
  152. * @param {QueryInterface} qi
  153. * @param {string} tableName
  154. * @param {Object} options Query Options
  155. *
  156. * @private
  157. * @returns {Promise}
  158. */
  159. function getForeignKeyReferencesForTable(qi, tableName, options) {
  160. const database = qi.sequelize.config.database;
  161. const query = qi.QueryGenerator.getForeignKeysQuery(tableName, database);
  162. return qi.sequelize.query(query, options)
  163. .then(result => {
  164. return result.map(row => ({
  165. tableName,
  166. columnName: row.from,
  167. referencedTableName: row.table,
  168. referencedColumnName: row.to,
  169. tableCatalog: database,
  170. referencedTableCatalog: database
  171. }));
  172. });
  173. }
  174. exports.getForeignKeyReferencesForTable = getForeignKeyReferencesForTable;
  175. /**
  176. * Describe a table structure
  177. *
  178. * This method returns an array of hashes containing information about all attributes in the table.
  179. *
  180. * ```js
  181. * {
  182. * name: {
  183. * type: 'VARCHAR(255)', // this will be 'CHARACTER VARYING' for pg!
  184. * allowNull: true,
  185. * defaultValue: null,
  186. * unique: true, // available for sqlite only
  187. * references: {}, // available for sqlite only
  188. * },
  189. * isBetaMember: {
  190. * type: 'TINYINT(1)', // this will be 'BOOLEAN' for pg!
  191. * allowNull: false,
  192. * defaultValue: false,
  193. * unique: false, // available for sqlite only
  194. * references: {}, // available for sqlite only
  195. * }
  196. * }
  197. * ```
  198. *
  199. * @param {QueryInterface} qi
  200. * @param {string} tableName table name
  201. * @param {Object} [options] Query options
  202. *
  203. * @returns {Promise<Object>}
  204. */
  205. function describeTable(qi, tableName, options) {
  206. let schema = null;
  207. let schemaDelimiter = null;
  208. if (typeof options === 'string') {
  209. schema = options;
  210. } else if (typeof options === 'object' && options !== null) {
  211. schema = options.schema || null;
  212. schemaDelimiter = options.schemaDelimiter || null;
  213. }
  214. if (typeof tableName === 'object' && tableName !== null) {
  215. schema = tableName.schema;
  216. tableName = tableName.tableName;
  217. }
  218. const sql = qi.QueryGenerator.describeTableQuery(tableName, schema, schemaDelimiter);
  219. options = Object.assign({}, options, { type: QueryTypes.DESCRIBE });
  220. return qi.sequelize.query(sql, options).then(data => {
  221. /*
  222. * If no data is returned from the query, then the table name may be wrong.
  223. * Query generators that use information_schema for retrieving table info will just return an empty result set,
  224. * it will not throw an error like built-ins do (e.g. DESCRIBE on MySql).
  225. */
  226. if (_.isEmpty(data)) {
  227. throw new Error(`No description found for "${tableName}" table. Check the table name and schema; remember, they _are_ case sensitive.`);
  228. }
  229. return qi.showIndex(tableName, options).then(indexes => {
  230. for (const prop in data) {
  231. data[prop].unique = false;
  232. }
  233. for (const index of indexes) {
  234. for (const field of index.fields) {
  235. if (index.unique !== undefined) {
  236. data[field.attribute].unique = index.unique;
  237. }
  238. }
  239. }
  240. return qi.getForeignKeyReferencesForTable(tableName, options).then(foreignKeys => {
  241. for (const foreignKey of foreignKeys) {
  242. data[foreignKey.columnName].references = {
  243. model: foreignKey.referencedTableName,
  244. key: foreignKey.referencedColumnName
  245. };
  246. }
  247. return data;
  248. });
  249. });
  250. }).catch(e => {
  251. if (e.original && e.original.code === 'ER_NO_SUCH_TABLE') {
  252. throw Error(`No description found for "${tableName}" table. Check the table name and schema; remember, they _are_ case sensitive.`);
  253. }
  254. throw e;
  255. });
  256. }
  257. exports.describeTable = describeTable;