123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715 |
- 'use strict';
- const { expect } = require('chai');
- const { DataTypes, Deferrable, Model } = require('@sequelize/core');
- const { getTestDialect, getTestDialectTeaser, sequelize } = require('../support');
- const dialect = getTestDialect();
- describe(getTestDialectTeaser('Model.sync & Sequelize#sync'), () => {
- it('removes a column if it exists in the databases schema but not the model', async () => {
- const User = sequelize.define('testSync', {
- name: DataTypes.STRING,
- age: DataTypes.INTEGER,
- badgeNumber: { type: DataTypes.INTEGER, columnName: 'badge_number' },
- });
- await sequelize.sync();
- const descr1 = await User.describe();
- expect(descr1).to.have.ownProperty('name');
- expect(descr1).to.have.ownProperty('age');
- expect(descr1).to.have.ownProperty('badge_number');
- sequelize.define('testSync', {
- name: DataTypes.STRING,
- });
- await sequelize.sync({ alter: true });
- const descr2 = await User.describe();
- expect(descr2).to.have.ownProperty('name');
- expect(descr2).to.not.have.ownProperty('age');
- expect(descr2).to.not.have.ownProperty('badge_number');
- });
- it('adds a column if it exists in the model but not the database', async () => {
- const testSync = sequelize.define('testSync', {
- name: DataTypes.STRING,
- });
- await sequelize.sync();
- const descr1 = await testSync.describe();
- expect(descr1).to.have.ownProperty('name');
- await sequelize.define('testSync', {
- name: DataTypes.STRING,
- age: DataTypes.INTEGER,
- height: { type: DataTypes.INTEGER, columnName: 'height_cm' },
- });
- await sequelize.sync({ alter: true });
- const descr2 = await testSync.describe();
- expect(descr2).to.have.ownProperty('name');
- expect(descr2).to.have.ownProperty('age');
- expect(descr2).to.have.ownProperty('height_cm');
- expect(descr2).not.to.have.ownProperty('height');
- });
- it('does not remove columns if drop is set to false in alter configuration', async () => {
- const testSync = sequelize.define('testSync', {
- name: DataTypes.STRING,
- age: DataTypes.INTEGER,
- });
- await sequelize.sync();
- await sequelize.define('testSync', {
- name: DataTypes.STRING,
- });
- await sequelize.sync({ alter: { drop: false } });
- const data = await testSync.describe();
- expect(data).to.have.ownProperty('name');
- expect(data).to.have.ownProperty('age');
- });
- it('removes columns if drop is set to true in alter configuration', async () => {
- const testSync = sequelize.define('testSync', {
- name: DataTypes.STRING,
- age: DataTypes.INTEGER,
- });
- await sequelize.sync();
- await sequelize.define('testSync', {
- name: DataTypes.STRING,
- });
- await sequelize.sync({ alter: { drop: true } });
- const data = await testSync.describe();
- expect(data).to.have.ownProperty('name');
- expect(data).not.to.have.ownProperty('age');
- });
- it('alters a column using the correct column name (#9515)', async () => {
- const testSync = sequelize.define('testSync', {
- name: DataTypes.STRING,
- });
- await sequelize.sync();
- await sequelize.define('testSync', {
- name: DataTypes.STRING,
- badgeNumber: { type: DataTypes.INTEGER, columnName: 'badge_number' },
- });
- await sequelize.sync({ alter: true });
- const data = await testSync.describe();
- expect(data).to.have.ownProperty('badge_number');
- expect(data).not.to.have.ownProperty('badgeNumber');
- });
- // IBM i can't alter INTEGER -> STRING
- if (dialect !== 'ibmi') {
- it('changes a column if it exists in the model but is different in the database', async () => {
- const testSync = sequelize.define('testSync', {
- name: DataTypes.STRING,
- age: DataTypes.INTEGER,
- });
- await sequelize.sync();
- await sequelize.define('testSync', {
- name: DataTypes.STRING,
- age: DataTypes.STRING,
- });
- await sequelize.sync({ alter: true });
- const data = await testSync.describe();
- expect(data).to.have.ownProperty('age');
- if (dialect === 'sqlite3') {
- // sqlite3 does not have a text type with a configurable max width. It uses TEXT which is unlimited.
- expect(data.age.type).to.have.string('TEXT');
- } else {
- expect(data.age.type).to.have.string('VAR'); // CHARACTER VARYING, VARCHAR(n)
- }
- });
- }
- it('does not alter table if data type does not change', async () => {
- const testSync = sequelize.define('testSync', {
- name: DataTypes.STRING,
- age: DataTypes.STRING,
- });
- await sequelize.sync();
- await testSync.create({ name: 'test', age: '1' });
- await sequelize.sync({ alter: true });
- const data = await testSync.findOne();
- expect(data.name).to.eql('test');
- expect(data.age).to.eql('1');
- });
- it('should properly alter tables when there are foreign keys', async () => {
- const foreignKeyTestSyncA = sequelize.define('foreignKeyTestSyncA', {
- dummy: DataTypes.STRING,
- });
- const foreignKeyTestSyncB = sequelize.define('foreignKeyTestSyncB', {
- dummy: DataTypes.STRING,
- });
- foreignKeyTestSyncA.hasMany(foreignKeyTestSyncB);
- foreignKeyTestSyncB.belongsTo(foreignKeyTestSyncA);
- await sequelize.sync({ alter: true });
- await sequelize.sync({ alter: true });
- });
- it('creates one unique index for unique:true column', async () => {
- const User = sequelize.define('testSync', {
- email: {
- type: DataTypes.STRING,
- unique: true,
- },
- });
- await User.sync({ force: true });
- const syncResults = await getNonPrimaryIndexes(User);
- expect(syncResults).to.have.length(1);
- expect(syncResults[0].name).to.eq('test_syncs_email_unique');
- expect(getIndexFields(syncResults[0])).to.deep.eq(['email']);
- await User.sync({ alter: true });
- const alterResults = await getNonPrimaryIndexes(User);
- expect(alterResults).to.deep.eq(
- syncResults,
- '"alter" should not create new indexes if they already exist.',
- );
- });
- it('creates one unique index per unique:true columns, and per entry in options.indexes', async () => {
- const User = sequelize.define(
- 'testSync',
- {
- email: {
- type: DataTypes.STRING,
- unique: true,
- },
- phone: {
- type: DataTypes.STRING,
- unique: true,
- },
- },
- {
- timestamps: false,
- indexes: [{ name: 'wow_my_index', fields: ['email', 'phone'], unique: true }],
- },
- );
- await User.sync({ force: true });
- const syncResults = await getNonPrimaryIndexes(User);
- syncResults.sort((a, b) => a.name.localeCompare(b.name));
- expect(syncResults).to.have.length(3);
- expect(syncResults[0].name).to.eq('test_syncs_email_unique');
- expect(getIndexFields(syncResults[0])).to.deep.eq(['email']);
- expect(syncResults[0].unique).to.eq(true, 'index should be unique');
- expect(syncResults[1].name).to.eq('test_syncs_phone_unique');
- expect(getIndexFields(syncResults[1])).to.deep.eq(['phone']);
- expect(syncResults[1].unique).to.eq(true, 'index should be unique');
- expect(syncResults[2].name).to.eq('wow_my_index');
- expect(getIndexFields(syncResults[2])).to.deep.eq(['email', 'phone']);
- expect(syncResults[2].unique).to.eq(true, 'index should be unique');
- await User.sync({ alter: true });
- const alterResults = await getNonPrimaryIndexes(User);
- expect(syncResults).to.deep.eq(
- alterResults,
- '"alter" should not create new indexes if they already exist.',
- );
- });
- it('creates one unique index per unique:name column (1 column)', async () => {
- const User = sequelize.define('testSync', {
- email: {
- type: DataTypes.STRING,
- unique: 'wow_my_index',
- },
- });
- await User.sync({ force: true });
- const syncResults = await getNonPrimaryIndexes(User);
- expect(syncResults).to.have.length(1);
- expect(syncResults[0].name).to.eq('wow_my_index');
- expect(getIndexFields(syncResults[0])).to.deep.eq(['email']);
- expect(syncResults[0].unique).to.eq(true, 'index should be unique');
- await User.sync({ alter: true });
- const alterResults = await getNonPrimaryIndexes(User);
- expect(syncResults).to.deep.eq(
- alterResults,
- '"alter" should not create new indexes if they already exist.',
- );
- });
- it('creates one unique index per unique:name column (multiple columns)', async () => {
- const User = sequelize.define('testSync', {
- email: {
- type: DataTypes.STRING,
- unique: 'wow_my_index',
- },
- phone: {
- type: DataTypes.STRING,
- unique: 'wow_my_index',
- },
- });
- await User.sync({ force: true });
- const syncResults = await getNonPrimaryIndexes(User);
- expect(syncResults).to.have.length(1);
- expect(syncResults[0].name).to.eq('wow_my_index');
- expect(getIndexFields(syncResults[0])).to.deep.eq(['email', 'phone']);
- expect(syncResults[0].unique).to.eq(true, 'index should be unique');
- await User.sync({ alter: true });
- const alterResults = await getNonPrimaryIndexes(User);
- expect(syncResults).to.deep.eq(
- alterResults,
- '"alter" should not create new indexes if they already exist.',
- );
- });
- it('throws if a name collision occurs between two indexes', async () => {
- expect(() => {
- sequelize.define(
- 'testSync',
- {
- email: {
- type: DataTypes.STRING,
- unique: true,
- },
- },
- {
- timestamps: false,
- indexes: [{ fields: ['email'], unique: true }],
- },
- );
- }).to.throwWithCause('Sequelize tried to give the name "test_syncs_email_unique" to index');
- });
- it('adds missing unique indexes to existing tables (unique attribute option)', async () => {
- const User1 = sequelize.define(
- 'User',
- {
- email: {
- type: DataTypes.STRING,
- },
- },
- { timestamps: false },
- );
- // create without the unique index
- await User1.sync({ force: true });
- // replace model (to emulate code changes)
- const User2 = sequelize.define(
- 'User',
- {
- email: {
- type: DataTypes.STRING,
- unique: true,
- },
- },
- { timestamps: false },
- );
- const out1 = await getNonPrimaryIndexes(User1);
- expect(out1).to.have.length(0);
- // alter to add the unique index
- await User2.sync({ alter: true });
- const out2 = await getNonPrimaryIndexes(User1);
- expect(out2).to.have.length(1);
- expect(out2[0].name).to.eq('users_email_unique');
- expect(getIndexFields(out2[0])).to.deep.eq(['email']);
- expect(out2[0].unique).to.eq(true, 'index should be unique');
- });
- it('adds missing unique indexes to existing tables (index option)', async () => {
- const User1 = sequelize.define(
- 'User',
- {
- email: {
- type: DataTypes.STRING,
- },
- },
- { timestamps: false },
- );
- // create without the unique index
- await User1.sync({ force: true });
- // replace model (to emulate code changes)
- const User2 = sequelize.define(
- 'User',
- {
- email: {
- type: DataTypes.STRING,
- },
- },
- {
- timestamps: false,
- indexes: [{ fields: ['email'], unique: true }],
- },
- );
- const out1 = await getNonPrimaryIndexes(User1);
- expect(out1).to.have.length(0);
- // alter to add the unique index
- await User2.sync({ alter: true });
- const out2 = await getNonPrimaryIndexes(User1);
- expect(out2).to.have.length(1);
- expect(out2[0].name).to.eq('users_email_unique');
- expect(getIndexFields(out2[0])).to.deep.eq(['email']);
- expect(out2[0].unique).to.eq(true, 'index should be unique');
- });
- it('adds missing non-unique indexes to existing tables (index option)', async () => {
- const User1 = sequelize.define(
- 'User',
- {
- email: {
- type: DataTypes.STRING,
- },
- },
- { timestamps: false },
- );
- // create without the unique index
- await User1.sync({ force: true });
- // replace model (to emulate code changes)
- const User2 = sequelize.define(
- 'User',
- {
- email: {
- type: DataTypes.STRING,
- },
- },
- {
- timestamps: false,
- indexes: [{ fields: ['email'] }],
- },
- );
- const out1 = await getNonPrimaryIndexes(User1);
- expect(out1).to.have.length(0);
- // alter to add the unique index
- await User2.sync({ alter: true });
- const out2 = await getNonPrimaryIndexes(User1);
- expect(out2).to.have.length(1);
- expect(out2[0].name).to.eq('users_email');
- expect(getIndexFields(out2[0])).to.deep.eq(['email']);
- expect(out2[0].unique).to.eq(false, 'index should not be unique');
- });
- it('adds missing unique columns to existing tables', async () => {
- const User1 = sequelize.define('User', {}, { timestamps: false });
- // create without the unique index
- await User1.sync({ force: true });
- await User1.create({ id: 1 });
- const out1 = await getNonPrimaryIndexes(User1);
- expect(out1).to.have.length(0);
- // replace model (to emulate code changes)
- const User2 = sequelize.define(
- 'User',
- {
- email: {
- type: DataTypes.STRING,
- unique: true,
- },
- },
- {
- timestamps: false,
- },
- );
- // alter to add the unique index
- await User2.sync({ alter: true });
- // db2 had a bug which re-created the table in some circumstances.
- // this ensures the table is not recreated, but altered.
- const existingData = await User2.findAll();
- expect(existingData).to.have.length(1);
- const out2 = await getNonPrimaryIndexes(User1);
- expect(out2).to.have.length(1);
- expect(out2[0].name).to.eq('users_email_unique');
- expect(getIndexFields(out2[0])).to.deep.eq(['email']);
- expect(out2[0].unique).to.eq(true, 'index should not be unique');
- });
- const SCHEMA_ONE = 'schema_one';
- const SCHEMA_TWO = 'schema_two';
- if (sequelize.dialect.supports.schemas) {
- it('can create two identically named indexes in different schemas', async () => {
- await Promise.all([sequelize.createSchema(SCHEMA_ONE), sequelize.createSchema(SCHEMA_TWO)]);
- const User = sequelize.define(
- 'User1',
- {
- name: DataTypes.STRING,
- },
- {
- schema: SCHEMA_ONE,
- indexes: [
- {
- name: 'test_slug_idx',
- fields: ['name'],
- },
- ],
- },
- );
- const Task = sequelize.define(
- 'Task2',
- {
- name: DataTypes.STRING,
- },
- {
- schema: SCHEMA_TWO,
- indexes: [
- {
- name: 'test_slug_idx',
- fields: ['name'],
- },
- ],
- },
- );
- await User.sync({ force: true });
- await Task.sync({ force: true });
- const [userIndexes, taskIndexes] = await Promise.all([
- getNonPrimaryIndexes(User),
- getNonPrimaryIndexes(Task),
- ]);
- expect(userIndexes).to.have.length(1);
- expect(taskIndexes).to.have.length(1);
- expect(userIndexes[0].name).to.eq('test_slug_idx');
- expect(taskIndexes[0].name).to.eq('test_slug_idx');
- });
- it('supports creating two identically named tables in different schemas', async () => {
- await sequelize.queryInterface.createSchema('custom_schema');
- const Model1 = sequelize.define(
- 'A1',
- {},
- { schema: 'custom_schema', tableName: 'a', timestamps: false },
- );
- const Model2 = sequelize.define('A2', {}, { tableName: 'a', timestamps: false });
- await sequelize.sync({ force: true });
- await Model1.create({ id: 1 });
- await Model2.create({ id: 2 });
- });
- }
- it('defaults to schema provided to sync() for references #11276', async function () {
- // TODO: this should work with MSSQL / MariaDB too
- if (!dialect !== 'postgres') {
- return;
- }
- await Promise.all([sequelize.createSchema(SCHEMA_ONE), sequelize.createSchema(SCHEMA_TWO)]);
- const User = this.sequelize.define('UserXYZ', {
- uid: {
- type: DataTypes.INTEGER,
- primaryKey: true,
- autoIncrement: true,
- allowNull: false,
- },
- });
- const Task = this.sequelize.define('TaskXYZ', {});
- Task.belongsTo(User);
- // TODO: do we really want to keep this? Shouldn't model schemas be defined and fixed?
- await User.sync({ force: true, schema: SCHEMA_ONE });
- await Task.sync({ force: true, schema: SCHEMA_ONE });
- const user0 = await User.withSchema(SCHEMA_ONE).create({});
- const task = await Task.withSchema(SCHEMA_ONE).create({});
- await task.setUserXYZ(user0);
- // TODO: do we really want to keep this? Shouldn't model schemas be defined and fixed?
- const user = await task.getUserXYZ({ schema: SCHEMA_ONE });
- expect(user).to.be.ok;
- });
- it('supports creating tables with cyclic associations', async () => {
- const A = sequelize.define('A', {}, { timestamps: false });
- const B = sequelize.define('B', {}, { timestamps: false });
- // mssql refuses cyclic references unless ON DELETE and ON UPDATE is set to NO ACTION
- const mssqlConstraints =
- dialect === 'mssql' ? { onDelete: 'NO ACTION', onUpdate: 'NO ACTION' } : null;
- // These models both have a foreign key that references the other model.
- // Sequelize should be able to create them.
- A.belongsTo(B, { foreignKey: { allowNull: false, ...mssqlConstraints } });
- B.belongsTo(A, { foreignKey: { allowNull: false, ...mssqlConstraints } });
- await sequelize.sync();
- const [aFks, bFks] = await Promise.all([
- sequelize.queryInterface.showConstraints(A, { constraintType: 'FOREIGN KEY' }),
- sequelize.queryInterface.showConstraints(B, { constraintType: 'FOREIGN KEY' }),
- ]);
- expect(aFks.length).to.eq(1);
- expect(aFks[0].referencedTableName).to.eq('Bs');
- expect(aFks[0].referencedColumnNames).to.deep.eq(['id']);
- expect(aFks[0].columnNames).to.deep.eq(['bId']);
- expect(bFks.length).to.eq(1);
- expect(bFks[0].referencedTableName).to.eq('As');
- expect(bFks[0].referencedColumnNames).to.deep.eq(['id']);
- expect(bFks[0].columnNames).to.deep.eq(['aId']);
- });
- // TODO: sqlite3's foreign_key_list pragma does not return the DEFERRABLE status of the column
- // so sync({ alter: true }) cannot know whether the column must be updated.
- // so for now, deferrable constraints is disabled for sqlite3 (as it's only used in tests)
- if (sequelize.dialect.supports.constraints.deferrable) {
- it('updates the deferrable property of a foreign key', async () => {
- const A = sequelize.define('A', {
- BId: {
- type: DataTypes.INTEGER,
- references: {
- deferrable: Deferrable.INITIALLY_IMMEDIATE,
- },
- },
- });
- const B = sequelize.define('B');
- A.belongsTo(B);
- await sequelize.sync();
- const aFks = await sequelize.queryInterface.showConstraints(A, {
- constraintType: 'FOREIGN KEY',
- });
- expect(aFks).to.have.length(1);
- expect(aFks[0].deferrable).to.eq(Deferrable.INITIALLY_IMMEDIATE);
- A.modelDefinition.rawAttributes.bId.references.deferrable = Deferrable.INITIALLY_DEFERRED;
- A.modelDefinition.refreshAttributes();
- await sequelize.sync({ alter: true });
- const aFks2 = await sequelize.queryInterface.showConstraints(A, {
- constraintType: 'FOREIGN KEY',
- });
- expect(aFks2).to.have.length(1);
- expect(aFks2[0].deferrable).to.eq(Deferrable.INITIALLY_DEFERRED);
- });
- }
- if (sequelize.dialect.supports.schemas) {
- it('should not recreate a foreign key if it already exists when { alter: true } is used with a custom schema', async () => {
- const schema = 'test';
- await sequelize.createSchema(schema);
- const User = sequelize.define('User', {}, { schema });
- const BelongsToUser = sequelize.define('BelongsToUser', {}, { schema });
- BelongsToUser.belongsTo(User, { foreignKey: { targetKey: 'id', allowNull: false } });
- await sequelize.sync({ alter: true });
- await sequelize.sync({ alter: true });
- const results = await sequelize.queryInterface.showConstraints(BelongsToUser, {
- constraintType: 'FOREIGN KEY',
- });
- expect(results).to.have.length(1);
- });
- it('should not recreate a foreign key if it already exists when { alter: true } is used with a custom schema (reference attribute is a Model)', async () => {
- const schema = 'test';
- await sequelize.createSchema(schema);
- class User extends Model {}
- class BelongsToUser extends Model {}
- User.init({}, { sequelize, schema });
- BelongsToUser.init(
- {
- user_id: {
- type: DataTypes.INTEGER,
- references: {
- model: User,
- key: 'id',
- },
- },
- },
- { sequelize, schema },
- );
- await sequelize.sync({ alter: true });
- await sequelize.sync({ alter: true });
- const results = await sequelize.queryInterface.showConstraints(BelongsToUser, {
- constraintType: 'FOREIGN KEY',
- });
- expect(results).to.have.length(1);
- });
- }
- // TODO add support for db2 and mssql dialects
- if (dialect !== 'db2' && dialect !== 'mssql') {
- it('does not recreate existing enums (#7649)', async () => {
- sequelize.define('Media', {
- type: DataTypes.ENUM(['video', 'audio']),
- });
- await sequelize.sync({ alter: true });
- sequelize.define('Media', {
- type: DataTypes.ENUM(['image', 'video', 'audio']),
- });
- await sequelize.sync({ alter: true });
- });
- }
- });
- async function getNonPrimaryIndexes(model) {
- return (await sequelize.queryInterface.showIndex(model.table))
- .filter(r => !r.primary)
- .sort((a, b) => a.name.localeCompare(b.name));
- }
- function getIndexFields(index) {
- return index.fields.map(field => field.attribute).sort();
- }
|