UNPKG

47.5 kBJavaScriptView Raw
1'use strict';
2
3const _ = require('lodash');
4
5const Utils = require('./utils');
6const DataTypes = require('./data-types');
7const SQLiteQueryInterface = require('./dialects/sqlite/query-interface');
8const MSSQLQueryInterface = require('./dialects/mssql/query-interface');
9const MySQLQueryInterface = require('./dialects/mysql/query-interface');
10const PostgresQueryInterface = require('./dialects/postgres/query-interface');
11const Transaction = require('./transaction');
12const Promise = require('./promise');
13const QueryTypes = require('./query-types');
14const Op = require('./operators');
15
16/**
17 * The interface that Sequelize uses to talk to all databases
18 *
19 * @class QueryInterface
20 */
21class QueryInterface {
22 constructor(sequelize) {
23 this.sequelize = sequelize;
24 this.QueryGenerator = this.sequelize.dialect.QueryGenerator;
25 }
26
27 /**
28 * Create a database
29 *
30 * @param {string} database Database name to create
31 * @param {Object} [options] Query options
32 * @param {string} [options.charset] Database default character set, MYSQL only
33 * @param {string} [options.collate] Database default collation
34 * @param {string} [options.encoding] Database default character set, PostgreSQL only
35 * @param {string} [options.ctype] Database character classification, PostgreSQL only
36 * @param {string} [options.template] The name of the template from which to create the new database, PostgreSQL only
37 *
38 * @returns {Promise}
39 */
40 createDatabase(database, options) {
41 options = options || {};
42 const sql = this.QueryGenerator.createDatabaseQuery(database, options);
43 return this.sequelize.query(sql, options);
44 }
45
46 /**
47 * Drop a database
48 *
49 * @param {string} database Database name to drop
50 * @param {Object} [options] Query options
51 *
52 * @returns {Promise}
53 */
54 dropDatabase(database, options) {
55 options = options || {};
56 const sql = this.QueryGenerator.dropDatabaseQuery(database);
57 return this.sequelize.query(sql, options);
58 }
59
60 /**
61 * Create a schema
62 *
63 * @param {string} schema Schema name to create
64 * @param {Object} [options] Query options
65 *
66 * @returns {Promise}
67 */
68 createSchema(schema, options) {
69 options = options || {};
70 const sql = this.QueryGenerator.createSchema(schema);
71 return this.sequelize.query(sql, options);
72 }
73
74 /**
75 * Drop a schema
76 *
77 * @param {string} schema Schema name to drop
78 * @param {Object} [options] Query options
79 *
80 * @returns {Promise}
81 */
82 dropSchema(schema, options) {
83 options = options || {};
84 const sql = this.QueryGenerator.dropSchema(schema);
85 return this.sequelize.query(sql, options);
86 }
87
88 /**
89 * Drop all schemas
90 *
91 * @param {Object} [options] Query options
92 *
93 * @returns {Promise}
94 */
95 dropAllSchemas(options) {
96 options = options || {};
97
98 if (!this.QueryGenerator._dialect.supports.schemas) {
99 return this.sequelize.drop(options);
100 }
101 return this.showAllSchemas(options).map(schemaName => this.dropSchema(schemaName, options));
102 }
103
104 /**
105 * Show all schemas
106 *
107 * @param {Object} [options] Query options
108 *
109 * @returns {Promise<Array>}
110 */
111 showAllSchemas(options) {
112 options = Object.assign({}, options, {
113 raw: true,
114 type: this.sequelize.QueryTypes.SELECT
115 });
116
117 const showSchemasSql = this.QueryGenerator.showSchemasQuery(options);
118
119 return this.sequelize.query(showSchemasSql, options).then(schemaNames => _.flatten(
120 schemaNames.map(value => value.schema_name ? value.schema_name : value)
121 ));
122 }
123
124 /**
125 * Return database version
126 *
127 * @param {Object} [options] Query options
128 * @param {QueryType} [options.type] Query type
129 *
130 * @returns {Promise}
131 * @private
132 */
133 databaseVersion(options) {
134 return this.sequelize.query(
135 this.QueryGenerator.versionQuery(),
136 Object.assign({}, options, { type: QueryTypes.VERSION })
137 );
138 }
139
140 /**
141 * Create a table with given set of attributes
142 *
143 * ```js
144 * queryInterface.createTable(
145 * 'nameOfTheNewTable',
146 * {
147 * id: {
148 * type: Sequelize.INTEGER,
149 * primaryKey: true,
150 * autoIncrement: true
151 * },
152 * createdAt: {
153 * type: Sequelize.DATE
154 * },
155 * updatedAt: {
156 * type: Sequelize.DATE
157 * },
158 * attr1: Sequelize.STRING,
159 * attr2: Sequelize.INTEGER,
160 * attr3: {
161 * type: Sequelize.BOOLEAN,
162 * defaultValue: false,
163 * allowNull: false
164 * },
165 * //foreign key usage
166 * attr4: {
167 * type: Sequelize.INTEGER,
168 * references: {
169 * model: 'another_table_name',
170 * key: 'id'
171 * },
172 * onUpdate: 'cascade',
173 * onDelete: 'cascade'
174 * }
175 * },
176 * {
177 * engine: 'MYISAM', // default: 'InnoDB'
178 * charset: 'latin1', // default: null
179 * schema: 'public', // default: public, PostgreSQL only.
180 * comment: 'my table', // comment for table
181 * collate: 'latin1_danish_ci' // collation, MYSQL only
182 * }
183 * )
184 * ```
185 *
186 * @param {string} tableName Name of table to create
187 * @param {Object} attributes Object representing a list of table attributes to create
188 * @param {Object} [options] create table and query options
189 * @param {Model} [model] model class
190 *
191 * @returns {Promise}
192 */
193 createTable(tableName, attributes, options, model) {
194 let sql = '';
195 let promise;
196
197 options = _.clone(options) || {};
198
199 if (options && options.uniqueKeys) {
200 _.forOwn(options.uniqueKeys, uniqueKey => {
201 if (uniqueKey.customIndex === undefined) {
202 uniqueKey.customIndex = true;
203 }
204 });
205 }
206
207 if (model) {
208 options.uniqueKeys = options.uniqueKeys || model.uniqueKeys;
209 }
210
211 attributes = _.mapValues(
212 attributes,
213 attribute => this.sequelize.normalizeAttribute(attribute)
214 );
215
216 // Postgres requires special SQL commands for ENUM/ENUM[]
217 if (this.sequelize.options.dialect === 'postgres') {
218 promise = PostgresQueryInterface.ensureEnums(this, tableName, attributes, options, model);
219 } else {
220 promise = Promise.resolve();
221 }
222
223 if (
224 !tableName.schema &&
225 (options.schema || !!model && model._schema)
226 ) {
227 tableName = this.QueryGenerator.addSchema({
228 tableName,
229 _schema: !!model && model._schema || options.schema
230 });
231 }
232
233 attributes = this.QueryGenerator.attributesToSQL(attributes, { table: tableName, context: 'createTable' });
234 sql = this.QueryGenerator.createTableQuery(tableName, attributes, options);
235
236 return promise.then(() => this.sequelize.query(sql, options));
237 }
238
239 /**
240 * Drop a table from database
241 *
242 * @param {string} tableName Table name to drop
243 * @param {Object} options Query options
244 *
245 * @returns {Promise}
246 */
247 dropTable(tableName, options) {
248 // if we're forcing we should be cascading unless explicitly stated otherwise
249 options = _.clone(options) || {};
250 options.cascade = options.cascade || options.force || false;
251
252 let sql = this.QueryGenerator.dropTableQuery(tableName, options);
253
254 return this.sequelize.query(sql, options).then(() => {
255 const promises = [];
256
257 // Since postgres has a special case for enums, we should drop the related
258 // enum type within the table and attribute
259 if (this.sequelize.options.dialect === 'postgres') {
260 const instanceTable = this.sequelize.modelManager.getModel(tableName, { attribute: 'tableName' });
261
262 if (instanceTable) {
263 const getTableName = (!options || !options.schema || options.schema === 'public' ? '' : `${options.schema}_`) + tableName;
264
265 const keys = Object.keys(instanceTable.rawAttributes);
266 const keyLen = keys.length;
267
268 for (let i = 0; i < keyLen; i++) {
269 if (instanceTable.rawAttributes[keys[i]].type instanceof DataTypes.ENUM) {
270 sql = this.QueryGenerator.pgEnumDrop(getTableName, keys[i]);
271 options.supportsSearchPath = false;
272 promises.push(this.sequelize.query(sql, Object.assign({}, options, { raw: true })));
273 }
274 }
275 }
276 }
277
278 return Promise.all(promises).get(0);
279 });
280 }
281
282 /**
283 * Drop all tables from database
284 *
285 * @param {Object} [options] query options
286 * @param {Array} [options.skip] List of table to skip
287 *
288 * @returns {Promise}
289 */
290 dropAllTables(options) {
291 options = options || {};
292 const skip = options.skip || [];
293
294 const dropAllTables = tableNames => Promise.each(tableNames, tableName => {
295 // if tableName is not in the Array of tables names then don't drop it
296 if (!skip.includes(tableName.tableName || tableName)) {
297 return this.dropTable(tableName, Object.assign({}, options, { cascade: true }) );
298 }
299 });
300
301 return this.showAllTables(options).then(tableNames => {
302 if (this.sequelize.options.dialect === 'sqlite') {
303 return this.sequelize.query('PRAGMA foreign_keys;', options).then(result => {
304 const foreignKeysAreEnabled = result.foreign_keys === 1;
305
306 if (foreignKeysAreEnabled) {
307 return this.sequelize.query('PRAGMA foreign_keys = OFF', options)
308 .then(() => dropAllTables(tableNames))
309 .then(() => this.sequelize.query('PRAGMA foreign_keys = ON', options));
310 }
311 return dropAllTables(tableNames);
312 });
313 }
314 return this.getForeignKeysForTables(tableNames, options).then(foreignKeys => {
315 const queries = [];
316
317 tableNames.forEach(tableName => {
318 let normalizedTableName = tableName;
319 if (_.isObject(tableName)) {
320 normalizedTableName = `${tableName.schema}.${tableName.tableName}`;
321 }
322
323 foreignKeys[normalizedTableName].forEach(foreignKey => {
324 queries.push(this.QueryGenerator.dropForeignKeyQuery(tableName, foreignKey));
325 });
326 });
327
328 return Promise.each(queries, q => this.sequelize.query(q, options))
329 .then(() => dropAllTables(tableNames));
330 });
331 });
332 }
333
334 /**
335 * Drop specified enum from database (Postgres only)
336 *
337 * @param {string} [enumName] Enum name to drop
338 * @param {Object} options Query options
339 *
340 * @returns {Promise}
341 * @private
342 */
343 dropEnum(enumName, options) {
344 if (this.sequelize.getDialect() !== 'postgres') {
345 return Promise.resolve();
346 }
347
348 options = options || {};
349
350 return this.sequelize.query(
351 this.QueryGenerator.pgEnumDrop(null, null, this.QueryGenerator.pgEscapeAndQuote(enumName)),
352 Object.assign({}, options, { raw: true })
353 );
354 }
355
356 /**
357 * Drop all enums from database (Postgres only)
358 *
359 * @param {Object} options Query options
360 *
361 * @returns {Promise}
362 * @private
363 */
364 dropAllEnums(options) {
365 if (this.sequelize.getDialect() !== 'postgres') {
366 return Promise.resolve();
367 }
368
369 options = options || {};
370
371 return this.pgListEnums(null, options).map(result => this.sequelize.query(
372 this.QueryGenerator.pgEnumDrop(null, null, this.QueryGenerator.pgEscapeAndQuote(result.enum_name)),
373 Object.assign({}, options, { raw: true })
374 ));
375 }
376
377 /**
378 * List all enums (Postgres only)
379 *
380 * @param {string} [tableName] Table whose enum to list
381 * @param {Object} [options] Query options
382 *
383 * @returns {Promise}
384 * @private
385 */
386 pgListEnums(tableName, options) {
387 options = options || {};
388 const sql = this.QueryGenerator.pgListEnums(tableName);
389 return this.sequelize.query(sql, Object.assign({}, options, { plain: false, raw: true, type: QueryTypes.SELECT }));
390 }
391
392 /**
393 * Rename a table
394 *
395 * @param {string} before Current name of table
396 * @param {string} after New name from table
397 * @param {Object} [options] Query options
398 *
399 * @returns {Promise}
400 */
401 renameTable(before, after, options) {
402 options = options || {};
403 const sql = this.QueryGenerator.renameTableQuery(before, after);
404 return this.sequelize.query(sql, options);
405 }
406
407 /**
408 * Get all tables in current database
409 *
410 * @param {Object} [options] Query options
411 * @param {boolean} [options.raw=true] Run query in raw mode
412 * @param {QueryType} [options.type=QueryType.SHOWTABLE] query type
413 *
414 * @returns {Promise<Array>}
415 * @private
416 */
417 showAllTables(options) {
418 options = Object.assign({}, options, {
419 raw: true,
420 type: QueryTypes.SHOWTABLES
421 });
422
423 const showTablesSql = this.QueryGenerator.showTablesQuery();
424 return this.sequelize.query(showTablesSql, options).then(tableNames => _.flatten(tableNames));
425 }
426
427 /**
428 * Describe a table structure
429 *
430 * This method returns an array of hashes containing information about all attributes in the table.
431 *
432 * ```js
433 * {
434 * name: {
435 * type: 'VARCHAR(255)', // this will be 'CHARACTER VARYING' for pg!
436 * allowNull: true,
437 * defaultValue: null
438 * },
439 * isBetaMember: {
440 * type: 'TINYINT(1)', // this will be 'BOOLEAN' for pg!
441 * allowNull: false,
442 * defaultValue: false
443 * }
444 * }
445 * ```
446 *
447 * @param {string} tableName table name
448 * @param {Object} [options] Query options
449 *
450 * @returns {Promise<Object>}
451 */
452 describeTable(tableName, options) {
453 let schema = null;
454 let schemaDelimiter = null;
455
456 if (typeof options === 'string') {
457 schema = options;
458 } else if (typeof options === 'object' && options !== null) {
459 schema = options.schema || null;
460 schemaDelimiter = options.schemaDelimiter || null;
461 }
462
463 if (typeof tableName === 'object' && tableName !== null) {
464 schema = tableName.schema;
465 tableName = tableName.tableName;
466 }
467
468 const sql = this.QueryGenerator.describeTableQuery(tableName, schema, schemaDelimiter);
469 options = Object.assign({}, options, { type: QueryTypes.DESCRIBE });
470
471 return this.sequelize.query(sql, options).then(data => {
472 /*
473 * If no data is returned from the query, then the table name may be wrong.
474 * Query generators that use information_schema for retrieving table info will just return an empty result set,
475 * it will not throw an error like built-ins do (e.g. DESCRIBE on MySql).
476 */
477 if (_.isEmpty(data)) {
478 throw new Error(`No description found for "${tableName}" table. Check the table name and schema; remember, they _are_ case sensitive.`);
479 }
480
481 return data;
482 }).catch(e => {
483 if (e.original && e.original.code === 'ER_NO_SUCH_TABLE') {
484 throw Error(`No description found for "${tableName}" table. Check the table name and schema; remember, they _are_ case sensitive.`);
485 }
486
487 throw e;
488 });
489 }
490
491 /**
492 * Add a new column to a table
493 *
494 * ```js
495 * queryInterface.addColumn('tableA', 'columnC', Sequelize.STRING, {
496 * after: 'columnB' // after option is only supported by MySQL
497 * });
498 * ```
499 *
500 * @param {string} table Table to add column to
501 * @param {string} key Column name
502 * @param {Object} attribute Attribute definition
503 * @param {Object} [options] Query options
504 *
505 * @returns {Promise}
506 */
507 addColumn(table, key, attribute, options) {
508 if (!table || !key || !attribute) {
509 throw new Error('addColumn takes at least 3 arguments (table, attribute name, attribute definition)');
510 }
511
512 options = options || {};
513 attribute = this.sequelize.normalizeAttribute(attribute);
514 return this.sequelize.query(this.QueryGenerator.addColumnQuery(table, key, attribute), options);
515 }
516
517 /**
518 * Remove a column from a table
519 *
520 * @param {string} tableName Table to remove column from
521 * @param {string} attributeName Column name to remove
522 * @param {Object} [options] Query options
523 *
524 * @returns {Promise}
525 */
526 removeColumn(tableName, attributeName, options) {
527 options = options || {};
528 switch (this.sequelize.options.dialect) {
529 case 'sqlite':
530 // sqlite needs some special treatment as it cannot drop a column
531 return SQLiteQueryInterface.removeColumn(this, tableName, attributeName, options);
532 case 'mssql':
533 // mssql needs special treatment as it cannot drop a column with a default or foreign key constraint
534 return MSSQLQueryInterface.removeColumn(this, tableName, attributeName, options);
535 case 'mysql':
536 case 'mariadb':
537 // mysql/mariadb need special treatment as it cannot drop a column with a foreign key constraint
538 return MySQLQueryInterface.removeColumn(this, tableName, attributeName, options);
539 default:
540 return this.sequelize.query(this.QueryGenerator.removeColumnQuery(tableName, attributeName), options);
541 }
542 }
543
544 /**
545 * Change a column definition
546 *
547 * @param {string} tableName Table name to change from
548 * @param {string} attributeName Column name
549 * @param {Object} dataTypeOrOptions Attribute definition for new column
550 * @param {Object} [options] Query options
551 *
552 * @returns {Promise}
553 */
554 changeColumn(tableName, attributeName, dataTypeOrOptions, options) {
555 const attributes = {};
556 options = options || {};
557
558 if (_.values(DataTypes).includes(dataTypeOrOptions)) {
559 attributes[attributeName] = { type: dataTypeOrOptions, allowNull: true };
560 } else {
561 attributes[attributeName] = dataTypeOrOptions;
562 }
563
564 attributes[attributeName] = this.sequelize.normalizeAttribute(attributes[attributeName]);
565
566 if (this.sequelize.options.dialect === 'sqlite') {
567 // sqlite needs some special treatment as it cannot change a column
568 return SQLiteQueryInterface.changeColumn(this, tableName, attributes, options);
569 }
570 const query = this.QueryGenerator.attributesToSQL(attributes, {
571 context: 'changeColumn',
572 table: tableName
573 });
574 const sql = this.QueryGenerator.changeColumnQuery(tableName, query);
575
576 return this.sequelize.query(sql, options);
577 }
578
579 /**
580 * Rename a column
581 *
582 * @param {string} tableName Table name whose column to rename
583 * @param {string} attrNameBefore Current column name
584 * @param {string} attrNameAfter New column name
585 * @param {Object} [options] Query option
586 *
587 * @returns {Promise}
588 */
589 renameColumn(tableName, attrNameBefore, attrNameAfter, options) {
590 options = options || {};
591 return this.describeTable(tableName, options).then(data => {
592 if (!data[attrNameBefore]) {
593 throw new Error(`Table ${tableName} doesn't have the column ${attrNameBefore}`);
594 }
595
596 data = data[attrNameBefore] || {};
597
598 const _options = {};
599
600 _options[attrNameAfter] = {
601 attribute: attrNameAfter,
602 type: data.type,
603 allowNull: data.allowNull,
604 defaultValue: data.defaultValue
605 };
606
607 // fix: a not-null column cannot have null as default value
608 if (data.defaultValue === null && !data.allowNull) {
609 delete _options[attrNameAfter].defaultValue;
610 }
611
612 if (this.sequelize.options.dialect === 'sqlite') {
613 // sqlite needs some special treatment as it cannot rename a column
614 return SQLiteQueryInterface.renameColumn(this, tableName, attrNameBefore, attrNameAfter, options);
615 }
616 const sql = this.QueryGenerator.renameColumnQuery(
617 tableName,
618 attrNameBefore,
619 this.QueryGenerator.attributesToSQL(_options)
620 );
621 return this.sequelize.query(sql, options);
622 });
623 }
624
625 /**
626 * Add an index to a column
627 *
628 * @param {string|Object} tableName Table name to add index on, can be a object with schema
629 * @param {Array} [attributes] Use options.fields instead, List of attributes to add index on
630 * @param {Object} options indexes options
631 * @param {Array} options.fields List of attributes to add index on
632 * @param {boolean} [options.concurrently] Pass CONCURRENT so other operations run while the index is created
633 * @param {boolean} [options.unique] Create a unique index
634 * @param {string} [options.using] Useful for GIN indexes
635 * @param {string} [options.operator] Index operator
636 * @param {string} [options.type] Type of index, available options are UNIQUE|FULLTEXT|SPATIAL
637 * @param {string} [options.name] Name of the index. Default is <table>_<attr1>_<attr2>
638 * @param {Object} [options.where] Where condition on index, for partial indexes
639 * @param {string} [rawTablename] table name, this is just for backward compatibiity
640 *
641 * @returns {Promise}
642 */
643 addIndex(tableName, attributes, options, rawTablename) {
644 // Support for passing tableName, attributes, options or tableName, options (with a fields param which is the attributes)
645 if (!Array.isArray(attributes)) {
646 rawTablename = options;
647 options = attributes;
648 attributes = options.fields;
649 }
650
651 if (!rawTablename) {
652 // Map for backwards compat
653 rawTablename = tableName;
654 }
655
656 options = Utils.cloneDeep(options);
657 options.fields = attributes;
658 const sql = this.QueryGenerator.addIndexQuery(tableName, options, rawTablename);
659 return this.sequelize.query(sql, Object.assign({}, options, { supportsSearchPath: false }));
660 }
661
662 /**
663 * Show indexes on a table
664 *
665 * @param {string} tableName table name
666 * @param {Object} [options] Query options
667 *
668 * @returns {Promise<Array>}
669 * @private
670 */
671 showIndex(tableName, options) {
672 const sql = this.QueryGenerator.showIndexesQuery(tableName, options);
673 return this.sequelize.query(sql, Object.assign({}, options, { type: QueryTypes.SHOWINDEXES }));
674 }
675
676 getForeignKeysForTables(tableNames, options) {
677 if (tableNames.length === 0) {
678 return Promise.resolve({});
679 }
680
681 options = Object.assign({}, options || {}, { type: QueryTypes.FOREIGNKEYS });
682
683 return Promise.map(tableNames, tableName =>
684 this.sequelize.query(this.QueryGenerator.getForeignKeysQuery(tableName, this.sequelize.config.database), options)
685 ).then(results => {
686 const result = {};
687
688 tableNames.forEach((tableName, i) => {
689 if (_.isObject(tableName)) {
690 tableName = `${tableName.schema}.${tableName.tableName}`;
691 }
692
693 result[tableName] = Array.isArray(results[i])
694 ? results[i].map(r => r.constraint_name)
695 : [results[i] && results[i].constraint_name];
696
697 result[tableName] = result[tableName].filter(_.identity);
698 });
699
700 return result;
701 });
702 }
703
704 /**
705 * Get foreign key references details for the table
706 *
707 * Those details contains constraintSchema, constraintName, constraintCatalog
708 * tableCatalog, tableSchema, tableName, columnName,
709 * referencedTableCatalog, referencedTableCatalog, referencedTableSchema, referencedTableName, referencedColumnName.
710 * Remind: constraint informations won't return if it's sqlite.
711 *
712 * @param {string} tableName table name
713 * @param {Object} [options] Query options
714 *
715 * @returns {Promise}
716 */
717 getForeignKeyReferencesForTable(tableName, options) {
718 const queryOptions = Object.assign({}, options, {
719 type: QueryTypes.FOREIGNKEYS
720 });
721 const catalogName = this.sequelize.config.database;
722 switch (this.sequelize.options.dialect) {
723 case 'sqlite':
724 // sqlite needs some special treatment.
725 return SQLiteQueryInterface.getForeignKeyReferencesForTable(this, tableName, queryOptions);
726 case 'postgres':
727 {
728 // postgres needs some special treatment as those field names returned are all lowercase
729 // in order to keep same result with other dialects.
730 const query = this.QueryGenerator.getForeignKeyReferencesQuery(tableName, catalogName);
731 return this.sequelize.query(query, queryOptions)
732 .then(result => result.map(Utils.camelizeObjectKeys));
733 }
734 case 'mssql':
735 case 'mysql':
736 case 'mariadb':
737 default: {
738 const query = this.QueryGenerator.getForeignKeysQuery(tableName, catalogName);
739 return this.sequelize.query(query, queryOptions);
740 }
741 }
742 }
743
744 /**
745 * Remove an already existing index from a table
746 *
747 * @param {string} tableName Table name to drop index from
748 * @param {string} indexNameOrAttributes Index name
749 * @param {Object} [options] Query options
750 *
751 * @returns {Promise}
752 */
753 removeIndex(tableName, indexNameOrAttributes, options) {
754 options = options || {};
755 const sql = this.QueryGenerator.removeIndexQuery(tableName, indexNameOrAttributes);
756 return this.sequelize.query(sql, options);
757 }
758
759 /**
760 * Add a constraint to a table
761 *
762 * Available constraints:
763 * - UNIQUE
764 * - DEFAULT (MSSQL only)
765 * - CHECK (MySQL - Ignored by the database engine )
766 * - FOREIGN KEY
767 * - PRIMARY KEY
768 *
769 * @example <caption>UNIQUE</caption>
770 * queryInterface.addConstraint('Users', ['email'], {
771 * type: 'unique',
772 * name: 'custom_unique_constraint_name'
773 * });
774 *
775 * @example <caption>CHECK</caption>
776 * queryInterface.addConstraint('Users', ['roles'], {
777 * type: 'check',
778 * where: {
779 * roles: ['user', 'admin', 'moderator', 'guest']
780 * }
781 * });
782 *
783 * @example <caption>Default - MSSQL only</caption>
784 * queryInterface.addConstraint('Users', ['roles'], {
785 * type: 'default',
786 * defaultValue: 'guest'
787 * });
788 *
789 * @example <caption>Primary Key</caption>
790 * queryInterface.addConstraint('Users', ['username'], {
791 * type: 'primary key',
792 * name: 'custom_primary_constraint_name'
793 * });
794 *
795 * @example <caption>Foreign Key</caption>
796 * queryInterface.addConstraint('Posts', ['username'], {
797 * type: 'foreign key',
798 * name: 'custom_fkey_constraint_name',
799 * references: { //Required field
800 * table: 'target_table_name',
801 * field: 'target_column_name'
802 * },
803 * onDelete: 'cascade',
804 * onUpdate: 'cascade'
805 * });
806 *
807 * @param {string} tableName Table name where you want to add a constraint
808 * @param {Array} attributes Array of column names to apply the constraint over
809 * @param {Object} options An object to define the constraint name, type etc
810 * @param {string} options.type Type of constraint. One of the values in available constraints(case insensitive)
811 * @param {string} [options.name] Name of the constraint. If not specified, sequelize automatically creates a named constraint based on constraint type, table & column names
812 * @param {string} [options.defaultValue] The value for the default constraint
813 * @param {Object} [options.where] Where clause/expression for the CHECK constraint
814 * @param {Object} [options.references] Object specifying target table, column name to create foreign key constraint
815 * @param {string} [options.references.table] Target table name
816 * @param {string} [options.references.field] Target column name
817 * @param {string} [rawTablename] Table name, for backward compatibility
818 *
819 * @returns {Promise}
820 */
821 addConstraint(tableName, attributes, options, rawTablename) {
822 if (!Array.isArray(attributes)) {
823 rawTablename = options;
824 options = attributes;
825 attributes = options.fields;
826 }
827
828 if (!options.type) {
829 throw new Error('Constraint type must be specified through options.type');
830 }
831
832 if (!rawTablename) {
833 // Map for backwards compat
834 rawTablename = tableName;
835 }
836
837 options = Utils.cloneDeep(options);
838 options.fields = attributes;
839
840 if (this.sequelize.dialect.name === 'sqlite') {
841 return SQLiteQueryInterface.addConstraint(this, tableName, options, rawTablename);
842 }
843 const sql = this.QueryGenerator.addConstraintQuery(tableName, options, rawTablename);
844 return this.sequelize.query(sql, options);
845 }
846
847 showConstraint(tableName, constraintName, options) {
848 const sql = this.QueryGenerator.showConstraintsQuery(tableName, constraintName);
849 return this.sequelize.query(sql, Object.assign({}, options, { type: QueryTypes.SHOWCONSTRAINTS }));
850 }
851
852 /**
853 * Remove a constraint from a table
854 *
855 * @param {string} tableName Table name to drop constraint from
856 * @param {string} constraintName Constraint name
857 * @param {Object} options Query options
858 *
859 * @returns {Promise}
860 */
861 removeConstraint(tableName, constraintName, options) {
862 options = options || {};
863
864 switch (this.sequelize.options.dialect) {
865 case 'mysql':
866 case 'mariadb':
867 //does not support DROP CONSTRAINT. Instead DROP PRIMARY, FOREIGN KEY, INDEX should be used
868 return MySQLQueryInterface.removeConstraint(this, tableName, constraintName, options);
869 case 'sqlite':
870 return SQLiteQueryInterface.removeConstraint(this, tableName, constraintName, options);
871 default:
872 const sql = this.QueryGenerator.removeConstraintQuery(tableName, constraintName);
873 return this.sequelize.query(sql, options);
874 }
875 }
876
877 insert(instance, tableName, values, options) {
878 options = Utils.cloneDeep(options);
879 options.hasTrigger = instance && instance.constructor.options.hasTrigger;
880 const sql = this.QueryGenerator.insertQuery(tableName, values, instance && instance.constructor.rawAttributes, options);
881
882 options.type = QueryTypes.INSERT;
883 options.instance = instance;
884
885 return this.sequelize.query(sql, options).then(results => {
886 if (instance) results[0].isNewRecord = false;
887 return results;
888 });
889 }
890
891 /**
892 * Upsert
893 *
894 * @param {string} tableName table to upsert on
895 * @param {Object} insertValues values to be inserted, mapped to field name
896 * @param {Object} updateValues values to be updated, mapped to field name
897 * @param {Object} where various conditions
898 * @param {Model} model Model to upsert on
899 * @param {Object} options query options
900 *
901 * @returns {Promise<boolean,?number>} Resolves an array with <created, primaryKey>
902 */
903 upsert(tableName, insertValues, updateValues, where, model, options) {
904 const wheres = [];
905 const attributes = Object.keys(insertValues);
906 let indexes = [];
907 let indexFields;
908
909 options = _.clone(options);
910
911 if (!Utils.isWhereEmpty(where)) {
912 wheres.push(where);
913 }
914
915 // Lets combine unique keys and indexes into one
916 indexes = _.map(model.uniqueKeys, value => {
917 return value.fields;
918 });
919
920 model._indexes.forEach(value => {
921 if (value.unique) {
922 // fields in the index may both the strings or objects with an attribute property - lets sanitize that
923 indexFields = value.fields.map(field => {
924 if (_.isPlainObject(field)) {
925 return field.attribute;
926 }
927 return field;
928 });
929 indexes.push(indexFields);
930 }
931 });
932
933 for (const index of indexes) {
934 if (_.intersection(attributes, index).length === index.length) {
935 where = {};
936 for (const field of index) {
937 where[field] = insertValues[field];
938 }
939 wheres.push(where);
940 }
941 }
942
943 where = { [Op.or]: wheres };
944
945 options.type = QueryTypes.UPSERT;
946 options.raw = true;
947
948 const sql = this.QueryGenerator.upsertQuery(tableName, insertValues, updateValues, where, model, options);
949 return this.sequelize.query(sql, options).then(result => {
950 switch (this.sequelize.options.dialect) {
951 case 'postgres':
952 return [result.created, result.primary_key];
953
954 case 'mssql':
955 return [
956 result.$action === 'INSERT',
957 result[model.primaryKeyField]
958 ];
959
960 // MySQL returns 1 for inserted, 2 for updated
961 // http://dev.mysql.com/doc/refman/5.0/en/insert-on-duplicate.html.
962 case 'mysql':
963 case 'mariadb':
964 return [result === 1, undefined];
965
966 default:
967 return [result, undefined];
968 }
969 });
970 }
971
972 /**
973 * Insert multiple records into a table
974 *
975 * @example
976 * queryInterface.bulkInsert('roles', [{
977 * label: 'user',
978 * createdAt: new Date(),
979 * updatedAt: new Date()
980 * }, {
981 * label: 'admin',
982 * createdAt: new Date(),
983 * updatedAt: new Date()
984 * }]);
985 *
986 * @param {string} tableName Table name to insert record to
987 * @param {Array} records List of records to insert
988 * @param {Object} options Various options, please see Model.bulkCreate options
989 * @param {Object} attributes Various attributes mapped by field name
990 *
991 * @returns {Promise}
992 */
993 bulkInsert(tableName, records, options, attributes) {
994 options = _.clone(options) || {};
995 options.type = QueryTypes.INSERT;
996
997 return this.sequelize.query(
998 this.QueryGenerator.bulkInsertQuery(tableName, records, options, attributes),
999 options
1000 ).then(results => results[0]);
1001 }
1002
1003 update(instance, tableName, values, identifier, options) {
1004 options = _.clone(options || {});
1005 options.hasTrigger = !!(instance && instance._modelOptions && instance._modelOptions.hasTrigger);
1006
1007 const sql = this.QueryGenerator.updateQuery(tableName, values, identifier, options, instance.constructor.rawAttributes);
1008
1009 options.type = QueryTypes.UPDATE;
1010
1011 options.instance = instance;
1012 return this.sequelize.query(sql, options);
1013 }
1014
1015 /**
1016 * Update multiple records of a table
1017 *
1018 * @example
1019 * queryInterface.bulkUpdate('roles', {
1020 * label: 'admin',
1021 * }, {
1022 * userType: 3,
1023 * },
1024 * );
1025 *
1026 * @param {string} tableName Table name to update
1027 * @param {Object} values Values to be inserted, mapped to field name
1028 * @param {Object} identifier A hash with conditions OR an ID as integer OR a string with conditions
1029 * @param {Object} [options] Various options, please see Model.bulkCreate options
1030 * @param {Object} [attributes] Attributes on return objects if supported by SQL dialect
1031 *
1032 * @returns {Promise}
1033 */
1034 bulkUpdate(tableName, values, identifier, options, attributes) {
1035 options = Utils.cloneDeep(options);
1036 if (typeof identifier === 'object') identifier = Utils.cloneDeep(identifier);
1037
1038 const sql = this.QueryGenerator.updateQuery(tableName, values, identifier, options, attributes);
1039 const table = _.isObject(tableName) ? tableName : { tableName };
1040 const model = _.find(this.sequelize.modelManager.models, { tableName: table.tableName });
1041
1042 options.model = model;
1043 return this.sequelize.query(sql, options);
1044 }
1045
1046 delete(instance, tableName, identifier, options) {
1047 const cascades = [];
1048 const sql = this.QueryGenerator.deleteQuery(tableName, identifier, {}, instance.constructor);
1049
1050 options = _.clone(options) || {};
1051
1052 // Check for a restrict field
1053 if (!!instance.constructor && !!instance.constructor.associations) {
1054 const keys = Object.keys(instance.constructor.associations);
1055 const length = keys.length;
1056 let association;
1057
1058 for (let i = 0; i < length; i++) {
1059 association = instance.constructor.associations[keys[i]];
1060 if (association.options && association.options.onDelete &&
1061 association.options.onDelete.toLowerCase() === 'cascade' &&
1062 association.options.useHooks === true) {
1063 cascades.push(association.accessors.get);
1064 }
1065 }
1066 }
1067
1068 return Promise.each(cascades, cascade => {
1069 return instance[cascade](options).then(instances => {
1070 // Check for hasOne relationship with non-existing associate ("has zero")
1071 if (!instances) {
1072 return Promise.resolve();
1073 }
1074
1075 if (!Array.isArray(instances)) instances = [instances];
1076
1077 return Promise.each(instances, instance => instance.destroy(options));
1078 });
1079 }).then(() => {
1080 options.instance = instance;
1081 return this.sequelize.query(sql, options);
1082 });
1083 }
1084
1085 /**
1086 * Delete multiple records from a table
1087 *
1088 * @param {string} tableName table name from where to delete records
1089 * @param {Object} where where conditions to find records to delete
1090 * @param {Object} [options] options
1091 * @param {boolean} [options.truncate] Use truncate table command
1092 * @param {Model} [model] Model
1093 *
1094 * @returns {Promise}
1095 */
1096 bulkDelete(tableName, where, options, model) {
1097 options = Utils.cloneDeep(options);
1098 options = _.defaults(options, { limit: null });
1099
1100 if (options.truncate === true) {
1101 return this.sequelize.query(
1102 this.QueryGenerator.truncateTableQuery(tableName, options),
1103 options
1104 );
1105 }
1106
1107 if (typeof identifier === 'object') where = Utils.cloneDeep(where);
1108
1109 return this.sequelize.query(
1110 this.QueryGenerator.deleteQuery(tableName, where, options, model),
1111 options
1112 );
1113 }
1114
1115 select(model, tableName, optionsArg) {
1116 const options = Object.assign({}, optionsArg, { type: QueryTypes.SELECT, model });
1117
1118 return this.sequelize.query(
1119 this.QueryGenerator.selectQuery(tableName, options, model),
1120 options
1121 );
1122 }
1123
1124 increment(model, tableName, values, identifier, options) {
1125 options = Utils.cloneDeep(options);
1126
1127 const sql = this.QueryGenerator.arithmeticQuery('+', tableName, values, identifier, options, options.attributes);
1128
1129 options.type = QueryTypes.UPDATE;
1130 options.model = model;
1131
1132 return this.sequelize.query(sql, options);
1133 }
1134
1135 decrement(model, tableName, values, identifier, options) {
1136 options = Utils.cloneDeep(options);
1137
1138 const sql = this.QueryGenerator.arithmeticQuery('-', tableName, values, identifier, options, options.attributes);
1139
1140 options.type = QueryTypes.UPDATE;
1141 options.model = model;
1142
1143 return this.sequelize.query(sql, options);
1144 }
1145
1146 rawSelect(tableName, options, attributeSelector, Model) {
1147 options = Utils.cloneDeep(options);
1148 options = _.defaults(options, {
1149 raw: true,
1150 plain: true,
1151 type: QueryTypes.SELECT
1152 });
1153
1154 const sql = this.QueryGenerator.selectQuery(tableName, options, Model);
1155
1156 if (attributeSelector === undefined) {
1157 throw new Error('Please pass an attribute selector!');
1158 }
1159
1160 return this.sequelize.query(sql, options).then(data => {
1161 if (!options.plain) {
1162 return data;
1163 }
1164
1165 const result = data ? data[attributeSelector] : null;
1166
1167 if (!options || !options.dataType) {
1168 return result;
1169 }
1170
1171 const dataType = options.dataType;
1172
1173 if (dataType instanceof DataTypes.DECIMAL || dataType instanceof DataTypes.FLOAT) {
1174 if (result !== null) {
1175 return parseFloat(result);
1176 }
1177 }
1178 if (dataType instanceof DataTypes.INTEGER || dataType instanceof DataTypes.BIGINT) {
1179 return parseInt(result, 10);
1180 }
1181 if (dataType instanceof DataTypes.DATE) {
1182 if (result !== null && !(result instanceof Date)) {
1183 return new Date(result);
1184 }
1185 }
1186 return result;
1187 });
1188 }
1189
1190 createTrigger(tableName, triggerName, timingType, fireOnArray, functionName, functionParams, optionsArray, options) {
1191 const sql = this.QueryGenerator.createTrigger(tableName, triggerName, timingType, fireOnArray, functionName, functionParams, optionsArray);
1192 options = options || {};
1193 if (sql) {
1194 return this.sequelize.query(sql, options);
1195 }
1196 return Promise.resolve();
1197 }
1198
1199 dropTrigger(tableName, triggerName, options) {
1200 const sql = this.QueryGenerator.dropTrigger(tableName, triggerName);
1201 options = options || {};
1202
1203 if (sql) {
1204 return this.sequelize.query(sql, options);
1205 }
1206 return Promise.resolve();
1207 }
1208
1209 renameTrigger(tableName, oldTriggerName, newTriggerName, options) {
1210 const sql = this.QueryGenerator.renameTrigger(tableName, oldTriggerName, newTriggerName);
1211 options = options || {};
1212
1213 if (sql) {
1214 return this.sequelize.query(sql, options);
1215 }
1216 return Promise.resolve();
1217 }
1218
1219 /**
1220 * Create an SQL function
1221 *
1222 * @example
1223 * queryInterface.createFunction(
1224 * 'someFunction',
1225 * [
1226 * {type: 'integer', name: 'param', direction: 'IN'}
1227 * ],
1228 * 'integer',
1229 * 'plpgsql',
1230 * 'RETURN param + 1;',
1231 * [
1232 * 'IMMUTABLE',
1233 * 'LEAKPROOF'
1234 * ],
1235 * {
1236 * variables:
1237 * [
1238 * {type: 'integer', name: 'myVar', default: 100}
1239 * ],
1240 * force: true
1241 * };
1242 * );
1243 *
1244 * @param {string} functionName Name of SQL function to create
1245 * @param {Array} params List of parameters declared for SQL function
1246 * @param {string} returnType SQL type of function returned value
1247 * @param {string} language The name of the language that the function is implemented in
1248 * @param {string} body Source code of function
1249 * @param {Array} optionsArray Extra-options for creation
1250 * @param {Object} [options] query options
1251 * @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
1252 * @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.
1253 *
1254 * @returns {Promise}
1255 */
1256 createFunction(functionName, params, returnType, language, body, optionsArray, options) {
1257 const sql = this.QueryGenerator.createFunction(functionName, params, returnType, language, body, optionsArray, options);
1258 options = options || {};
1259
1260 if (sql) {
1261 return this.sequelize.query(sql, options);
1262 }
1263 return Promise.resolve();
1264 }
1265
1266 /**
1267 * Drop an SQL function
1268 *
1269 * @example
1270 * queryInterface.dropFunction(
1271 * 'someFunction',
1272 * [
1273 * {type: 'varchar', name: 'param1', direction: 'IN'},
1274 * {type: 'integer', name: 'param2', direction: 'INOUT'}
1275 * ]
1276 * );
1277 *
1278 * @param {string} functionName Name of SQL function to drop
1279 * @param {Array} params List of parameters declared for SQL function
1280 * @param {Object} [options] query options
1281 *
1282 * @returns {Promise}
1283 */
1284 dropFunction(functionName, params, options) {
1285 const sql = this.QueryGenerator.dropFunction(functionName, params);
1286 options = options || {};
1287
1288 if (sql) {
1289 return this.sequelize.query(sql, options);
1290 }
1291 return Promise.resolve();
1292 }
1293
1294 /**
1295 * Rename an SQL function
1296 *
1297 * @example
1298 * queryInterface.renameFunction(
1299 * 'fooFunction',
1300 * [
1301 * {type: 'varchar', name: 'param1', direction: 'IN'},
1302 * {type: 'integer', name: 'param2', direction: 'INOUT'}
1303 * ],
1304 * 'barFunction'
1305 * );
1306 *
1307 * @param {string} oldFunctionName Current name of function
1308 * @param {Array} params List of parameters declared for SQL function
1309 * @param {string} newFunctionName New name of function
1310 * @param {Object} [options] query options
1311 *
1312 * @returns {Promise}
1313 */
1314 renameFunction(oldFunctionName, params, newFunctionName, options) {
1315 const sql = this.QueryGenerator.renameFunction(oldFunctionName, params, newFunctionName);
1316 options = options || {};
1317
1318 if (sql) {
1319 return this.sequelize.query(sql, options);
1320 }
1321 return Promise.resolve();
1322 }
1323
1324 // Helper methods useful for querying
1325
1326 /**
1327 * Escape an identifier (e.g. a table or attribute name)
1328 *
1329 * @param {string} identifier identifier to quote
1330 * @param {boolean} [force] If force is true,the identifier will be quoted even if the `quoteIdentifiers` option is false.
1331 *
1332 * @private
1333 */
1334 quoteIdentifier(identifier, force) {
1335 return this.QueryGenerator.quoteIdentifier(identifier, force);
1336 }
1337
1338 quoteTable(identifier) {
1339 return this.QueryGenerator.quoteTable(identifier);
1340 }
1341
1342 /**
1343 * Quote array of identifiers at once
1344 *
1345 * @param {string[]} identifiers array of identifiers to quote
1346 * @param {boolean} [force] If force is true,the identifier will be quoted even if the `quoteIdentifiers` option is false.
1347 *
1348 * @private
1349 */
1350 quoteIdentifiers(identifiers, force) {
1351 return this.QueryGenerator.quoteIdentifiers(identifiers, force);
1352 }
1353
1354 /**
1355 * Escape a value (e.g. a string, number or date)
1356 *
1357 * @param {string} value string to escape
1358 *
1359 * @private
1360 */
1361 escape(value) {
1362 return this.QueryGenerator.escape(value);
1363 }
1364
1365 setIsolationLevel(transaction, value, options) {
1366 if (!transaction || !(transaction instanceof Transaction)) {
1367 throw new Error('Unable to set isolation level for a transaction without transaction object!');
1368 }
1369
1370 if (transaction.parent || !value) {
1371 // Not possible to set a separate isolation level for savepoints
1372 return Promise.resolve();
1373 }
1374
1375 options = Object.assign({}, options, {
1376 transaction: transaction.parent || transaction
1377 });
1378
1379 const sql = this.QueryGenerator.setIsolationLevelQuery(value, {
1380 parent: transaction.parent
1381 });
1382
1383 if (!sql) return Promise.resolve();
1384
1385 return this.sequelize.query(sql, options);
1386 }
1387
1388 startTransaction(transaction, options) {
1389 if (!transaction || !(transaction instanceof Transaction)) {
1390 throw new Error('Unable to start a transaction without transaction object!');
1391 }
1392
1393 options = Object.assign({}, options, {
1394 transaction: transaction.parent || transaction
1395 });
1396 options.transaction.name = transaction.parent ? transaction.name : undefined;
1397 const sql = this.QueryGenerator.startTransactionQuery(transaction);
1398
1399 return this.sequelize.query(sql, options);
1400 }
1401
1402 deferConstraints(transaction, options) {
1403 options = Object.assign({}, options, {
1404 transaction: transaction.parent || transaction
1405 });
1406
1407 const sql = this.QueryGenerator.deferConstraintsQuery(options);
1408
1409 if (sql) {
1410 return this.sequelize.query(sql, options);
1411 }
1412
1413 return Promise.resolve();
1414 }
1415
1416 commitTransaction(transaction, options) {
1417 if (!transaction || !(transaction instanceof Transaction)) {
1418 throw new Error('Unable to commit a transaction without transaction object!');
1419 }
1420 if (transaction.parent) {
1421 // Savepoints cannot be committed
1422 return Promise.resolve();
1423 }
1424
1425 options = Object.assign({}, options, {
1426 transaction: transaction.parent || transaction,
1427 supportsSearchPath: false,
1428 completesTransaction: true
1429 });
1430
1431 const sql = this.QueryGenerator.commitTransactionQuery(transaction);
1432 const promise = this.sequelize.query(sql, options);
1433
1434 transaction.finished = 'commit';
1435
1436 return promise;
1437 }
1438
1439 rollbackTransaction(transaction, options) {
1440 if (!transaction || !(transaction instanceof Transaction)) {
1441 throw new Error('Unable to rollback a transaction without transaction object!');
1442 }
1443
1444 options = Object.assign({}, options, {
1445 transaction: transaction.parent || transaction,
1446 supportsSearchPath: false,
1447 completesTransaction: true
1448 });
1449 options.transaction.name = transaction.parent ? transaction.name : undefined;
1450 const sql = this.QueryGenerator.rollbackTransactionQuery(transaction);
1451 const promise = this.sequelize.query(sql, options);
1452
1453 transaction.finished = 'rollback';
1454
1455 return promise;
1456 }
1457}
1458
1459module.exports = QueryInterface;
1460module.exports.QueryInterface = QueryInterface;
1461module.exports.default = QueryInterface;