API Reference Source

lib/dialects/sqlite/query-interface.js

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