123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557 |
- 'use strict';
- const chai = require('chai');
- const expect = chai.expect;
- const Support = require('../support');
- const { DataTypes } = require('@sequelize/core');
- const dialect = Support.getTestDialect();
- describe(Support.getTestDialectTeaser('QueryInterface'), () => {
- beforeEach(async function () {
- this.queryInterface = this.sequelize.queryInterface;
- });
- describe('changeColumn', () => {
- if (Support.sequelize.dialect.supports.schemas) {
- it('should support schemas', async function () {
- await this.sequelize.createSchema('archive');
- await this.queryInterface.createTable(
- {
- tableName: 'users',
- schema: 'archive',
- },
- {
- id: {
- type: DataTypes.INTEGER,
- primaryKey: true,
- autoIncrement: true,
- },
- currency: DataTypes.INTEGER,
- },
- );
- await this.queryInterface.changeColumn(
- {
- tableName: 'users',
- schema: 'archive',
- },
- 'currency',
- {
- type: DataTypes.FLOAT,
- },
- );
- const table = await this.queryInterface.describeTable({
- tableName: 'users',
- schema: 'archive',
- });
- if (['postgres', 'postgres-native', 'mssql', 'db2'].includes(dialect)) {
- expect(table.currency.type).to.equal('REAL');
- } else {
- expect(table.currency.type).to.equal('FLOAT');
- }
- });
- }
- it('should change columns', async function () {
- await this.queryInterface.createTable(
- {
- tableName: 'users',
- },
- {
- id: {
- type: DataTypes.INTEGER,
- primaryKey: true,
- autoIncrement: true,
- },
- currency: DataTypes.INTEGER,
- },
- );
- if (dialect === 'db2') {
- // DB2 can change only one attr of a column
- await this.queryInterface.changeColumn('users', 'currency', {
- type: DataTypes.FLOAT,
- });
- } else {
- await this.queryInterface.changeColumn('users', 'currency', {
- type: DataTypes.FLOAT,
- allowNull: true,
- });
- }
- const table = await this.queryInterface.describeTable({
- tableName: 'users',
- });
- if (['postgres', 'postgres-native', 'mssql', 'sqlite3', 'db2'].includes(dialect)) {
- expect(table.currency.type).to.equal('REAL');
- } else {
- expect(table.currency.type).to.equal('FLOAT');
- }
- });
- // MSSQL doesn't support using a modified column in a check constraint.
- // https://docs.microsoft.com/en-us/sql/t-sql/statements/alter-table-transact-sql
- if (dialect !== 'mssql' && dialect !== 'db2') {
- it('should work with enums (case 1)', async function () {
- await this.queryInterface.createTable(
- {
- tableName: 'users',
- },
- {
- firstName: DataTypes.STRING,
- },
- );
- await this.queryInterface.changeColumn('users', 'firstName', {
- type: DataTypes.ENUM(['value1', 'value2', 'value3']),
- });
- });
- it('should work with enums (case 2)', async function () {
- await this.queryInterface.createTable(
- {
- tableName: 'users',
- },
- {
- firstName: DataTypes.STRING,
- },
- );
- await this.queryInterface.changeColumn('users', 'firstName', {
- type: DataTypes.ENUM(['value1', 'value2', 'value3']),
- });
- });
- if (Support.sequelize.dialect.supports.schemas) {
- it('should work with enums with schemas', async function () {
- await this.sequelize.createSchema('archive');
- await this.queryInterface.createTable(
- {
- tableName: 'users',
- schema: 'archive',
- },
- {
- firstName: DataTypes.STRING,
- },
- );
- await this.queryInterface.changeColumn(
- {
- tableName: 'users',
- schema: 'archive',
- },
- 'firstName',
- {
- type: DataTypes.ENUM(['value1', 'value2', 'value3']),
- },
- );
- });
- }
- }
- describe('should support foreign keys', () => {
- beforeEach(async function () {
- await this.queryInterface.createTable('users', {
- id: {
- type: DataTypes.INTEGER,
- primaryKey: true,
- autoIncrement: true,
- },
- level_id: {
- type: DataTypes.INTEGER,
- allowNull: false,
- },
- });
- await this.queryInterface.createTable('level', {
- id: {
- type: DataTypes.INTEGER,
- primaryKey: true,
- autoIncrement: true,
- },
- });
- });
- it('able to change column to foreign key', async function () {
- const foreignKeys = await this.queryInterface.showConstraints('users', {
- constraintType: 'FOREIGN KEY',
- });
- expect(foreignKeys).to.be.an('array');
- expect(foreignKeys).to.be.empty;
- await this.queryInterface.changeColumn('users', 'level_id', {
- type: DataTypes.INTEGER,
- references: {
- table: 'level',
- key: 'id',
- },
- onUpdate: 'cascade',
- onDelete: 'cascade',
- });
- const newForeignKeys = await this.queryInterface.showConstraints('users', {
- constraintType: 'FOREIGN KEY',
- });
- expect(newForeignKeys).to.be.an('array');
- expect(newForeignKeys).to.have.lengthOf(1);
- expect(newForeignKeys[0].columnNames).to.deep.equal(['level_id']);
- });
- it('able to change column property without affecting other properties', async function () {
- // 1. look for users table information
- // 2. change column level_id on users to have a Foreign Key
- // 3. look for users table Foreign Keys information
- // 4. change column level_id AGAIN to allow null values
- // 5. look for new foreign keys information
- // 6. look for new table structure information
- // 7. compare foreign keys and tables(before and after the changes)
- const firstTable = await this.queryInterface.describeTable({
- tableName: 'users',
- });
- await this.queryInterface.changeColumn('users', 'level_id', {
- type: DataTypes.INTEGER,
- references: {
- table: 'level',
- key: 'id',
- },
- onUpdate: 'cascade',
- onDelete: 'cascade',
- });
- const keys = await this.queryInterface.showConstraints('users', {
- constraintType: 'FOREIGN KEY',
- });
- const firstForeignKeys = keys;
- await this.queryInterface.changeColumn('users', 'level_id', {
- type: DataTypes.INTEGER,
- allowNull: true,
- });
- const newForeignKeys = await this.queryInterface.showConstraints('users', {
- constraintType: 'FOREIGN KEY',
- });
- expect(firstForeignKeys.length).to.equal(newForeignKeys.length);
- expect(firstForeignKeys[0].columnNames).to.deep.equal(['level_id']);
- expect(firstForeignKeys[0].columnNames).to.deep.equal(newForeignKeys[0].columnNames);
- const describedTable = await this.queryInterface.describeTable({
- tableName: 'users',
- });
- expect(describedTable.level_id).to.have.property('allowNull');
- expect(describedTable.level_id.allowNull).to.not.equal(firstTable.level_id.allowNull);
- expect(describedTable.level_id.allowNull).to.equal(true);
- });
- if (!['db2', 'ibmi', 'sqlite3'].includes(dialect)) {
- it('should change the comment of column', async function () {
- const describedTable = await this.queryInterface.describeTable({
- tableName: 'users',
- });
- expect(describedTable.level_id.comment).to.equal(null);
- await this.queryInterface.changeColumn('users', 'level_id', {
- type: DataTypes.INTEGER,
- comment: 'FooBar',
- });
- const describedTable2 = await this.queryInterface.describeTable({ tableName: 'users' });
- expect(describedTable2.level_id.comment).to.equal('FooBar');
- });
- }
- });
- // sqlite has limited ALTER TABLE capapibilites which requires a workaround involving recreating tables.
- // This leads to issues with losing data or losing foreign key references.
- // The tests below address these problems
- // TODO: run in all dialects
- if (dialect === 'sqlite3') {
- it('should not loose indexes & unique constraints when adding or modifying columns', async function () {
- await this.queryInterface.createTable('foos', {
- id: {
- allowNull: false,
- autoIncrement: true,
- primaryKey: true,
- type: DataTypes.INTEGER,
- },
- name: {
- allowNull: false,
- unique: true,
- type: DataTypes.STRING,
- },
- email: {
- allowNull: false,
- unique: true,
- type: DataTypes.STRING,
- },
- birthday: {
- allowNull: false,
- type: DataTypes.DATEONLY,
- },
- });
- await this.queryInterface.addIndex('foos', ['birthday']);
- const initialIndexes = await this.queryInterface.showIndex('foos');
- let table = await this.queryInterface.describeTable('foos');
- expect(table.email.unique).to.equal(true, '(0) email column should be unique');
- expect(table.name.unique).to.equal(true, '(0) name column should be unique');
- await this.queryInterface.addColumn('foos', 'phone', {
- type: DataTypes.STRING,
- defaultValue: null,
- allowNull: true,
- });
- expect(await this.queryInterface.showIndex('foos')).to.deep.equal(
- initialIndexes,
- 'addColumn should not modify indexes',
- );
- table = await this.queryInterface.describeTable('foos');
- expect(table.phone.allowNull).to.equal(true, '(1) phone column should allow null values');
- expect(table.phone.defaultValue).to.equal(
- null,
- '(1) phone column should have a default value of null',
- );
- expect(table.email.unique).to.equal(true, '(1) email column should remain unique');
- expect(table.name.unique).to.equal(true, '(1) name column should remain unique');
- await this.queryInterface.changeColumn('foos', 'email', {
- type: DataTypes.STRING,
- allowNull: true,
- });
- expect(await this.queryInterface.showIndex('foos')).to.deep.equal(
- initialIndexes,
- 'changeColumn should not modify indexes',
- );
- table = await this.queryInterface.describeTable('foos');
- expect(table.email.allowNull).to.equal(true, '(2) email column should allow null values');
- expect(table.email.unique).to.equal(true, '(2) email column should remain unique');
- expect(table.name.unique).to.equal(true, '(2) name column should remain unique');
- });
- it('should add unique constraints to 2 columns and keep allowNull', async function () {
- await this.queryInterface.createTable(
- {
- tableName: 'Foos',
- },
- {
- id: {
- allowNull: false,
- autoIncrement: true,
- primaryKey: true,
- type: DataTypes.INTEGER,
- },
- name: {
- allowNull: false,
- type: DataTypes.STRING,
- },
- email: {
- allowNull: true,
- type: DataTypes.STRING,
- },
- },
- );
- await this.queryInterface.changeColumn('Foos', 'name', {
- type: DataTypes.STRING,
- unique: true,
- });
- await this.queryInterface.changeColumn('Foos', 'email', {
- type: DataTypes.STRING,
- unique: true,
- });
- const table = await this.queryInterface.describeTable({
- tableName: 'Foos',
- });
- expect(table.name.allowNull).to.equal(false);
- expect(table.name.unique).to.equal(true);
- expect(table.email.allowNull).to.equal(true);
- expect(table.email.unique).to.equal(true);
- });
- it('should not remove foreign keys when adding or modifying columns', async function () {
- const Task = this.sequelize.define('Task', { title: DataTypes.STRING });
- const User = this.sequelize.define('User', { username: DataTypes.STRING });
- User.hasOne(Task);
- await User.sync({ force: true });
- await Task.sync({ force: true });
- await this.queryInterface.addColumn('Tasks', 'bar', DataTypes.INTEGER);
- let refs = await this.queryInterface.showConstraints(Task, {
- constraintType: 'FOREIGN KEY',
- });
- expect(refs.length).to.equal(1, 'should keep foreign key after adding column');
- expect(refs[0].columnNames).to.deep.equal(['userId']);
- expect(refs[0].referencedTableName).to.equal('Users');
- expect(refs[0].referencedColumnNames).to.deep.equal(['id']);
- await this.queryInterface.changeColumn('Tasks', 'bar', DataTypes.STRING);
- refs = await this.queryInterface.showConstraints(Task, { constraintType: 'FOREIGN KEY' });
- expect(refs.length).to.equal(1, 'should keep foreign key after changing column');
- expect(refs[0].columnNames).to.deep.equal(['userId']);
- expect(refs[0].referencedTableName).to.equal('Users');
- expect(refs[0].referencedColumnNames).to.deep.equal(['id']);
- await this.queryInterface.renameColumn('Tasks', 'bar', 'foo');
- refs = await this.queryInterface.showConstraints(Task, { constraintType: 'FOREIGN KEY' });
- expect(refs.length).to.equal(1, 'should keep foreign key after renaming column');
- expect(refs[0].columnNames).to.deep.equal(['userId']);
- expect(refs[0].referencedTableName).to.equal('Users');
- expect(refs[0].referencedColumnNames).to.deep.equal(['id']);
- });
- it('should retain ON UPDATE and ON DELETE constraints after a column is changed', async function () {
- await this.queryInterface.createTable('level', {
- id: {
- type: DataTypes.INTEGER,
- primaryKey: true,
- autoIncrement: true,
- },
- name: {
- type: DataTypes.STRING,
- allowNull: false,
- },
- });
- await this.queryInterface.createTable('users', {
- id: {
- type: DataTypes.INTEGER,
- primaryKey: true,
- autoIncrement: true,
- },
- name: {
- type: DataTypes.STRING,
- allowNull: true,
- },
- level_id: {
- type: DataTypes.INTEGER,
- allowNull: false,
- references: {
- key: 'id',
- table: 'level',
- },
- onDelete: 'CASCADE',
- onUpdate: 'CASCADE',
- },
- });
- await this.queryInterface.changeColumn('users', 'name', {
- type: DataTypes.STRING,
- allowNull: false,
- });
- await this.queryInterface.changeColumn('users', 'level_id', {
- type: DataTypes.INTEGER,
- allowNull: true,
- references: {
- key: 'id',
- table: 'level',
- },
- onDelete: 'CASCADE',
- onUpdate: 'CASCADE',
- });
- const constraints = await this.queryInterface.showConstraints('users');
- const foreignKey = constraints.find(
- constraint => constraint.constraintType === 'FOREIGN KEY',
- );
- expect(foreignKey).to.not.be.undefined;
- expect(foreignKey).to.have.property('deleteAction', 'CASCADE');
- expect(foreignKey).to.have.property('updateAction', 'CASCADE');
- });
- it('should change columns with foreign key constraints without data loss', async function () {
- await this.queryInterface.createTable('users', {
- id: {
- type: DataTypes.INTEGER,
- primaryKey: true,
- autoIncrement: true,
- },
- name: {
- type: DataTypes.INTEGER,
- allowNull: true,
- },
- level_id: {
- type: DataTypes.INTEGER,
- allowNull: false,
- references: {
- key: 'id',
- table: 'level',
- },
- onDelete: 'CASCADE',
- onUpdate: 'CASCADE',
- },
- });
- await this.queryInterface.createTable('level', {
- id: {
- type: DataTypes.INTEGER,
- primaryKey: true,
- autoIncrement: true,
- },
- name: {
- type: DataTypes.STRING,
- allowNull: false,
- },
- });
- const levels = [
- {
- id: 1,
- name: 'L1',
- },
- {
- id: 2,
- name: 'L2',
- },
- {
- id: 3,
- name: 'L3',
- },
- ];
- const users = [
- {
- name: 'Morpheus',
- level_id: 2,
- },
- {
- name: 'Neo',
- level_id: 1,
- },
- ];
- await Promise.all([
- this.queryInterface.bulkInsert('level', levels),
- this.queryInterface.bulkInsert('users', users),
- ]);
- await this.queryInterface.changeColumn('level', 'name', {
- type: DataTypes.STRING,
- allowNull: true,
- });
- const userRows = await this.queryInterface.sequelize.query('SELECT * from users;', {
- type: 'SELECT',
- });
- expect(userRows).to.have.length(users.length, 'user records should be unaffected');
- });
- }
- });
- });
|