1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258 |
- 'use strict';
- const _ = require('lodash');
- const Utils = require('../../utils');
- const DataTypes = require('../../data-types');
- const Transaction = require('../../transaction');
- const QueryTypes = require('../../query-types');
- /**
- * The interface that Sequelize uses to talk to all databases
- */
- class QueryInterface {
- constructor(sequelize, queryGenerator) {
- this.sequelize = sequelize;
- this.queryGenerator = queryGenerator;
- }
- /**
- * Create a database
- *
- * @param {string} database Database name to create
- * @param {object} [options] Query options
- * @param {string} [options.charset] Database default character set, MYSQL only
- * @param {string} [options.collate] Database default collation
- * @param {string} [options.encoding] Database default character set, PostgreSQL only
- * @param {string} [options.ctype] Database character classification, PostgreSQL only
- * @param {string} [options.template] The name of the template from which to create the new database, PostgreSQL only
- *
- * @returns {Promise}
- */
- async createDatabase(database, options) {
- options = options || {};
- const sql = this.queryGenerator.createDatabaseQuery(database, options);
- return await this.sequelize.query(sql, options);
- }
- /**
- * Drop a database
- *
- * @param {string} database Database name to drop
- * @param {object} [options] Query options
- *
- * @returns {Promise}
- */
- async dropDatabase(database, options) {
- options = options || {};
- const sql = this.queryGenerator.dropDatabaseQuery(database);
- return await this.sequelize.query(sql, options);
- }
- /**
- * Create a schema
- *
- * @param {string} schema Schema name to create
- * @param {object} [options] Query options
- *
- * @returns {Promise}
- */
- async createSchema(schema, options) {
- options = options || {};
- const sql = this.queryGenerator.createSchema(schema);
- return await this.sequelize.query(sql, options);
- }
- /**
- * Drop a schema
- *
- * @param {string} schema Schema name to drop
- * @param {object} [options] Query options
- *
- * @returns {Promise}
- */
- async dropSchema(schema, options) {
- options = options || {};
- const sql = this.queryGenerator.dropSchema(schema);
- return await this.sequelize.query(sql, options);
- }
- /**
- * Drop all schemas
- *
- * @param {object} [options] Query options
- *
- * @returns {Promise}
- */
- async dropAllSchemas(options) {
- options = options || {};
- if (!this.queryGenerator._dialect.supports.schemas) {
- return this.sequelize.drop(options);
- }
- const schemas = await this.showAllSchemas(options);
- return Promise.all(schemas.map(schemaName => this.dropSchema(schemaName, options)));
- }
- /**
- * Show all schemas
- *
- * @param {object} [options] Query options
- *
- * @returns {Promise<Array>}
- */
- async showAllSchemas(options) {
- options = {
- ...options,
- raw: true,
- type: this.sequelize.QueryTypes.SELECT
- };
- const showSchemasSql = this.queryGenerator.showSchemasQuery(options);
- const schemaNames = await this.sequelize.query(showSchemasSql, options);
- return _.flatten(schemaNames.map(value => value.schema_name ? value.schema_name : value));
- }
- /**
- * Return database version
- *
- * @param {object} [options] Query options
- * @param {QueryType} [options.type] Query type
- *
- * @returns {Promise}
- * @private
- */
- async databaseVersion(options) {
- return await this.sequelize.query(
- this.queryGenerator.versionQuery(),
- { ...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.
- * comment: 'my table', // comment for table
- * collate: 'latin1_danish_ci' // collation, MYSQL only
- * }
- * )
- * ```
- *
- * @param {string} tableName Name of table to create
- * @param {object} attributes Object representing a list of table attributes to create
- * @param {object} [options] create table and query options
- * @param {Model} [model] model class
- *
- * @returns {Promise}
- */
- async createTable(tableName, attributes, options, model) {
- let sql = '';
- options = { ...options };
- if (options && options.uniqueKeys) {
- _.forOwn(options.uniqueKeys, uniqueKey => {
- if (uniqueKey.customIndex === undefined) {
- uniqueKey.customIndex = true;
- }
- });
- }
- if (model) {
- options.uniqueKeys = options.uniqueKeys || model.uniqueKeys;
- }
- attributes = _.mapValues(
- attributes,
- attribute => this.sequelize.normalizeAttribute(attribute)
- );
- // Postgres requires special SQL commands for ENUM/ENUM[]
- await this.ensureEnums(tableName, attributes, options, model);
- if (
- !tableName.schema &&
- (options.schema || !!model && model._schema)
- ) {
- tableName = this.queryGenerator.addSchema({
- tableName,
- _schema: !!model && model._schema || options.schema
- });
- }
- attributes = this.queryGenerator.attributesToSQL(attributes, { table: tableName, context: 'createTable' });
- sql = this.queryGenerator.createTableQuery(tableName, attributes, options);
- return await this.sequelize.query(sql, options);
- }
- /**
- * Drop a table from database
- *
- * @param {string} tableName Table name to drop
- * @param {object} options Query options
- *
- * @returns {Promise}
- */
- async dropTable(tableName, options) {
- // if we're forcing we should be cascading unless explicitly stated otherwise
- options = { ...options };
- options.cascade = options.cascade || options.force || false;
- const sql = this.queryGenerator.dropTableQuery(tableName, options);
- await this.sequelize.query(sql, options);
- }
- async _dropAllTables(tableNames, skip, options) {
- for (const tableName of tableNames) {
- // if tableName is not in the Array of tables names then don't drop it
- if (!skip.includes(tableName.tableName || tableName)) {
- await this.dropTable(tableName, { ...options, cascade: true } );
- }
- }
- }
- /**
- * Drop all tables from database
- *
- * @param {object} [options] query options
- * @param {Array} [options.skip] List of table to skip
- *
- * @returns {Promise}
- */
- async dropAllTables(options) {
- options = options || {};
- const skip = options.skip || [];
- const tableNames = await this.showAllTables(options);
- const foreignKeys = await this.getForeignKeysForTables(tableNames, options);
- for (const tableName of tableNames) {
- let normalizedTableName = tableName;
- if (_.isObject(tableName)) {
- normalizedTableName = `${tableName.schema}.${tableName.tableName}`;
- }
- for (const foreignKey of foreignKeys[normalizedTableName]) {
- await this.sequelize.query(this.queryGenerator.dropForeignKeyQuery(tableName, foreignKey));
- }
- }
- await this._dropAllTables(tableNames, skip, options);
- }
- /**
- * Rename a table
- *
- * @param {string} before Current name of table
- * @param {string} after New name from table
- * @param {object} [options] Query options
- *
- * @returns {Promise}
- */
- async renameTable(before, after, options) {
- options = options || {};
- const sql = this.queryGenerator.renameTableQuery(before, after);
- return await 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] query type
- *
- * @returns {Promise<Array>}
- * @private
- */
- async showAllTables(options) {
- options = {
- ...options,
- raw: true,
- type: QueryTypes.SHOWTABLES
- };
- const showTablesSql = this.queryGenerator.showTablesQuery(this.sequelize.config.database);
- const tableNames = await this.sequelize.query(showTablesSql, options);
- return _.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 table name
- * @param {object} [options] Query options
- *
- * @returns {Promise<object>}
- */
- 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 };
- 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.`);
- }
- 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;
- }
- }
- /**
- * Add a new column to a table
- *
- * ```js
- * queryInterface.addColumn('tableA', 'columnC', Sequelize.STRING, {
- * after: 'columnB' // after option is only supported by MySQL
- * });
- * ```
- *
- * @param {string} table Table to add column to
- * @param {string} key Column name
- * @param {object} attribute Attribute definition
- * @param {object} [options] Query options
- *
- * @returns {Promise}
- */
- async addColumn(table, key, attribute, options) {
- if (!table || !key || !attribute) {
- throw new Error('addColumn takes at least 3 arguments (table, attribute name, attribute definition)');
- }
- options = options || {};
- attribute = this.sequelize.normalizeAttribute(attribute);
- return await this.sequelize.query(this.queryGenerator.addColumnQuery(table, key, attribute), options);
- }
- /**
- * Remove a column from a table
- *
- * @param {string} tableName Table to remove column from
- * @param {string} attributeName Column name to remove
- * @param {object} [options] Query options
- */
- async removeColumn(tableName, attributeName, options) {
- return this.sequelize.query(this.queryGenerator.removeColumnQuery(tableName, attributeName), options);
- }
- normalizeAttribute(dataTypeOrOptions) {
- let attribute;
- if (Object.values(DataTypes).includes(dataTypeOrOptions)) {
- attribute = { type: dataTypeOrOptions, allowNull: true };
- } else {
- attribute = dataTypeOrOptions;
- }
- return this.sequelize.normalizeAttribute(attribute);
- }
- /**
- * 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
- */
- async changeColumn(tableName, attributeName, dataTypeOrOptions, options) {
- options = options || {};
- const query = this.queryGenerator.attributesToSQL({
- [attributeName]: this.normalizeAttribute(dataTypeOrOptions)
- }, {
- context: 'changeColumn',
- table: tableName
- });
- const sql = this.queryGenerator.changeColumnQuery(tableName, query);
- return this.sequelize.query(sql, options);
- }
- /**
- * Rejects if the table doesn't have the specified column, otherwise returns the column description.
- *
- * @param {string} tableName
- * @param {string} columnName
- * @param {object} options
- * @private
- */
- async assertTableHasColumn(tableName, columnName, options) {
- const description = await this.describeTable(tableName, options);
- if (description[columnName]) {
- return description;
- }
- throw new Error(`Table ${tableName} doesn't have the column ${columnName}`);
- }
- /**
- * 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
- *
- * @returns {Promise}
- */
- async renameColumn(tableName, attrNameBefore, attrNameAfter, options) {
- options = options || {};
- const data = (await this.assertTableHasColumn(tableName, attrNameBefore, options))[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;
- }
- const sql = this.queryGenerator.renameColumnQuery(
- tableName,
- attrNameBefore,
- this.queryGenerator.attributesToSQL(_options)
- );
- return await this.sequelize.query(sql, options);
- }
- /**
- * Add an index to a column
- *
- * @param {string|object} tableName Table name to add index on, can be a object with schema
- * @param {Array} [attributes] Use options.fields instead, List of attributes to add index on
- * @param {object} options indexes options
- * @param {Array} options.fields List of attributes to add index on
- * @param {boolean} [options.concurrently] Pass CONCURRENT so other operations run while the index is created
- * @param {boolean} [options.unique] Create a unique index
- * @param {string} [options.using] Useful for GIN indexes
- * @param {string} [options.operator] Index operator
- * @param {string} [options.type] Type of index, available options are UNIQUE|FULLTEXT|SPATIAL
- * @param {string} [options.name] Name of the index. Default is <table>_<attr1>_<attr2>
- * @param {object} [options.where] Where condition on index, for partial indexes
- * @param {string} [rawTablename] table name, this is just for backward compatibiity
- *
- * @returns {Promise}
- */
- async 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;
- }
- if (!rawTablename) {
- // Map for backwards compat
- rawTablename = tableName;
- }
- options = Utils.cloneDeep(options);
- options.fields = attributes;
- const sql = this.queryGenerator.addIndexQuery(tableName, options, rawTablename);
- return await this.sequelize.query(sql, { ...options, supportsSearchPath: false });
- }
- /**
- * Show indexes on a table
- *
- * @param {string} tableName table name
- * @param {object} [options] Query options
- *
- * @returns {Promise<Array>}
- * @private
- */
- async showIndex(tableName, options) {
- const sql = this.queryGenerator.showIndexesQuery(tableName, options);
- return await this.sequelize.query(sql, { ...options, type: QueryTypes.SHOWINDEXES });
- }
- /**
- * Returns all foreign key constraints of requested tables
- *
- * @param {string[]} tableNames table names
- * @param {object} [options] Query options
- *
- * @returns {Promise}
- */
- async getForeignKeysForTables(tableNames, options) {
- if (tableNames.length === 0) {
- return {};
- }
- options = { ...options, type: QueryTypes.FOREIGNKEYS };
- const results = await Promise.all(tableNames.map(tableName =>
- this.sequelize.query(this.queryGenerator.getForeignKeysQuery(tableName, this.sequelize.config.database), options)));
- const result = {};
- tableNames.forEach((tableName, i) => {
- if (_.isObject(tableName)) {
- tableName = `${tableName.schema}.${tableName.tableName}`;
- }
- result[tableName] = Array.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 table name
- * @param {object} [options] Query options
- */
- async getForeignKeyReferencesForTable(tableName, options) {
- const queryOptions = {
- ...options,
- type: QueryTypes.FOREIGNKEYS
- };
- const query = this.queryGenerator.getForeignKeysQuery(tableName, this.sequelize.config.database);
- 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|string[]} indexNameOrAttributes Index name or list of attributes that in the index
- * @param {object} [options] Query options
- *
- * @returns {Promise}
- */
- async removeIndex(tableName, indexNameOrAttributes, options) {
- options = options || {};
- const sql = this.queryGenerator.removeIndexQuery(tableName, indexNameOrAttributes);
- return await this.sequelize.query(sql, options);
- }
- /**
- * Add a constraint to a table
- *
- * Available constraints:
- * - UNIQUE
- * - DEFAULT (MSSQL only)
- * - CHECK (MySQL - Ignored by the database engine )
- * - FOREIGN KEY
- * - PRIMARY KEY
- *
- * @example <caption>UNIQUE</caption>
- * queryInterface.addConstraint('Users', {
- * fields: ['email'],
- * type: 'unique',
- * name: 'custom_unique_constraint_name'
- * });
- *
- * @example <caption>CHECK</caption>
- * queryInterface.addConstraint('Users', {
- * fields: ['roles'],
- * type: 'check',
- * where: {
- * roles: ['user', 'admin', 'moderator', 'guest']
- * }
- * });
- *
- * @example <caption>Default - MSSQL only</caption>
- * queryInterface.addConstraint('Users', {
- * fields: ['roles'],
- * type: 'default',
- * defaultValue: 'guest'
- * });
- *
- * @example <caption>Primary Key</caption>
- * queryInterface.addConstraint('Users', {
- * fields: ['username'],
- * type: 'primary key',
- * name: 'custom_primary_constraint_name'
- * });
- *
- * @example <caption>Foreign Key</caption>
- * queryInterface.addConstraint('Posts', {
- * fields: ['username'],
- * type: 'foreign key',
- * name: 'custom_fkey_constraint_name',
- * references: { //Required field
- * table: 'target_table_name',
- * field: 'target_column_name'
- * },
- * onDelete: 'cascade',
- * onUpdate: 'cascade'
- * });
- *
- * @example <caption>Composite Foreign Key</caption>
- * queryInterface.addConstraint('TableName', {
- * fields: ['source_column_name', 'other_source_column_name'],
- * type: 'foreign key',
- * name: 'custom_fkey_constraint_name',
- * references: { //Required field
- * table: 'target_table_name',
- * fields: ['target_column_name', 'other_target_column_name']
- * },
- * onDelete: 'cascade',
- * onUpdate: 'cascade'
- * });
- *
- * @param {string} tableName Table name where you want to add a constraint
- * @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 {Array} options.fields Array of column names to apply the constraint over
- * @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
- * @param {string} [options.references.fields] Target column names for a composite primary key. Must match the order of fields in options.fields.
- * @param {string} [options.deferrable] Sets the constraint to be deferred or immediately checked. See Sequelize.Deferrable. PostgreSQL Only
- *
- * @returns {Promise}
- */
- 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 = Utils.cloneDeep(options);
- const sql = this.queryGenerator.addConstraintQuery(tableName, options);
- return await this.sequelize.query(sql, options);
- }
- async showConstraint(tableName, constraintName, options) {
- const sql = this.queryGenerator.showConstraintsQuery(tableName, constraintName);
- return await this.sequelize.query(sql, { ...options, type: QueryTypes.SHOWCONSTRAINTS });
- }
- /**
- * Remove a constraint from a table
- *
- * @param {string} tableName Table name to drop constraint from
- * @param {string} constraintName Constraint name
- * @param {object} options Query options
- */
- async removeConstraint(tableName, constraintName, options) {
- return this.sequelize.query(this.queryGenerator.removeConstraintQuery(tableName, constraintName), options);
- }
- async 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;
- const results = await this.sequelize.query(sql, options);
- if (instance) results[0].isNewRecord = false;
- return results;
- }
- /**
- * Upsert
- *
- * @param {string} tableName table to upsert on
- * @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 where conditions, which can be used for UPDATE part when INSERT fails
- * @param {object} options query options
- *
- * @returns {Promise<boolean,?number>} Resolves an array with <created, primaryKey>
- */
- async upsert(tableName, insertValues, updateValues, where, options) {
- options = { ...options };
- const model = options.model;
- const primaryKeys = Object.values(model.primaryKeys).map(item => item.field);
- const uniqueKeys = Object.values(model.uniqueKeys).filter(c => c.fields.length >= 1).map(c => c.fields);
- const indexKeys = Object.values(model._indexes).filter(c => c.unique && c.fields.length >= 1).map(c => c.fields);
- options.type = QueryTypes.UPSERT;
- options.updateOnDuplicate = Object.keys(updateValues);
- options.upsertKeys = [];
- // For fields in updateValues, try to find a constraint or unique index
- // that includes given field. Only first matching upsert key is used.
- for (const field of options.updateOnDuplicate) {
- const uniqueKey = uniqueKeys.find(fields => fields.includes(field));
- if (uniqueKey) {
- options.upsertKeys = uniqueKey;
- break;
- }
- const indexKey = indexKeys.find(fields => fields.includes(field));
- if (indexKey) {
- options.upsertKeys = indexKey;
- break;
- }
- }
- // Always use PK, if no constraint available OR update data contains PK
- if (
- options.upsertKeys.length === 0
- || _.intersection(options.updateOnDuplicate, primaryKeys).length
- ) {
- options.upsertKeys = primaryKeys;
- }
- options.upsertKeys = _.uniq(options.upsertKeys);
- const sql = this.queryGenerator.insertQuery(tableName, insertValues, model.rawAttributes, options);
- return await this.sequelize.query(sql, options);
- }
- /**
- * Insert multiple records into a table
- *
- * @example
- * 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} attributes Various attributes mapped by field name
- *
- * @returns {Promise}
- */
- async bulkInsert(tableName, records, options, attributes) {
- options = { ...options };
- options.type = QueryTypes.INSERT;
- const results = await this.sequelize.query(
- this.queryGenerator.bulkInsertQuery(tableName, records, options, attributes),
- options
- );
- return results[0];
- }
- async update(instance, tableName, values, identifier, options) {
- options = { ...options };
- options.hasTrigger = instance && instance.constructor.options.hasTrigger;
- const sql = this.queryGenerator.updateQuery(tableName, values, identifier, options, instance.constructor.rawAttributes);
- options.type = QueryTypes.UPDATE;
- options.instance = instance;
- return await this.sequelize.query(sql, options);
- }
- /**
- * Update multiple records of a table
- *
- * @example
- * queryInterface.bulkUpdate('roles', {
- * label: 'admin',
- * }, {
- * userType: 3,
- * },
- * );
- *
- * @param {string} tableName Table name to update
- * @param {object} values Values to be inserted, mapped to field name
- * @param {object} identifier A hash with conditions OR an ID as integer OR a string with conditions
- * @param {object} [options] Various options, please see Model.bulkCreate options
- * @param {object} [attributes] Attributes on return objects if supported by SQL dialect
- *
- * @returns {Promise}
- */
- async 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.type = QueryTypes.BULKUPDATE;
- options.model = model;
- return await this.sequelize.query(sql, options);
- }
- async delete(instance, tableName, identifier, options) {
- const cascades = [];
- const sql = this.queryGenerator.deleteQuery(tableName, identifier, {}, instance.constructor);
- options = { ...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);
- }
- }
- }
- for (const cascade of cascades) {
- let instances = await instance[cascade](options);
- // Check for hasOne relationship with non-existing associate ("has zero")
- if (!instances) continue;
- if (!Array.isArray(instances)) instances = [instances];
- for (const _instance of instances) await _instance.destroy(options);
- }
- options.instance = instance;
- return await this.sequelize.query(sql, options);
- }
- /**
- * Delete multiple records from a table
- *
- * @param {string} tableName table name from where to delete records
- * @param {object} where where conditions to find records to delete
- * @param {object} [options] options
- * @param {boolean} [options.truncate] Use truncate table command
- * @param {boolean} [options.cascade=false] Only used in conjunction with TRUNCATE. Truncates all tables that have foreign-key references to the named table, or to any tables added to the group due to CASCADE.
- * @param {boolean} [options.restartIdentity=false] Only used in conjunction with TRUNCATE. Automatically restart sequences owned by columns of the truncated table.
- * @param {Model} [model] Model
- *
- * @returns {Promise}
- */
- async bulkDelete(tableName, where, options, model) {
- options = Utils.cloneDeep(options);
- options = _.defaults(options, { limit: null });
- if (options.truncate === true) {
- return this.sequelize.query(
- this.queryGenerator.truncateTableQuery(tableName, options),
- options
- );
- }
- if (typeof identifier === 'object') where = Utils.cloneDeep(where);
- return await this.sequelize.query(
- this.queryGenerator.deleteQuery(tableName, where, options, model),
- options
- );
- }
- async select(model, tableName, optionsArg) {
- const options = { ...optionsArg, type: QueryTypes.SELECT, model };
- return await this.sequelize.query(
- this.queryGenerator.selectQuery(tableName, options, model),
- options
- );
- }
- async increment(model, tableName, where, incrementAmountsByField, extraAttributesToBeUpdated, options) {
- options = Utils.cloneDeep(options);
- const sql = this.queryGenerator.arithmeticQuery('+', tableName, where, incrementAmountsByField, extraAttributesToBeUpdated, options);
- options.type = QueryTypes.UPDATE;
- options.model = model;
- return await this.sequelize.query(sql, options);
- }
- async decrement(model, tableName, where, incrementAmountsByField, extraAttributesToBeUpdated, options) {
- options = Utils.cloneDeep(options);
- const sql = this.queryGenerator.arithmeticQuery('-', tableName, where, incrementAmountsByField, extraAttributesToBeUpdated, options);
- options.type = QueryTypes.UPDATE;
- options.model = model;
- return await this.sequelize.query(sql, options);
- }
- async rawSelect(tableName, options, attributeSelector, Model) {
- 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!');
- }
- const data = await this.sequelize.query(sql, options);
- if (!options.plain) {
- return data;
- }
- const result = data ? data[attributeSelector] : null;
- if (!options || !options.dataType) {
- return result;
- }
- const dataType = options.dataType;
- if (dataType instanceof DataTypes.DECIMAL || dataType instanceof DataTypes.FLOAT) {
- if (result !== null) {
- return parseFloat(result);
- }
- }
- if (dataType instanceof DataTypes.INTEGER || dataType instanceof DataTypes.BIGINT) {
- return parseInt(result, 10);
- }
- if (dataType instanceof DataTypes.DATE) {
- if (result !== null && !(result instanceof Date)) {
- return new Date(result);
- }
- }
- return result;
- }
- async 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 await this.sequelize.query(sql, options);
- }
- }
- async dropTrigger(tableName, triggerName, options) {
- const sql = this.queryGenerator.dropTrigger(tableName, triggerName);
- options = options || {};
- if (sql) {
- return await this.sequelize.query(sql, options);
- }
- }
- async renameTrigger(tableName, oldTriggerName, newTriggerName, options) {
- const sql = this.queryGenerator.renameTrigger(tableName, oldTriggerName, newTriggerName);
- options = options || {};
- if (sql) {
- return await this.sequelize.query(sql, options);
- }
- }
- /**
- * Create an SQL function
- *
- * @example
- * queryInterface.createFunction(
- * 'someFunction',
- * [
- * {type: 'integer', name: 'param', direction: 'IN'}
- * ],
- * 'integer',
- * 'plpgsql',
- * 'RETURN param + 1;',
- * [
- * 'IMMUTABLE',
- * 'LEAKPROOF'
- * ],
- * {
- * variables:
- * [
- * {type: 'integer', name: 'myVar', default: 100}
- * ],
- * force: true
- * };
- * );
- *
- * @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] query options
- * @param {boolean} options.force If force is true, any existing functions with the same parameters will be replaced. For postgres, this means using `CREATE OR REPLACE FUNCTION` instead of `CREATE FUNCTION`. Default is false
- * @param {Array<object>} options.variables List of declared variables. Each variable should be an object with string fields `type` and `name`, and optionally having a `default` field as well.
- *
- * @returns {Promise}
- */
- async createFunction(functionName, params, returnType, language, body, optionsArray, options) {
- const sql = this.queryGenerator.createFunction(functionName, params, returnType, language, body, optionsArray, options);
- options = options || {};
- if (sql) {
- return await this.sequelize.query(sql, options);
- }
- }
- /**
- * Drop an SQL function
- *
- * @example
- * 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] query options
- *
- * @returns {Promise}
- */
- async dropFunction(functionName, params, options) {
- const sql = this.queryGenerator.dropFunction(functionName, params);
- options = options || {};
- if (sql) {
- return await this.sequelize.query(sql, options);
- }
- }
- /**
- * Rename an SQL function
- *
- * @example
- * queryInterface.renameFunction(
- * 'fooFunction',
- * [
- * {type: 'varchar', name: 'param1', direction: 'IN'},
- * {type: 'integer', name: 'param2', direction: 'INOUT'}
- * ],
- * 'barFunction'
- * );
- *
- * @param {string} oldFunctionName Current name of function
- * @param {Array} params List of parameters declared for SQL function
- * @param {string} newFunctionName New name of function
- * @param {object} [options] query options
- *
- * @returns {Promise}
- */
- async renameFunction(oldFunctionName, params, newFunctionName, options) {
- const sql = this.queryGenerator.renameFunction(oldFunctionName, params, newFunctionName);
- options = options || {};
- if (sql) {
- return await this.sequelize.query(sql, options);
- }
- }
- // Helper methods useful for querying
- /**
- * @private
- */
- ensureEnums() {
- // noop by default
- }
- async 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;
- }
- options = { ...options, transaction: transaction.parent || transaction };
- const sql = this.queryGenerator.setIsolationLevelQuery(value, {
- parent: transaction.parent
- });
- if (!sql) return;
- return await this.sequelize.query(sql, options);
- }
- async startTransaction(transaction, options) {
- if (!transaction || !(transaction instanceof Transaction)) {
- throw new Error('Unable to start a transaction without transaction object!');
- }
- options = { ...options, transaction: transaction.parent || transaction };
- options.transaction.name = transaction.parent ? transaction.name : undefined;
- const sql = this.queryGenerator.startTransactionQuery(transaction);
- return await this.sequelize.query(sql, options);
- }
- async deferConstraints(transaction, options) {
- options = { ...options, transaction: transaction.parent || transaction };
- const sql = this.queryGenerator.deferConstraintsQuery(options);
- if (sql) {
- return await this.sequelize.query(sql, options);
- }
- }
- async 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;
- }
- options = {
- ...options,
- transaction: transaction.parent || transaction,
- supportsSearchPath: false,
- completesTransaction: true
- };
- const sql = this.queryGenerator.commitTransactionQuery(transaction);
- const promise = this.sequelize.query(sql, options);
- transaction.finished = 'commit';
- return await promise;
- }
- async rollbackTransaction(transaction, options) {
- if (!transaction || !(transaction instanceof Transaction)) {
- throw new Error('Unable to rollback a transaction without transaction object!');
- }
- options = {
- ...options,
- transaction: transaction.parent || transaction,
- supportsSearchPath: false,
- completesTransaction: true
- };
- 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 await promise;
- }
- }
- exports.QueryInterface = QueryInterface;
|