123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238 |
- 'use strict';
- const sequelizeErrors = require('../../errors');
- const QueryTypes = require('../../query-types');
- const { QueryInterface } = require('../abstract/query-interface');
- const { cloneDeep } = require('../../utils');
- const _ = require('lodash');
- /**
- * The interface that Sequelize uses to talk with SQLite database
- */
- class SQLiteQueryInterface extends QueryInterface {
- /**
- * A wrapper that fixes SQLite's inability to remove columns from existing tables.
- * It will create a backup of the table, drop the table afterwards and create a
- * new table with the same name but without the obsolete column.
- *
- * @override
- */
- async removeColumn(tableName, attributeName, options) {
- options = options || {};
- const fields = await this.describeTable(tableName, options);
- delete fields[attributeName];
- const sql = this.queryGenerator.removeColumnQuery(tableName, fields);
- const subQueries = sql.split(';').filter(q => q !== '');
- for (const subQuery of subQueries) await this.sequelize.query(`${subQuery};`, { raw: true, ...options });
- }
- /**
- * A wrapper that fixes SQLite's inability to change columns from existing tables.
- * It will create a backup of the table, drop the table afterwards and create a
- * new table with the same name but with a modified version of the respective column.
- *
- * @override
- */
- async changeColumn(tableName, attributeName, dataTypeOrOptions, options) {
- options = options || {};
- const fields = await this.describeTable(tableName, options);
- Object.assign(fields[attributeName], this.normalizeAttribute(dataTypeOrOptions));
- const sql = this.queryGenerator.removeColumnQuery(tableName, fields);
- const subQueries = sql.split(';').filter(q => q !== '');
- for (const subQuery of subQueries) await this.sequelize.query(`${subQuery};`, { raw: true, ...options });
- }
- /**
- * A wrapper that fixes SQLite's inability to rename columns from existing tables.
- * It will create a backup of the table, drop the table afterwards and create a
- * new table with the same name but with a renamed version of the respective column.
- *
- * @override
- */
- async renameColumn(tableName, attrNameBefore, attrNameAfter, options) {
- options = options || {};
- const fields = await this.assertTableHasColumn(tableName, attrNameBefore, options);
- fields[attrNameAfter] = { ...fields[attrNameBefore] };
- delete fields[attrNameBefore];
- const sql = this.queryGenerator.renameColumnQuery(tableName, attrNameBefore, attrNameAfter, fields);
- const subQueries = sql.split(';').filter(q => q !== '');
- for (const subQuery of subQueries) await this.sequelize.query(`${subQuery};`, { raw: true, ...options });
- }
- /**
- * @override
- */
- async removeConstraint(tableName, constraintName, options) {
- let createTableSql;
- const constraints = await this.showConstraint(tableName, constraintName);
- // sqlite can't show only one constraint, so we find here the one to remove
- const constraint = constraints.find(constaint => constaint.constraintName === constraintName);
- if (!constraint) {
- throw new sequelizeErrors.UnknownConstraintError({
- message: `Constraint ${constraintName} on table ${tableName} does not exist`,
- constraint: constraintName,
- table: tableName
- });
- }
- createTableSql = constraint.sql;
- constraint.constraintName = this.queryGenerator.quoteIdentifier(constraint.constraintName);
- let constraintSnippet = `, CONSTRAINT ${constraint.constraintName} ${constraint.constraintType} ${constraint.constraintCondition}`;
- if (constraint.constraintType === 'FOREIGN KEY') {
- const referenceTableName = this.queryGenerator.quoteTable(constraint.referenceTableName);
- constraint.referenceTableKeys = constraint.referenceTableKeys.map(columnName => this.queryGenerator.quoteIdentifier(columnName));
- const referenceTableKeys = constraint.referenceTableKeys.join(', ');
- constraintSnippet += ` REFERENCES ${referenceTableName} (${referenceTableKeys})`;
- constraintSnippet += ` ON UPDATE ${constraint.updateAction}`;
- constraintSnippet += ` ON DELETE ${constraint.deleteAction}`;
- }
- createTableSql = createTableSql.replace(constraintSnippet, '');
- createTableSql += ';';
- const fields = await this.describeTable(tableName, options);
- const sql = this.queryGenerator._alterConstraintQuery(tableName, fields, createTableSql);
- const subQueries = sql.split(';').filter(q => q !== '');
- for (const subQuery of subQueries) await this.sequelize.query(`${subQuery};`, { raw: true, ...options });
- }
- /**
- * @override
- */
- async addConstraint(tableName, options) {
- if (!options.fields) {
- throw new Error('Fields must be specified through options.fields');
- }
- if (!options.type) {
- throw new Error('Constraint type must be specified through options.type');
- }
- options = cloneDeep(options);
- const constraintSnippet = this.queryGenerator.getConstraintSnippet(tableName, options);
- const describeCreateTableSql = this.queryGenerator.describeCreateTableQuery(tableName);
- const constraints = await this.sequelize.query(describeCreateTableSql, { ...options, type: QueryTypes.SELECT, raw: true });
- let sql = constraints[0].sql;
- const index = sql.length - 1;
- //Replace ending ')' with constraint snippet - Simulates String.replaceAt
- //http://stackoverflow.com/questions/1431094
- const createTableSql = `${sql.substr(0, index)}, ${constraintSnippet})${sql.substr(index + 1)};`;
- const fields = await this.describeTable(tableName, options);
- sql = this.queryGenerator._alterConstraintQuery(tableName, fields, createTableSql);
- const subQueries = sql.split(';').filter(q => q !== '');
- for (const subQuery of subQueries) await this.sequelize.query(`${subQuery};`, { raw: true, ...options });
- }
- /**
- * @override
- */
- async getForeignKeyReferencesForTable(tableName, options) {
- const database = this.sequelize.config.database;
- const query = this.queryGenerator.getForeignKeysQuery(tableName, database);
- const result = await this.sequelize.query(query, options);
- return result.map(row => ({
- tableName,
- columnName: row.from,
- referencedTableName: row.table,
- referencedColumnName: row.to,
- tableCatalog: database,
- referencedTableCatalog: database
- }));
- }
- /**
- * @override
- */
- async dropAllTables(options) {
- options = options || {};
- const skip = options.skip || [];
- const tableNames = await this.showAllTables(options);
- await this.sequelize.query('PRAGMA foreign_keys = OFF', options);
- await this._dropAllTables(tableNames, skip, options);
- await this.sequelize.query('PRAGMA foreign_keys = ON', options);
- }
- /**
- * @override
- */
- async describeTable(tableName, options) {
- let schema = null;
- let schemaDelimiter = null;
- if (typeof options === 'string') {
- schema = options;
- } else if (typeof options === 'object' && options !== null) {
- schema = options.schema || null;
- schemaDelimiter = options.schemaDelimiter || null;
- }
- if (typeof tableName === 'object' && tableName !== null) {
- schema = tableName.schema;
- tableName = tableName.tableName;
- }
- const sql = this.queryGenerator.describeTableQuery(tableName, schema, schemaDelimiter);
- options = { ...options, type: QueryTypes.DESCRIBE };
- const sqlIndexes = this.queryGenerator.showIndexesQuery(tableName);
- try {
- const data = await this.sequelize.query(sql, options);
- /*
- * If no data is returned from the query, then the table name may be wrong.
- * Query generators that use information_schema for retrieving table info will just return an empty result set,
- * it will not throw an error like built-ins do (e.g. DESCRIBE on MySql).
- */
- if (_.isEmpty(data)) {
- throw new Error(`No description found for "${tableName}" table. Check the table name and schema; remember, they _are_ case sensitive.`);
- }
- const indexes = await this.sequelize.query(sqlIndexes, options);
- for (const prop in data) {
- data[prop].unique = false;
- }
- for (const index of indexes) {
- for (const field of index.fields) {
- if (index.unique !== undefined) {
- data[field.attribute].unique = index.unique;
- }
- }
- }
- const foreignKeys = await this.getForeignKeyReferencesForTable(tableName, options);
- for (const foreignKey of foreignKeys) {
- data[foreignKey.columnName].references = {
- model: foreignKey.referencedTableName,
- key: foreignKey.referencedColumnName
- };
- }
- return data;
- } catch (e) {
- if (e.original && e.original.code === 'ER_NO_SUCH_TABLE') {
- throw new Error(`No description found for "${tableName}" table. Check the table name and schema; remember, they _are_ case sensitive.`);
- }
- throw e;
- }
- }
- }
- exports.SQLiteQueryInterface = SQLiteQueryInterface;
|