query-generator.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469
  1. 'use strict';
  2. const Utils = require('../../utils');
  3. const Transaction = require('../../transaction');
  4. const _ = require('lodash');
  5. const MySqlQueryGenerator = require('../mysql/query-generator');
  6. const AbstractQueryGenerator = require('../abstract/query-generator');
  7. class SQLiteQueryGenerator extends MySqlQueryGenerator {
  8. createSchema() {
  9. return "SELECT name FROM `sqlite_master` WHERE type='table' and name!='sqlite_sequence';";
  10. }
  11. showSchemasQuery() {
  12. return "SELECT name FROM `sqlite_master` WHERE type='table' and name!='sqlite_sequence';";
  13. }
  14. versionQuery() {
  15. return 'SELECT sqlite_version() as `version`';
  16. }
  17. createTableQuery(tableName, attributes, options) {
  18. options = options || {};
  19. const primaryKeys = [];
  20. const needsMultiplePrimaryKeys = Object.values(attributes).filter(definition => definition.includes('PRIMARY KEY')).length > 1;
  21. const attrArray = [];
  22. for (const attr in attributes) {
  23. if (Object.prototype.hasOwnProperty.call(attributes, attr)) {
  24. const dataType = attributes[attr];
  25. const containsAutoIncrement = dataType.includes('AUTOINCREMENT');
  26. let dataTypeString = dataType;
  27. if (dataType.includes('PRIMARY KEY')) {
  28. if (dataType.includes('INT')) {
  29. // Only INTEGER is allowed for primary key, see https://github.com/sequelize/sequelize/issues/969 (no lenght, unsigned etc)
  30. dataTypeString = containsAutoIncrement ? 'INTEGER PRIMARY KEY AUTOINCREMENT' : 'INTEGER PRIMARY KEY';
  31. if (dataType.includes(' REFERENCES')) {
  32. dataTypeString += dataType.substr(dataType.indexOf(' REFERENCES'));
  33. }
  34. }
  35. if (needsMultiplePrimaryKeys) {
  36. primaryKeys.push(attr);
  37. if (dataType.includes('NOT NULL')) {
  38. dataTypeString = dataType.replace(' PRIMARY KEY', '');
  39. } else {
  40. dataTypeString = dataType.replace('PRIMARY KEY', 'NOT NULL');
  41. }
  42. }
  43. }
  44. attrArray.push(`${this.quoteIdentifier(attr)} ${dataTypeString}`);
  45. }
  46. }
  47. const table = this.quoteTable(tableName);
  48. let attrStr = attrArray.join(', ');
  49. const pkString = primaryKeys.map(pk => this.quoteIdentifier(pk)).join(', ');
  50. if (options.uniqueKeys) {
  51. _.each(options.uniqueKeys, columns => {
  52. if (columns.customIndex) {
  53. attrStr += `, UNIQUE (${columns.fields.map(field => this.quoteIdentifier(field)).join(', ')})`;
  54. }
  55. });
  56. }
  57. if (pkString.length > 0) {
  58. attrStr += `, PRIMARY KEY (${pkString})`;
  59. }
  60. const sql = `CREATE TABLE IF NOT EXISTS ${table} (${attrStr});`;
  61. return this.replaceBooleanDefaults(sql);
  62. }
  63. booleanValue(value) {
  64. return value ? 1 : 0;
  65. }
  66. /**
  67. * Check whether the statmement is json function or simple path
  68. *
  69. * @param {string} stmt The statement to validate
  70. * @returns {boolean} true if the given statement is json function
  71. * @throws {Error} throw if the statement looks like json function but has invalid token
  72. */
  73. _checkValidJsonStatement(stmt) {
  74. if (typeof stmt !== 'string') {
  75. return false;
  76. }
  77. // https://sqlite.org/json1.html
  78. const jsonFunctionRegex = /^\s*(json(?:_[a-z]+){0,2})\([^)]*\)/i;
  79. const tokenCaptureRegex = /^\s*((?:([`"'])(?:(?!\2).|\2{2})*\2)|[\w\d\s]+|[().,;+-])/i;
  80. let currentIndex = 0;
  81. let openingBrackets = 0;
  82. let closingBrackets = 0;
  83. let hasJsonFunction = false;
  84. let hasInvalidToken = false;
  85. while (currentIndex < stmt.length) {
  86. const string = stmt.substr(currentIndex);
  87. const functionMatches = jsonFunctionRegex.exec(string);
  88. if (functionMatches) {
  89. currentIndex += functionMatches[0].indexOf('(');
  90. hasJsonFunction = true;
  91. continue;
  92. }
  93. const tokenMatches = tokenCaptureRegex.exec(string);
  94. if (tokenMatches) {
  95. const capturedToken = tokenMatches[1];
  96. if (capturedToken === '(') {
  97. openingBrackets++;
  98. } else if (capturedToken === ')') {
  99. closingBrackets++;
  100. } else if (capturedToken === ';') {
  101. hasInvalidToken = true;
  102. break;
  103. }
  104. currentIndex += tokenMatches[0].length;
  105. continue;
  106. }
  107. break;
  108. }
  109. // Check invalid json statement
  110. hasInvalidToken |= openingBrackets !== closingBrackets;
  111. if (hasJsonFunction && hasInvalidToken) {
  112. throw new Error(`Invalid json statement: ${stmt}`);
  113. }
  114. // return true if the statement has valid json function
  115. return hasJsonFunction;
  116. }
  117. //sqlite can't cast to datetime so we need to convert date values to their ISO strings
  118. _toJSONValue(value) {
  119. if (value instanceof Date) {
  120. return value.toISOString();
  121. }
  122. if (Array.isArray(value) && value[0] instanceof Date) {
  123. return value.map(val => val.toISOString());
  124. }
  125. return value;
  126. }
  127. handleSequelizeMethod(smth, tableName, factory, options, prepend) {
  128. if (smth instanceof Utils.Json) {
  129. return super.handleSequelizeMethod(smth, tableName, factory, options, prepend);
  130. }
  131. if (smth instanceof Utils.Cast) {
  132. if (/timestamp/i.test(smth.type)) {
  133. smth.type = 'datetime';
  134. }
  135. }
  136. return AbstractQueryGenerator.prototype.handleSequelizeMethod.call(this, smth, tableName, factory, options, prepend);
  137. }
  138. addColumnQuery(table, key, dataType) {
  139. const attributes = {};
  140. attributes[key] = dataType;
  141. const fields = this.attributesToSQL(attributes, { context: 'addColumn' });
  142. const attribute = `${this.quoteIdentifier(key)} ${fields[key]}`;
  143. const sql = `ALTER TABLE ${this.quoteTable(table)} ADD ${attribute};`;
  144. return this.replaceBooleanDefaults(sql);
  145. }
  146. showTablesQuery() {
  147. return 'SELECT name FROM `sqlite_master` WHERE type=\'table\' and name!=\'sqlite_sequence\';';
  148. }
  149. updateQuery(tableName, attrValueHash, where, options, attributes) {
  150. options = options || {};
  151. _.defaults(options, this.options);
  152. attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, options.omitNull, options);
  153. const modelAttributeMap = {};
  154. const values = [];
  155. const bind = [];
  156. const bindParam = options.bindParam || this.bindParam(bind);
  157. if (attributes) {
  158. _.each(attributes, (attribute, key) => {
  159. modelAttributeMap[key] = attribute;
  160. if (attribute.field) {
  161. modelAttributeMap[attribute.field] = attribute;
  162. }
  163. });
  164. }
  165. for (const key in attrValueHash) {
  166. const value = attrValueHash[key];
  167. if (value instanceof Utils.SequelizeMethod || options.bindParam === false) {
  168. values.push(`${this.quoteIdentifier(key)}=${this.escape(value, modelAttributeMap && modelAttributeMap[key] || undefined, { context: 'UPDATE' })}`);
  169. } else {
  170. values.push(`${this.quoteIdentifier(key)}=${this.format(value, modelAttributeMap && modelAttributeMap[key] || undefined, { context: 'UPDATE' }, bindParam)}`);
  171. }
  172. }
  173. let query;
  174. const whereOptions = { ...options, bindParam };
  175. if (options.limit) {
  176. query = `UPDATE ${this.quoteTable(tableName)} SET ${values.join(',')} WHERE rowid IN (SELECT rowid FROM ${this.quoteTable(tableName)} ${this.whereQuery(where, whereOptions)} LIMIT ${this.escape(options.limit)})`;
  177. } else {
  178. query = `UPDATE ${this.quoteTable(tableName)} SET ${values.join(',')} ${this.whereQuery(where, whereOptions)}`;
  179. }
  180. return { query, bind };
  181. }
  182. truncateTableQuery(tableName, options = {}) {
  183. return [
  184. `DELETE FROM ${this.quoteTable(tableName)}`,
  185. options.restartIdentity ? `; DELETE FROM ${this.quoteTable('sqlite_sequence')} WHERE ${this.quoteIdentifier('name')} = ${Utils.addTicks(Utils.removeTicks(this.quoteTable(tableName), '`'), "'")};` : ''
  186. ].join('');
  187. }
  188. deleteQuery(tableName, where, options = {}, model) {
  189. _.defaults(options, this.options);
  190. let whereClause = this.getWhereConditions(where, null, model, options);
  191. if (whereClause) {
  192. whereClause = `WHERE ${whereClause}`;
  193. }
  194. if (options.limit) {
  195. whereClause = `WHERE rowid IN (SELECT rowid FROM ${this.quoteTable(tableName)} ${whereClause} LIMIT ${this.escape(options.limit)})`;
  196. }
  197. return `DELETE FROM ${this.quoteTable(tableName)} ${whereClause}`;
  198. }
  199. attributesToSQL(attributes) {
  200. const result = {};
  201. for (const name in attributes) {
  202. const dataType = attributes[name];
  203. const fieldName = dataType.field || name;
  204. if (_.isObject(dataType)) {
  205. let sql = dataType.type.toString();
  206. if (Object.prototype.hasOwnProperty.call(dataType, 'allowNull') && !dataType.allowNull) {
  207. sql += ' NOT NULL';
  208. }
  209. if (Utils.defaultValueSchemable(dataType.defaultValue)) {
  210. // TODO thoroughly check that DataTypes.NOW will properly
  211. // get populated on all databases as DEFAULT value
  212. // i.e. mysql requires: DEFAULT CURRENT_TIMESTAMP
  213. sql += ` DEFAULT ${this.escape(dataType.defaultValue, dataType)}`;
  214. }
  215. if (dataType.unique === true) {
  216. sql += ' UNIQUE';
  217. }
  218. if (dataType.primaryKey) {
  219. sql += ' PRIMARY KEY';
  220. if (dataType.autoIncrement) {
  221. sql += ' AUTOINCREMENT';
  222. }
  223. }
  224. if (dataType.references) {
  225. const referencesTable = this.quoteTable(dataType.references.model);
  226. let referencesKey;
  227. if (dataType.references.key) {
  228. referencesKey = this.quoteIdentifier(dataType.references.key);
  229. } else {
  230. referencesKey = this.quoteIdentifier('id');
  231. }
  232. sql += ` REFERENCES ${referencesTable} (${referencesKey})`;
  233. if (dataType.onDelete) {
  234. sql += ` ON DELETE ${dataType.onDelete.toUpperCase()}`;
  235. }
  236. if (dataType.onUpdate) {
  237. sql += ` ON UPDATE ${dataType.onUpdate.toUpperCase()}`;
  238. }
  239. }
  240. result[fieldName] = sql;
  241. } else {
  242. result[fieldName] = dataType;
  243. }
  244. }
  245. return result;
  246. }
  247. showIndexesQuery(tableName) {
  248. return `PRAGMA INDEX_LIST(${this.quoteTable(tableName)})`;
  249. }
  250. showConstraintsQuery(tableName, constraintName) {
  251. let sql = `SELECT sql FROM sqlite_master WHERE tbl_name='${tableName}'`;
  252. if (constraintName) {
  253. sql += ` AND sql LIKE '%${constraintName}%'`;
  254. }
  255. return `${sql};`;
  256. }
  257. removeIndexQuery(tableName, indexNameOrAttributes) {
  258. let indexName = indexNameOrAttributes;
  259. if (typeof indexName !== 'string') {
  260. indexName = Utils.underscore(`${tableName}_${indexNameOrAttributes.join('_')}`);
  261. }
  262. return `DROP INDEX IF EXISTS ${this.quoteIdentifier(indexName)}`;
  263. }
  264. describeTableQuery(tableName, schema, schemaDelimiter) {
  265. const table = {
  266. _schema: schema,
  267. _schemaDelimiter: schemaDelimiter,
  268. tableName
  269. };
  270. return `PRAGMA TABLE_INFO(${this.quoteTable(this.addSchema(table))});`;
  271. }
  272. describeCreateTableQuery(tableName) {
  273. return `SELECT sql FROM sqlite_master WHERE tbl_name='${tableName}';`;
  274. }
  275. removeColumnQuery(tableName, attributes) {
  276. attributes = this.attributesToSQL(attributes);
  277. let backupTableName;
  278. if (typeof tableName === 'object') {
  279. backupTableName = {
  280. tableName: `${tableName.tableName}_backup`,
  281. schema: tableName.schema
  282. };
  283. } else {
  284. backupTableName = `${tableName}_backup`;
  285. }
  286. const quotedTableName = this.quoteTable(tableName);
  287. const quotedBackupTableName = this.quoteTable(backupTableName);
  288. const attributeNames = Object.keys(attributes).map(attr => this.quoteIdentifier(attr)).join(', ');
  289. // Temporary table cannot work for foreign keys.
  290. return `${this.createTableQuery(backupTableName, attributes)
  291. }INSERT INTO ${quotedBackupTableName} SELECT ${attributeNames} FROM ${quotedTableName};`
  292. + `DROP TABLE ${quotedTableName};${
  293. this.createTableQuery(tableName, attributes)
  294. }INSERT INTO ${quotedTableName} SELECT ${attributeNames} FROM ${quotedBackupTableName};`
  295. + `DROP TABLE ${quotedBackupTableName};`;
  296. }
  297. _alterConstraintQuery(tableName, attributes, createTableSql) {
  298. let backupTableName;
  299. attributes = this.attributesToSQL(attributes);
  300. if (typeof tableName === 'object') {
  301. backupTableName = {
  302. tableName: `${tableName.tableName}_backup`,
  303. schema: tableName.schema
  304. };
  305. } else {
  306. backupTableName = `${tableName}_backup`;
  307. }
  308. const quotedTableName = this.quoteTable(tableName);
  309. const quotedBackupTableName = this.quoteTable(backupTableName);
  310. const attributeNames = Object.keys(attributes).map(attr => this.quoteIdentifier(attr)).join(', ');
  311. return `${createTableSql
  312. .replace(`CREATE TABLE ${quotedTableName}`, `CREATE TABLE ${quotedBackupTableName}`)
  313. .replace(`CREATE TABLE ${quotedTableName.replace(/`/g, '"')}`, `CREATE TABLE ${quotedBackupTableName}`)
  314. }INSERT INTO ${quotedBackupTableName} SELECT ${attributeNames} FROM ${quotedTableName};`
  315. + `DROP TABLE ${quotedTableName};`
  316. + `ALTER TABLE ${quotedBackupTableName} RENAME TO ${quotedTableName};`;
  317. }
  318. renameColumnQuery(tableName, attrNameBefore, attrNameAfter, attributes) {
  319. let backupTableName;
  320. attributes = this.attributesToSQL(attributes);
  321. if (typeof tableName === 'object') {
  322. backupTableName = {
  323. tableName: `${tableName.tableName}_backup`,
  324. schema: tableName.schema
  325. };
  326. } else {
  327. backupTableName = `${tableName}_backup`;
  328. }
  329. const quotedTableName = this.quoteTable(tableName);
  330. const quotedBackupTableName = this.quoteTable(backupTableName);
  331. const attributeNamesImport = Object.keys(attributes).map(attr =>
  332. attrNameAfter === attr ? `${this.quoteIdentifier(attrNameBefore)} AS ${this.quoteIdentifier(attr)}` : this.quoteIdentifier(attr)
  333. ).join(', ');
  334. const attributeNamesExport = Object.keys(attributes).map(attr => this.quoteIdentifier(attr)).join(', ');
  335. // Temporary tables don't support foreign keys, so creating a temporary table will not allow foreign keys to be preserved
  336. return `${this.createTableQuery(backupTableName, attributes)
  337. }INSERT INTO ${quotedBackupTableName} SELECT ${attributeNamesImport} FROM ${quotedTableName};`
  338. + `DROP TABLE ${quotedTableName};${
  339. this.createTableQuery(tableName, attributes)
  340. }INSERT INTO ${quotedTableName} SELECT ${attributeNamesExport} FROM ${quotedBackupTableName};`
  341. + `DROP TABLE ${quotedBackupTableName};`;
  342. }
  343. startTransactionQuery(transaction) {
  344. if (transaction.parent) {
  345. return `SAVEPOINT ${this.quoteIdentifier(transaction.name)};`;
  346. }
  347. return `BEGIN ${transaction.options.type} TRANSACTION;`;
  348. }
  349. setIsolationLevelQuery(value) {
  350. switch (value) {
  351. case Transaction.ISOLATION_LEVELS.REPEATABLE_READ:
  352. return '-- SQLite is not able to choose the isolation level REPEATABLE READ.';
  353. case Transaction.ISOLATION_LEVELS.READ_UNCOMMITTED:
  354. return 'PRAGMA read_uncommitted = ON;';
  355. case Transaction.ISOLATION_LEVELS.READ_COMMITTED:
  356. return 'PRAGMA read_uncommitted = OFF;';
  357. case Transaction.ISOLATION_LEVELS.SERIALIZABLE:
  358. return '-- SQLite\'s default isolation level is SERIALIZABLE. Nothing to do.';
  359. default:
  360. throw new Error(`Unknown isolation level: ${value}`);
  361. }
  362. }
  363. replaceBooleanDefaults(sql) {
  364. return sql.replace(/DEFAULT '?false'?/g, 'DEFAULT 0').replace(/DEFAULT '?true'?/g, 'DEFAULT 1');
  365. }
  366. /**
  367. * Generates an SQL query that returns all foreign keys of a table.
  368. *
  369. * @param {string} tableName The name of the table.
  370. * @returns {string} The generated sql query.
  371. * @private
  372. */
  373. getForeignKeysQuery(tableName) {
  374. return `PRAGMA foreign_key_list(${tableName})`;
  375. }
  376. }
  377. module.exports = SQLiteQueryGenerator;