'use strict'; const Utils = require('./utils'); const _ = require('lodash'); const DataTypes = require('./data-types'); const SQLiteQueryInterface = require('./dialects/sqlite/query-interface'); const MSSSQLQueryInterface = require('./dialects/mssql/query-interface'); const MySQLQueryInterface = require('./dialects/mysql/query-interface'); const Transaction = require('./transaction'); const Promise = require('./promise'); const QueryTypes = require('./query-types'); const Op = require('./operators'); /** * The interface that Sequelize uses to talk to all databases * * @class QueryInterface */ class QueryInterface { constructor(sequelize) { this.sequelize = sequelize; this.QueryGenerator = this.sequelize.dialect.QueryGenerator; } /** * Creates a schema * * @param {String} schema Schema name to create * @param {Object} [options] Query options * * @return {Promise} */ createSchema(schema, options) { options = options || {}; const sql = this.QueryGenerator.createSchema(schema); return this.sequelize.query(sql, options); } /** * Drops a schema * * @param {String} schema Schema name to create * @param {Object} [options] Query options * * @return {Promise} */ dropSchema(schema, options) { options = options || {}; const sql = this.QueryGenerator.dropSchema(schema); return this.sequelize.query(sql, options); } /** * Drop all schemas * * @param {Object} [options] Query options * * @return {Promise} */ dropAllSchemas(options) { options = options || {}; if (!this.QueryGenerator._dialect.supports.schemas) { return this.sequelize.drop(options); } else { return this.showAllSchemas(options).map(schemaName => this.dropSchema(schemaName, options)); } } /** * Show all schemas * * @param {Object} [options] Query options * * @return {Promise} */ showAllSchemas(options) { options = _.assign({}, options, { raw: true, type: this.sequelize.QueryTypes.SELECT }); const showSchemasSql = this.QueryGenerator.showSchemasQuery(); return this.sequelize.query(showSchemasSql, options).then(schemaNames => _.flatten( _.map(schemaNames, value => value.schema_name ? value.schema_name : value) )); } /** * Returns database version * * @param {Object} [options] Query options * @param {QueryType} [options.type] Query type * * @returns {Promise} * @private */ databaseVersion(options) { return this.sequelize.query( this.QueryGenerator.versionQuery(), _.assign({}, options, { type: QueryTypes.VERSION }) ); } /** * Create a table with given set of attributes * * ```js * queryInterface.createTable( * 'nameOfTheNewTable', * { * id: { * type: Sequelize.INTEGER, * primaryKey: true, * autoIncrement: true * }, * createdAt: { * type: Sequelize.DATE * }, * updatedAt: { * type: Sequelize.DATE * }, * attr1: Sequelize.STRING, * attr2: Sequelize.INTEGER, * attr3: { * type: Sequelize.BOOLEAN, * defaultValue: false, * allowNull: false * }, * //foreign key usage * attr4: { * type: Sequelize.INTEGER, * references: { * model: 'another_table_name', * key: 'id' * }, * onUpdate: 'cascade', * onDelete: 'cascade' * } * }, * { * engine: 'MYISAM', // default: 'InnoDB' * charset: 'latin1', // default: null * schema: 'public' // default: public, PostgreSQL only. * } * ) * ``` * * @param {String} tableName Name of table to create * @param {Object} attributes Object representing a list of table attributes to create * @param {Object} [options] * @param {Model} [model] * * @return {Promise} */ createTable(tableName, attributes, options, model) { const keys = Object.keys(attributes); const keyLen = keys.length; let sql = ''; let i = 0; options = _.clone(options) || {}; attributes = _.mapValues(attributes, attribute => { if (!_.isPlainObject(attribute)) { attribute = { type: attribute, allowNull: true }; } attribute = this.sequelize.normalizeAttribute(attribute); return attribute; }); // Postgres requires a special SQL command for enums if (this.sequelize.options.dialect === 'postgres') { const promises = []; for (i = 0; i < keyLen; i++) { const attribute = attributes[keys[i]]; const type = attribute.type; if ( type instanceof DataTypes.ENUM || (type instanceof DataTypes.ARRAY && type.type instanceof DataTypes.ENUM) //ARRAY sub type is ENUM ) { sql = this.QueryGenerator.pgListEnums(tableName, attribute.field || keys[i], options); promises.push(this.sequelize.query( sql, _.assign({}, options, { plain: true, raw: true, type: QueryTypes.SELECT }) )); } } return Promise.all(promises).then(results => { const promises = []; let enumIdx = 0; for (i = 0; i < keyLen; i++) { const attribute = attributes[keys[i]]; const type = attribute.type; const enumType = type.type || type; if ( type instanceof DataTypes.ENUM || (type instanceof DataTypes.ARRAY && enumType instanceof DataTypes.ENUM) //ARRAY sub type is ENUM ) { // If the enum type doesn't exist then create it if (!results[enumIdx]) { sql = this.QueryGenerator.pgEnum(tableName, attribute.field || keys[i], enumType, options); promises.push(this.sequelize.query( sql, _.assign({}, options, { raw: true }) )); } else if (!!results[enumIdx] && !!model) { const enumVals = this.QueryGenerator.fromArray(results[enumIdx].enum_value); const vals = enumType.values; vals.forEach((value, idx) => { // reset out after/before options since it's for every enum value const valueOptions = _.clone(options); valueOptions.before = null; valueOptions.after = null; if (enumVals.indexOf(value) === -1) { if (vals[idx + 1]) { valueOptions.before = vals[idx + 1]; } else if (vals[idx - 1]) { valueOptions.after = vals[idx - 1]; } valueOptions.supportsSearchPath = false; promises.push(this.sequelize.query(this.QueryGenerator.pgEnumAdd(tableName, attribute.field || keys[i], value, valueOptions), valueOptions)); } }); enumIdx++; } } } if (!tableName.schema && (options.schema || !!model && model._schema)) { tableName = this.QueryGenerator.addSchema({ tableName, _schema: !!model && model._schema || options.schema }); } attributes = this.QueryGenerator.attributesToSQL(attributes, { context: 'createTable' }); sql = this.QueryGenerator.createTableQuery(tableName, attributes, options); return Promise.all(promises) .tap(() => { // If ENUM processed, then refresh OIDs if (promises.length) { return this.sequelize.dialect.connectionManager._refreshDynamicOIDs(); } }) .then(() => { return this.sequelize.query(sql, options); }); }); } else { if (!tableName.schema && (options.schema || !!model && model._schema)) { tableName = this.QueryGenerator.addSchema({ tableName, _schema: !!model && model._schema || options.schema }); } attributes = this.QueryGenerator.attributesToSQL(attributes, { context: 'createTable' }); sql = this.QueryGenerator.createTableQuery(tableName, attributes, options); return this.sequelize.query(sql, options); } } /** * Drops a table from database * * @param {String} tableName Table name to drop * @param {Object} options Query options * * @return {Promise} */ dropTable(tableName, options) { // if we're forcing we should be cascading unless explicitly stated otherwise options = _.clone(options) || {}; options.cascade = options.cascade || options.force || false; let sql = this.QueryGenerator.dropTableQuery(tableName, options); return this.sequelize.query(sql, options).then(() => { const promises = []; // Since postgres has a special case for enums, we should drop the related // enum type within the table and attribute if (this.sequelize.options.dialect === 'postgres') { const instanceTable = this.sequelize.modelManager.getModel(tableName, { attribute: 'tableName' }); if (instanceTable) { const getTableName = (!options || !options.schema || options.schema === 'public' ? '' : options.schema + '_') + tableName; const keys = Object.keys(instanceTable.rawAttributes); const keyLen = keys.length; for (let i = 0; i < keyLen; i++) { if (instanceTable.rawAttributes[keys[i]].type instanceof DataTypes.ENUM) { sql = this.QueryGenerator.pgEnumDrop(getTableName, keys[i]); options.supportsSearchPath = false; promises.push(this.sequelize.query(sql, _.assign({}, options, { raw: true }))); } } } } return Promise.all(promises).get(0); }); } /** * Drop all tables from database * * @param {Object} [options] * @param {Array} [options.skip] List of table to skip * * @return {Promise} */ dropAllTables(options) { options = options || {}; const skip = options.skip || []; const dropAllTables = tableNames => Promise.each(tableNames, tableName => { // if tableName is not in the Array of tables names then dont drop it if (skip.indexOf(tableName.tableName || tableName) === -1) { return this.dropTable(tableName, _.assign({}, options, { cascade: true }) ); } }); return this.showAllTables(options).then(tableNames => { if (this.sequelize.options.dialect === 'sqlite') { return this.sequelize.query('PRAGMA foreign_keys;', options).then(result => { const foreignKeysAreEnabled = result.foreign_keys === 1; if (foreignKeysAreEnabled) { return this.sequelize.query('PRAGMA foreign_keys = OFF', options) .then(() => dropAllTables(tableNames)) .then(() => this.sequelize.query('PRAGMA foreign_keys = ON', options)); } else { return dropAllTables(tableNames); } }); } else { return this.getForeignKeysForTables(tableNames, options).then(foreignKeys => { const promises = []; tableNames.forEach(tableName => { let normalizedTableName = tableName; if (_.isObject(tableName)) { normalizedTableName = tableName.schema + '.' + tableName.tableName; } foreignKeys[normalizedTableName].forEach(foreignKey => { const sql = this.QueryGenerator.dropForeignKeyQuery(tableName, foreignKey); promises.push(this.sequelize.query(sql, options)); }); }); return Promise.all(promises).then(() => dropAllTables(tableNames)); }); } }); } /** * Drop all enums from database, Postgres Only * * @param {Object} options Query options * * @return {Promise} * @private */ dropAllEnums(options) { if (this.sequelize.getDialect() !== 'postgres') { return Promise.resolve(); } options = options || {}; return this.pgListEnums(null, options).map(result => this.sequelize.query( this.QueryGenerator.pgEnumDrop(null, null, this.QueryGenerator.pgEscapeAndQuote(result.enum_name)), _.assign({}, options, { raw: true }) )); } /** * List all enums, Postgres Only * * @param {String} [tableName] Table whose enum to list * @param {Object} [options] Query options * * @return {Promise} * @private */ pgListEnums(tableName, options) { options = options || {}; const sql = this.QueryGenerator.pgListEnums(tableName); return this.sequelize.query(sql, _.assign({}, options, { plain: false, raw: true, type: QueryTypes.SELECT })); } /** * Renames a table * * @param {String} before Current name of table * @param {String} after New name from table * @param {Object} [options] Query options * * @return {Promise} */ renameTable(before, after, options) { options = options || {}; const sql = this.QueryGenerator.renameTableQuery(before, after); return this.sequelize.query(sql, options); } /** * Get all tables in current database * * @param {Object} [options] Query options * @param {Boolean} [options.raw=true] Run query in raw mode * @param {QueryType} [options.type=QueryType.SHOWTABLE] * * @return {Promise} * @private */ showAllTables(options) { options = _.assign({}, options, { raw: true, type: QueryTypes.SHOWTABLES }); const showTablesSql = this.QueryGenerator.showTablesQuery(); return this.sequelize.query(showTablesSql, options).then(tableNames => _.flatten(tableNames)); } /** * Describe a table structure * * This method returns an array of hashes containing information about all attributes in the table. * * ```js * { * name: { * type: 'VARCHAR(255)', // this will be 'CHARACTER VARYING' for pg! * allowNull: true, * defaultValue: null * }, * isBetaMember: { * type: 'TINYINT(1)', // this will be 'BOOLEAN' for pg! * allowNull: false, * defaultValue: false * } * } * ``` * @param {String} tableName * @param {Object} [options] Query options * * @return {Promise} */ 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); return this.sequelize.query( sql, _.assign({}, options, { type: QueryTypes.DESCRIBE }) ).then(data => { // 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)) { return Promise.reject('No description found for "' + tableName + '" table. Check the table name and schema; remember, they _are_ case sensitive.'); } else { return Promise.resolve(data); } }); } /** * Add a new column into a table * * @param {String} table Table to add column to * @param {String} key Column name * @param {Object} attribute Attribute definition * @param {Object} [options] Query options * * @return {Promise} */ addColumn(table, key, attribute, options) { if (!table || !key || !attribute) { throw new Error('addColumn takes atleast 3 arguments (table, attribute name, attribute definition)'); } options = options || {}; attribute = this.sequelize.normalizeAttribute(attribute); return this.sequelize.query(this.QueryGenerator.addColumnQuery(table, key, attribute), options); } /** * Remove a column from table * * @param {String} tableName Table to remove column from * @param {String} attributeName Columns name to remove * @param {Object} [options] Query options * * @return {Promise} */ removeColumn(tableName, attributeName, options) { options = options || {}; switch (this.sequelize.options.dialect) { case 'sqlite': // sqlite needs some special treatment as it cannot drop a column return SQLiteQueryInterface.removeColumn.call(this, tableName, attributeName, options); case 'mssql': // mssql needs special treatment as it cannot drop a column with a default or foreign key constraint return MSSSQLQueryInterface.removeColumn.call(this, tableName, attributeName, options); case 'mysql': // mysql needs special treatment as it cannot drop a column with a foreign key constraint return MySQLQueryInterface.removeColumn.call(this, tableName, attributeName, options); default: return this.sequelize.query(this.QueryGenerator.removeColumnQuery(tableName, attributeName), options); } } /** * Change a column definition * * @param {String} tableName Table name to change from * @param {String} attributeName Column name * @param {Object} dataTypeOrOptions Attribute definition for new column * @param {Object} [options] Query options * * @return {Promise} */ changeColumn(tableName, attributeName, dataTypeOrOptions, options) { const attributes = {}; options = options || {}; if (_.values(DataTypes).indexOf(dataTypeOrOptions) > -1) { attributes[attributeName] = { type: dataTypeOrOptions, allowNull: true }; } else { attributes[attributeName] = dataTypeOrOptions; } attributes[attributeName].type = this.sequelize.normalizeDataType(attributes[attributeName].type); if (this.sequelize.options.dialect === 'sqlite') { // sqlite needs some special treatment as it cannot change a column return SQLiteQueryInterface.changeColumn.call(this, tableName, attributes, options); } else { const query = this.QueryGenerator.attributesToSQL(attributes); const sql = this.QueryGenerator.changeColumnQuery(tableName, query); return this.sequelize.query(sql, options); } } /** * Rename a column * * @param {String} tableName Table name whose column to rename * @param {String} attrNameBefore Current column name * @param {String} attrNameAfter New column name * @param {Object} [options] Query option * * @return {Promise} */ renameColumn(tableName, attrNameBefore, attrNameAfter, options) { options = options || {}; return this.describeTable(tableName, options).then(data => { if (!data[attrNameBefore]) { throw new Error('Table ' + tableName + ' doesn\'t have the column ' + attrNameBefore); } data = data[attrNameBefore] || {}; const _options = {}; _options[attrNameAfter] = { attribute: attrNameAfter, type: data.type, allowNull: data.allowNull, defaultValue: data.defaultValue }; // fix: a not-null column cannot have null as default value if (data.defaultValue === null && !data.allowNull) { delete _options[attrNameAfter].defaultValue; } if (this.sequelize.options.dialect === 'sqlite') { // sqlite needs some special treatment as it cannot rename a column return SQLiteQueryInterface.renameColumn.call(this, tableName, attrNameBefore, attrNameAfter, options); } else { const sql = this.QueryGenerator.renameColumnQuery( tableName, attrNameBefore, this.QueryGenerator.attributesToSQL(_options) ); return this.sequelize.query(sql, options); } }); } /** * Add index to a column * * @param {String} tableName Table name to add index on * @param {Object} options * @param {Array} options.fields List of attributes to add index on * @param {Boolean} [options.unique] Create a unique index * @param {String} [options.using] Useful for GIN indexes * @param {String} [options.type] Type of index, available options are UNIQUE|FULLTEXT|SPATIAL * @param {String} [options.name] Name of the index. Default is __ * @param {Object} [options.where] Where condition on index, for partial indexes * * @return {Promise} */ addIndex(tableName, attributes, options, rawTablename) { // Support for passing tableName, attributes, options or tableName, options (with a fields param which is the attributes) if (!Array.isArray(attributes)) { rawTablename = options; options = attributes; attributes = options.fields; } // testhint argsConform.end if (!rawTablename) { // Map for backwards compat rawTablename = tableName; } options = Utils.cloneDeep(options); options.fields = attributes; const sql = this.QueryGenerator.addIndexQuery(tableName, options, rawTablename); return this.sequelize.query(sql, _.assign({}, options, { supportsSearchPath: false })); } /** * Show indexes on a table * * @param {String} tableName * @param {Object} [options] Query options * * @return {Promise} * @private */ showIndex(tableName, options) { const sql = this.QueryGenerator.showIndexesQuery(tableName, options); return this.sequelize.query(sql, _.assign({}, options, { type: QueryTypes.SHOWINDEXES })); } nameIndexes(indexes, rawTablename) { return this.QueryGenerator.nameIndexes(indexes, rawTablename); } getForeignKeysForTables(tableNames, options) { if (tableNames.length === 0) { return Promise.resolve({}); } options = _.assign({}, options || {}, { type: QueryTypes.FOREIGNKEYS }); return Promise.map(tableNames, tableName => this.sequelize.query(this.QueryGenerator.getForeignKeysQuery(tableName, this.sequelize.config.database), options) ).then(results => { const result = {}; tableNames.forEach((tableName, i) => { if (_.isObject(tableName)) { tableName = tableName.schema + '.' + tableName.tableName; } result[tableName] = _.isArray(results[i]) ? results[i].map(r => r.constraint_name) : [results[i] && results[i].constraint_name]; result[tableName] = result[tableName].filter(_.identity); }); return result; }); } /** * Get foreign key references details for the table. * * Those details contains constraintSchema, constraintName, constraintCatalog * tableCatalog, tableSchema, tableName, columnName, * referencedTableCatalog, referencedTableCatalog, referencedTableSchema, referencedTableName, referencedColumnName. * Remind: constraint informations won't return if it's sqlite. * * @param {String} tableName * @param {Object} [options] Query options * @returns {Promise} */ getForeignKeyReferencesForTable(tableName, options) { const queryOptions = Object.assign({}, options, { type: QueryTypes.FOREIGNKEYS }); const catalogName = this.sequelize.config.database; switch (this.sequelize.options.dialect) { case 'sqlite': // sqlite needs some special treatment. return SQLiteQueryInterface.getForeignKeyReferencesForTable.call(this, tableName, queryOptions); case 'postgres': { // postgres needs some special treatment as those field names returned are all lowercase // in order to keep same result with other dialects. const query = this.QueryGenerator.getForeignKeyReferencesQuery(tableName, catalogName); return this.sequelize.query(query, queryOptions) .then(result => result.map(Utils.camelizeObjectKeys)); } case 'mssql': case 'mysql': default: { const query = this.QueryGenerator.getForeignKeysQuery(tableName, catalogName); return this.sequelize.query(query, queryOptions); } } } /** * Remove an already existing index from a table * * @param {String} tableName Table name to drop index from * @param {String} indexNameOrAttributes Index name * @param {Object} [options] Query options * * @return {Promise} */ removeIndex(tableName, indexNameOrAttributes, options) { options = options || {}; const sql = this.QueryGenerator.removeIndexQuery(tableName, indexNameOrAttributes); return this.sequelize.query(sql, options); } /** * Add constraints to table * * Available constraints: * - UNIQUE * - DEFAULT (MSSQL only) * - CHECK (MySQL - Ignored by the database engine ) * - FOREIGN KEY * - PRIMARY KEY * * UNIQUE * ```js * queryInterface.addConstraint('Users', ['email'], { * type: 'unique', * name: 'custom_unique_constraint_name' * }); * ``` * * CHECK * ```js * queryInterface.addConstraint('Users', ['roles'], { * type: 'check', * where: { * roles: ['user', 'admin', 'moderator', 'guest'] * } * }); * ``` * Default - MSSQL only * ```js * queryInterface.addConstraint('Users', ['roles'], { * type: 'default', * defaultValue: 'guest' * }); * ``` * * Primary Key * ```js * queryInterface.addConstraint('Users', ['username'], { * type: 'primary key', * name: 'custom_primary_constraint_name' * }); * ``` * * Foreign Key * ```js * queryInterface.addConstraint('Posts', ['username'], { * type: 'foreign key', * name: 'custom_fkey_constraint_name', * references: { //Required field * table: 'target_table_name', * field: 'target_column_name' * }, * onDelete: 'cascade', * onUpdate: 'cascade' * }); * ``` * * @param {String} tableName Table name where you want to add a constraint * @param {Array} attributes Array of column names to apply the constraint over * @param {Object} options An object to define the constraint name, type etc * @param {String} options.type Type of constraint. One of the values in available constraints(case insensitive) * @param {String} [options.name] Name of the constraint. If not specified, sequelize automatically creates a named constraint based on constraint type, table & column names * @param {String} [options.defaultValue] The value for the default constraint * @param {Object} [options.where] Where clause/expression for the CHECK constraint * @param {Object} [options.references] Object specifying target table, column name to create foreign key constraint * @param {String} [options.references.table] Target table name * @param {String} [options.references.field] Target column name * * @return {Promise} */ addConstraint(tableName, attributes, options, rawTablename) { if (!Array.isArray(attributes)) { rawTablename = options; options = attributes; attributes = options.fields; } if (!options.type) { throw new Error('Constraint type must be specified through options.type'); } if (!rawTablename) { // Map for backwards compat rawTablename = tableName; } options = Utils.cloneDeep(options); options.fields = attributes; if (this.sequelize.dialect.name === 'sqlite') { return SQLiteQueryInterface.addConstraint.call(this, tableName, options, rawTablename); } else { const sql = this.QueryGenerator.addConstraintQuery(tableName, options, rawTablename); return this.sequelize.query(sql, options); } } showConstraint(tableName, constraintName, options) { const sql = this.QueryGenerator.showConstraintsQuery(tableName, constraintName); return this.sequelize.query(sql, Object.assign({}, options, { type: QueryTypes.SHOWCONSTRAINTS })); } /** * * @param {String} tableName Table name to drop constraint from * @param {String} constraintName Constraint name * @param {Object} options Query options * * @return {Promise} */ removeConstraint(tableName, constraintName, options) { options = options || {}; switch (this.sequelize.options.dialect) { case 'mysql': //Mysql does not support DROP CONSTRAINT. Instead DROP PRIMARY, FOREIGN KEY, INDEX should be used return MySQLQueryInterface.removeConstraint.call(this, tableName, constraintName, options); case 'sqlite': return SQLiteQueryInterface.removeConstraint.call(this, tableName, constraintName, options); default: const sql = this.QueryGenerator.removeConstraintQuery(tableName, constraintName); return this.sequelize.query(sql, options); } } insert(instance, tableName, values, options) { options = Utils.cloneDeep(options); options.hasTrigger = instance && instance.constructor.options.hasTrigger; const sql = this.QueryGenerator.insertQuery(tableName, values, instance && instance.constructor.rawAttributes, options); options.type = QueryTypes.INSERT; options.instance = instance; return this.sequelize.query(sql, options).then(results => { if (instance) results[0].isNewRecord = false; return results; }); } /** * Upsert * * @param {String} tableName * @param {Object} insertValues values to be inserted, mapped to field name * @param {Object} updateValues values to be updated, mapped to field name * @param {Object} where various conditions * @param {Model} model * @param {Object} options * * @returns {Promise} */ upsert(tableName, insertValues, updateValues, where, model, options) { const wheres = []; const attributes = Object.keys(insertValues); let indexes = []; let indexFields; options = _.clone(options); if (!Utils.isWhereEmpty(where)) { wheres.push(where); } // Lets combine uniquekeys and indexes into one indexes = _.map(model.options.uniqueKeys, value => { return value.fields; }); _.each(model.options.indexes, value => { if (value.unique) { // fields in the index may both the strings or objects with an attribute property - lets sanitize that indexFields = _.map(value.fields, field => { if (_.isPlainObject(field)) { return field.attribute; } return field; }); indexes.push(indexFields); } }); for (const index of indexes) { if (_.intersection(attributes, index).length === index.length) { where = {}; for (const field of index) { where[field] = insertValues[field]; } wheres.push(where); } } where = { [Op.or]: wheres }; options.type = QueryTypes.UPSERT; options.raw = true; const sql = this.QueryGenerator.upsertQuery(tableName, insertValues, updateValues, where, model, options); return this.sequelize.query(sql, options).then(result => { switch (this.sequelize.options.dialect) { case 'postgres': return [result.created, result.primary_key]; case 'mssql': return [ result.$action === 'INSERT', result[model.primaryKeyField] ]; // MySQL returns 1 for inserted, 2 for updated // http://dev.mysql.com/doc/refman/5.0/en/insert-on-duplicate.html. case 'mysql': return [result === 1, undefined]; default: return [result, undefined]; } }); } /** * Insert records into a table * * ```js * queryInterface.bulkInsert('roles', [{ * label: 'user', * createdAt: new Date(), * updatedAt: new Date() * }, { * label: 'admin', * createdAt: new Date(), * updatedAt: new Date() * }]); * ``` * * @param {String} tableName Table name to insert record to * @param {Array} records List of records to insert * @param {Object} options Various options, please see Model.bulkCreate options * @param {Object} fieldMappedAttributes Various attributes mapped by field name * * @return {Promise} */ bulkInsert(tableName, records, options, attributes) { options = _.clone(options) || {}; options.type = QueryTypes.INSERT; return this.sequelize.query( this.QueryGenerator.bulkInsertQuery(tableName, records, options, attributes), options ).then(results => results[0]); } update(instance, tableName, values, identifier, options) { options = _.clone(options || {}); options.hasTrigger = !!(instance && instance._modelOptions && instance._modelOptions.hasTrigger); const sql = this.QueryGenerator.updateQuery(tableName, values, identifier, options, instance.constructor.rawAttributes); options.type = QueryTypes.UPDATE; options.instance = instance; return this.sequelize.query(sql, options); } bulkUpdate(tableName, values, identifier, options, attributes) { options = Utils.cloneDeep(options); if (typeof identifier === 'object') identifier = Utils.cloneDeep(identifier); const sql = this.QueryGenerator.updateQuery(tableName, values, identifier, options, attributes); const table = _.isObject(tableName) ? tableName : { tableName }; const model = _.find(this.sequelize.modelManager.models, { tableName: table.tableName }); options.model = model; return this.sequelize.query(sql, options); } delete(instance, tableName, identifier, options) { const cascades = []; const sql = this.QueryGenerator.deleteQuery(tableName, identifier, null, instance.constructor); options = _.clone(options) || {}; // Check for a restrict field if (!!instance.constructor && !!instance.constructor.associations) { const keys = Object.keys(instance.constructor.associations); const length = keys.length; let association; for (let i = 0; i < length; i++) { association = instance.constructor.associations[keys[i]]; if (association.options && association.options.onDelete && association.options.onDelete.toLowerCase() === 'cascade' && association.options.useHooks === true) { cascades.push(association.accessors.get); } } } return Promise.each(cascades, cascade => { return instance[cascade](options).then(instances => { // Check for hasOne relationship with non-existing associate ("has zero") if (!instances) { return Promise.resolve(); } if (!Array.isArray(instances)) instances = [instances]; return Promise.each(instances, instance => instance.destroy(options)); }); }).then(() => { options.instance = instance; return this.sequelize.query(sql, options); }); } /** * Delete records from a table * * @param {String} tableName Table name from where to delete records * @param {Object} identifier Where conditions to find records to delete * * @return {Promise} */ bulkDelete(tableName, identifier, options, model) { options = Utils.cloneDeep(options); options = _.defaults(options, {limit: null}); if (typeof identifier === 'object') identifier = Utils.cloneDeep(identifier); const sql = this.QueryGenerator.deleteQuery(tableName, identifier, options, model); return this.sequelize.query(sql, options); } select(model, tableName, options) { options = Utils.cloneDeep(options); options.type = QueryTypes.SELECT; options.model = model; return this.sequelize.query( this.QueryGenerator.selectQuery(tableName, options, model), options ); } increment(model, tableName, values, identifier, options) { options = Utils.cloneDeep(options); const sql = this.QueryGenerator.arithmeticQuery('+', tableName, values, identifier, options, options.attributes); options.type = QueryTypes.UPDATE; options.model = model; return this.sequelize.query(sql, options); } decrement(model, tableName, values, identifier, options) { options = Utils.cloneDeep(options); const sql = this.QueryGenerator.arithmeticQuery('-', tableName, values, identifier, options, options.attributes); options.type = QueryTypes.UPDATE; options.model = model; return this.sequelize.query(sql, options); } rawSelect(tableName, options, attributeSelector, Model) { if (options.schema) { tableName = this.QueryGenerator.addSchema({ tableName, _schema: options.schema }); } options = Utils.cloneDeep(options); options = _.defaults(options, { raw: true, plain: true, type: QueryTypes.SELECT }); const sql = this.QueryGenerator.selectQuery(tableName, options, Model); if (attributeSelector === undefined) { throw new Error('Please pass an attribute selector!'); } return this.sequelize.query(sql, options).then(data => { if (!options.plain) { return data; } let result = data ? data[attributeSelector] : null; if (options && options.dataType) { const dataType = options.dataType; if (dataType instanceof DataTypes.DECIMAL || dataType instanceof DataTypes.FLOAT) { result = parseFloat(result); } else if (dataType instanceof DataTypes.INTEGER || dataType instanceof DataTypes.BIGINT) { result = parseInt(result, 10); } else if (dataType instanceof DataTypes.DATE) { if (!_.isNull(result) && !_.isDate(result)) { result = new Date(result); } } else if (dataType instanceof DataTypes.STRING) { // Nothing to do, result is already a string. } } return result; }); } createTrigger(tableName, triggerName, timingType, fireOnArray, functionName, functionParams, optionsArray, options) { const sql = this.QueryGenerator.createTrigger(tableName, triggerName, timingType, fireOnArray, functionName, functionParams, optionsArray); options = options || {}; if (sql) { return this.sequelize.query(sql, options); } else { return Promise.resolve(); } } dropTrigger(tableName, triggerName, options) { const sql = this.QueryGenerator.dropTrigger(tableName, triggerName); options = options || {}; if (sql) { return this.sequelize.query(sql, options); } else { return Promise.resolve(); } } renameTrigger(tableName, oldTriggerName, newTriggerName, options) { const sql = this.QueryGenerator.renameTrigger(tableName, oldTriggerName, newTriggerName); options = options || {}; if (sql) { return this.sequelize.query(sql, options); } else { return Promise.resolve(); } } /** * Create SQL function * * ```js * queryInterface.createFunction( * 'someFunction', * [ * {type: 'integer', name: 'param', direction: 'IN'} * ], * 'integer', * 'plpgsql', * 'RETURN param + 1;', * [ * 'IMMUTABLE', * 'LEAKPROOF' * ] * ); * ``` * * @param {String} functionName Name of SQL function to create * @param {Array} params List of parameters declared for SQL function * @param {String} returnType SQL type of function returned value * @param {String} language The name of the language that the function is implemented in * @param {String} body Source code of function * @param {Array} optionsArray Extra-options for creation * @param {Object} [options] * * @return {Promise} */ createFunction(functionName, params, returnType, language, body, optionsArray, options) { const sql = this.QueryGenerator.createFunction(functionName, params, returnType, language, body, optionsArray); options = options || {}; if (sql) { return this.sequelize.query(sql, options); } else { return Promise.resolve(); } } /** * Drop SQL function * * ```js * queryInterface.dropFunction( * 'someFunction', * [ * {type: 'varchar', name: 'param1', direction: 'IN'}, * {type: 'integer', name: 'param2', direction: 'INOUT'} * ] * ); * ``` * * @param {String} functionName Name of SQL function to drop * @param {Array} params List of parameters declared for SQL function * @param {Object} [options] * * @return {Promise} */ dropFunction(functionName, params, options) { const sql = this.QueryGenerator.dropFunction(functionName, params); options = options || {}; if (sql) { return this.sequelize.query(sql, options); } else { return Promise.resolve(); } } /** * Rename SQL function * * ```js * queryInterface.renameFunction( * 'fooFunction', * [ * {type: 'varchar', name: 'param1', direction: 'IN'}, * {type: 'integer', name: 'param2', direction: 'INOUT'} * ], * 'barFunction' * ); * ``` * * @param {String} oldFunctionName * @param {Array} params List of parameters declared for SQL function * @param {String} newFunctionName * @param {Object} [options] * * @return {Promise} */ renameFunction(oldFunctionName, params, newFunctionName, options) { const sql = this.QueryGenerator.renameFunction(oldFunctionName, params, newFunctionName); options = options || {}; if (sql) { return this.sequelize.query(sql, options); } else { return Promise.resolve(); } } // Helper methods useful for querying /** * Escape an identifier (e.g. a table or attribute name). If force is true, * the identifier will be quoted even if the `quoteIdentifiers` option is * false. * @private */ quoteIdentifier(identifier, force) { return this.QueryGenerator.quoteIdentifier(identifier, force); } quoteTable(identifier) { return this.QueryGenerator.quoteTable(identifier); } /** * Split an identifier into .-separated tokens and quote each part. * If force is true, the identifier will be quoted even if the * `quoteIdentifiers` option is false. * @private */ quoteIdentifiers(identifiers, force) { return this.QueryGenerator.quoteIdentifiers(identifiers, force); } /** * Escape a value (e.g. a string, number or date) * @private */ escape(value) { return this.QueryGenerator.escape(value); } setAutocommit(transaction, value, options) { if (!transaction || !(transaction instanceof Transaction)) { throw new Error('Unable to set autocommit for a transaction without transaction object!'); } if (transaction.parent) { // Not possible to set a separate isolation level for savepoints return Promise.resolve(); } options = _.assign({}, options, { transaction: transaction.parent || transaction }); const sql = this.QueryGenerator.setAutocommitQuery(value, { parent: transaction.parent }); if (!sql) return Promise.resolve(); return this.sequelize.query(sql, options); } setIsolationLevel(transaction, value, options) { if (!transaction || !(transaction instanceof Transaction)) { throw new Error('Unable to set isolation level for a transaction without transaction object!'); } if (transaction.parent || !value) { // Not possible to set a separate isolation level for savepoints return Promise.resolve(); } options = _.assign({}, options, { transaction: transaction.parent || transaction }); const sql = this.QueryGenerator.setIsolationLevelQuery(value, { parent: transaction.parent }); if (!sql) return Promise.resolve(); return this.sequelize.query(sql, options); } startTransaction(transaction, options) { if (!transaction || !(transaction instanceof Transaction)) { throw new Error('Unable to start a transaction without transaction object!'); } options = _.assign({}, options, { transaction: transaction.parent || transaction }); options.transaction.name = transaction.parent ? transaction.name : undefined; const sql = this.QueryGenerator.startTransactionQuery(transaction); return this.sequelize.query(sql, options); } deferConstraints(transaction, options) { options = _.assign({}, options, { transaction: transaction.parent || transaction }); const sql = this.QueryGenerator.deferConstraintsQuery(options); if (sql) { return this.sequelize.query(sql, options); } return Promise.resolve(); } commitTransaction(transaction, options) { if (!transaction || !(transaction instanceof Transaction)) { throw new Error('Unable to commit a transaction without transaction object!'); } if (transaction.parent) { // Savepoints cannot be committed return Promise.resolve(); } options = _.assign({}, options, { transaction: transaction.parent || transaction, supportsSearchPath: false }); const sql = this.QueryGenerator.commitTransactionQuery(transaction); const promise = this.sequelize.query(sql, options); transaction.finished = 'commit'; return promise; } rollbackTransaction(transaction, options) { if (!transaction || !(transaction instanceof Transaction)) { throw new Error('Unable to rollback a transaction without transaction object!'); } options = _.assign({}, options, { transaction: transaction.parent || transaction, supportsSearchPath: false }); options.transaction.name = transaction.parent ? transaction.name : undefined; const sql = this.QueryGenerator.rollbackTransactionQuery(transaction); const promise = this.sequelize.query(sql, options); transaction.finished = 'rollback'; return promise; } } module.exports = QueryInterface; module.exports.QueryInterface = QueryInterface; module.exports.default = QueryInterface;