query-interface.js 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. 'use strict';
  2. const sequelizeErrors = require('../../errors');
  3. const QueryTypes = require('../../query-types');
  4. const { QueryInterface } = require('../abstract/query-interface');
  5. const { cloneDeep } = require('../../utils');
  6. const _ = require('lodash');
  7. /**
  8. * The interface that Sequelize uses to talk with SQLite database
  9. */
  10. class SQLiteQueryInterface extends QueryInterface {
  11. /**
  12. * A wrapper that fixes SQLite's inability to remove columns from existing tables.
  13. * It will create a backup of the table, drop the table afterwards and create a
  14. * new table with the same name but without the obsolete column.
  15. *
  16. * @override
  17. */
  18. async removeColumn(tableName, attributeName, options) {
  19. options = options || {};
  20. const fields = await this.describeTable(tableName, options);
  21. delete fields[attributeName];
  22. const sql = this.queryGenerator.removeColumnQuery(tableName, fields);
  23. const subQueries = sql.split(';').filter(q => q !== '');
  24. for (const subQuery of subQueries) await this.sequelize.query(`${subQuery};`, { raw: true, ...options });
  25. }
  26. /**
  27. * A wrapper that fixes SQLite's inability to change columns from existing tables.
  28. * It will create a backup of the table, drop the table afterwards and create a
  29. * new table with the same name but with a modified version of the respective column.
  30. *
  31. * @override
  32. */
  33. async changeColumn(tableName, attributeName, dataTypeOrOptions, options) {
  34. options = options || {};
  35. const fields = await this.describeTable(tableName, options);
  36. Object.assign(fields[attributeName], this.normalizeAttribute(dataTypeOrOptions));
  37. const sql = this.queryGenerator.removeColumnQuery(tableName, fields);
  38. const subQueries = sql.split(';').filter(q => q !== '');
  39. for (const subQuery of subQueries) await this.sequelize.query(`${subQuery};`, { raw: true, ...options });
  40. }
  41. /**
  42. * A wrapper that fixes SQLite's inability to rename columns from existing tables.
  43. * It will create a backup of the table, drop the table afterwards and create a
  44. * new table with the same name but with a renamed version of the respective column.
  45. *
  46. * @override
  47. */
  48. async renameColumn(tableName, attrNameBefore, attrNameAfter, options) {
  49. options = options || {};
  50. const fields = await this.assertTableHasColumn(tableName, attrNameBefore, options);
  51. fields[attrNameAfter] = { ...fields[attrNameBefore] };
  52. delete fields[attrNameBefore];
  53. const sql = this.queryGenerator.renameColumnQuery(tableName, attrNameBefore, attrNameAfter, fields);
  54. const subQueries = sql.split(';').filter(q => q !== '');
  55. for (const subQuery of subQueries) await this.sequelize.query(`${subQuery};`, { raw: true, ...options });
  56. }
  57. /**
  58. * @override
  59. */
  60. async removeConstraint(tableName, constraintName, options) {
  61. let createTableSql;
  62. const constraints = await this.showConstraint(tableName, constraintName);
  63. // sqlite can't show only one constraint, so we find here the one to remove
  64. const constraint = constraints.find(constaint => constaint.constraintName === constraintName);
  65. if (!constraint) {
  66. throw new sequelizeErrors.UnknownConstraintError({
  67. message: `Constraint ${constraintName} on table ${tableName} does not exist`,
  68. constraint: constraintName,
  69. table: tableName
  70. });
  71. }
  72. createTableSql = constraint.sql;
  73. constraint.constraintName = this.queryGenerator.quoteIdentifier(constraint.constraintName);
  74. let constraintSnippet = `, CONSTRAINT ${constraint.constraintName} ${constraint.constraintType} ${constraint.constraintCondition}`;
  75. if (constraint.constraintType === 'FOREIGN KEY') {
  76. const referenceTableName = this.queryGenerator.quoteTable(constraint.referenceTableName);
  77. constraint.referenceTableKeys = constraint.referenceTableKeys.map(columnName => this.queryGenerator.quoteIdentifier(columnName));
  78. const referenceTableKeys = constraint.referenceTableKeys.join(', ');
  79. constraintSnippet += ` REFERENCES ${referenceTableName} (${referenceTableKeys})`;
  80. constraintSnippet += ` ON UPDATE ${constraint.updateAction}`;
  81. constraintSnippet += ` ON DELETE ${constraint.deleteAction}`;
  82. }
  83. createTableSql = createTableSql.replace(constraintSnippet, '');
  84. createTableSql += ';';
  85. const fields = await this.describeTable(tableName, options);
  86. const sql = this.queryGenerator._alterConstraintQuery(tableName, fields, createTableSql);
  87. const subQueries = sql.split(';').filter(q => q !== '');
  88. for (const subQuery of subQueries) await this.sequelize.query(`${subQuery};`, { raw: true, ...options });
  89. }
  90. /**
  91. * @override
  92. */
  93. async addConstraint(tableName, options) {
  94. if (!options.fields) {
  95. throw new Error('Fields must be specified through options.fields');
  96. }
  97. if (!options.type) {
  98. throw new Error('Constraint type must be specified through options.type');
  99. }
  100. options = cloneDeep(options);
  101. const constraintSnippet = this.queryGenerator.getConstraintSnippet(tableName, options);
  102. const describeCreateTableSql = this.queryGenerator.describeCreateTableQuery(tableName);
  103. const constraints = await this.sequelize.query(describeCreateTableSql, { ...options, type: QueryTypes.SELECT, raw: true });
  104. let sql = constraints[0].sql;
  105. const index = sql.length - 1;
  106. //Replace ending ')' with constraint snippet - Simulates String.replaceAt
  107. //http://stackoverflow.com/questions/1431094
  108. const createTableSql = `${sql.substr(0, index)}, ${constraintSnippet})${sql.substr(index + 1)};`;
  109. const fields = await this.describeTable(tableName, options);
  110. sql = this.queryGenerator._alterConstraintQuery(tableName, fields, createTableSql);
  111. const subQueries = sql.split(';').filter(q => q !== '');
  112. for (const subQuery of subQueries) await this.sequelize.query(`${subQuery};`, { raw: true, ...options });
  113. }
  114. /**
  115. * @override
  116. */
  117. async getForeignKeyReferencesForTable(tableName, options) {
  118. const database = this.sequelize.config.database;
  119. const query = this.queryGenerator.getForeignKeysQuery(tableName, database);
  120. const result = await this.sequelize.query(query, options);
  121. return result.map(row => ({
  122. tableName,
  123. columnName: row.from,
  124. referencedTableName: row.table,
  125. referencedColumnName: row.to,
  126. tableCatalog: database,
  127. referencedTableCatalog: database
  128. }));
  129. }
  130. /**
  131. * @override
  132. */
  133. async dropAllTables(options) {
  134. options = options || {};
  135. const skip = options.skip || [];
  136. const tableNames = await this.showAllTables(options);
  137. await this.sequelize.query('PRAGMA foreign_keys = OFF', options);
  138. await this._dropAllTables(tableNames, skip, options);
  139. await this.sequelize.query('PRAGMA foreign_keys = ON', options);
  140. }
  141. /**
  142. * @override
  143. */
  144. async describeTable(tableName, options) {
  145. let schema = null;
  146. let schemaDelimiter = null;
  147. if (typeof options === 'string') {
  148. schema = options;
  149. } else if (typeof options === 'object' && options !== null) {
  150. schema = options.schema || null;
  151. schemaDelimiter = options.schemaDelimiter || null;
  152. }
  153. if (typeof tableName === 'object' && tableName !== null) {
  154. schema = tableName.schema;
  155. tableName = tableName.tableName;
  156. }
  157. const sql = this.queryGenerator.describeTableQuery(tableName, schema, schemaDelimiter);
  158. options = { ...options, type: QueryTypes.DESCRIBE };
  159. const sqlIndexes = this.queryGenerator.showIndexesQuery(tableName);
  160. try {
  161. const data = await this.sequelize.query(sql, options);
  162. /*
  163. * If no data is returned from the query, then the table name may be wrong.
  164. * Query generators that use information_schema for retrieving table info will just return an empty result set,
  165. * it will not throw an error like built-ins do (e.g. DESCRIBE on MySql).
  166. */
  167. if (_.isEmpty(data)) {
  168. throw new Error(`No description found for "${tableName}" table. Check the table name and schema; remember, they _are_ case sensitive.`);
  169. }
  170. const indexes = await this.sequelize.query(sqlIndexes, options);
  171. for (const prop in data) {
  172. data[prop].unique = false;
  173. }
  174. for (const index of indexes) {
  175. for (const field of index.fields) {
  176. if (index.unique !== undefined) {
  177. data[field.attribute].unique = index.unique;
  178. }
  179. }
  180. }
  181. const foreignKeys = await this.getForeignKeyReferencesForTable(tableName, options);
  182. for (const foreignKey of foreignKeys) {
  183. data[foreignKey.columnName].references = {
  184. model: foreignKey.referencedTableName,
  185. key: foreignKey.referencedColumnName
  186. };
  187. }
  188. return data;
  189. } catch (e) {
  190. if (e.original && e.original.code === 'ER_NO_SUCH_TABLE') {
  191. throw new Error(`No description found for "${tableName}" table. Check the table name and schema; remember, they _are_ case sensitive.`);
  192. }
  193. throw e;
  194. }
  195. }
  196. }
  197. exports.SQLiteQueryInterface = SQLiteQueryInterface;