sequelize: sync() with new alter mode fails on second run when using CONSTRAINTS

What you are doing?

'use strict';

const Sequelize = require('sequelize');

//first connect to the database
const database = new Sequelize('database', 'user', 'password', {
    dialect: 'postgres',
    host: '127.0.0.1',
    port: 5432,

    pool: {
        max: 5,
        min: 0,
        idle: 10000
    },
});

return database.authenticate()
.then(() => {
    //success connecting,
    console.log('database connected');
    return database;
})
.then(database => {
    const containerModel = database.define('TestContainer', {
        id: {
            type: Sequelize.DataTypes.UUID,
            defaultValue: Sequelize.DataTypes.UUIDV4,
            primaryKey: true,
            allowNull: false
        },
    });

    const itemModel = database.define('TestItem', {
        id: {
            type: Sequelize.DataTypes.UUID,
            defaultValue: Sequelize.DataTypes.UUIDV4,
            primaryKey: true,
            allowNull: false
        },
    });

    const userModel = database.define('TestUser', {
        id: {
            type: Sequelize.DataTypes.UUID,
            defaultValue: Sequelize.DataTypes.UUIDV4,
            primaryKey: true,
            allowNull: false
        },
    });

    containerModel.belongsTo(userModel);
    itemModel.belongsTo(userModel);
    containerModel.belongsToMany(itemModel, {through: 'TestContainerItems', timestamps: false, onDelete: 'CASCADE'});

    return database.sync({
        alter: true,
        force: false,
    });
})
.catch(err => {
    //failed to connect
    console.error(err);
    process.exit(1);
});

What do you expect to happen?

Being able to run the example multiple times without fail

What is actually happening?

On second run, an error occurs:

SequelizeDatabaseError: constraint “TestUserId_foreign_idx” for relation “TestContainers” already exists

Dialect: postgres Database version: 9.6.2 Sequelize version: git master

About this issue

  • Original URL
  • State: open
  • Created 7 years ago
  • Reactions: 19
  • Comments: 44 (3 by maintainers)

Most upvoted comments

is there solution to this yet

Any updates on this?

No idea if this is safe, but needed a quick fix for testing:

function dropForeignKeyConstraints(database) {
    //this is a hack for dev only!
    //todo: check status of posted github issue, https://github.com/sequelize/sequelize/issues/7606
    const queryInterface = database.getQueryInterface();
    return queryInterface.showAllTables()
    .then(tableNames => {
        return Promise.all(tableNames.map(tableName => {
            return queryInterface.showConstraint(tableName)
            .then(constraints => {
                return Promise.all(constraints.map(constraint => {
                    if (constraint.constraintType === 'FOREIGN KEY') {
                        return queryInterface.removeConstraint(tableName, constraint.constraintName);
                    }
                }));
            });
        }));
    })
    .then(() => database);
}

Run before sync to drop all foreign keys. BRUTAL!

Workaround that I found: Move all the unique indexes definitions to second argument of define method

const Voucher = sequelize.define('Voucher', {
    code: {
      type: DataTypes.STRING
    },
    receiver_name: {
      type: DataTypes.STRING,
    },
  }, {
    indexes: [
      {
        unique: true,
        fields: ['code']
      }
    ]
  });

Don’t know why, but it works with sync({alter: true}) (still doesn’t work with unique:true property defined directly at column definition) checked with Sequelize version 4.37.8 and 4.42.0

This is a bit of a pain… is anyone working on a fix?

@The solution would be to check for existing constraint as a method exists to do it. As a quick fix but not a long term solution, I’ve written the following code in the query-interface file in the changeColumn method to check if a constraint already exists and it worked pretty well (starting line 556):

if (this.sequelize.options.dialect === 'sqlite') {
    // sqlite needs some special treatment as it cannot change a column
    return SQLiteQueryInterface.changeColumn.call(this, tableName, attributes, options);
} else {
    /************************************************************
    * HERE IS WHERE I CHECK FOR AN EXISTING CONSTRAINT
    ************************************************************/
    if (attributes[attributeName].references) {
        this.showConstraint(tableName, `${tableName}_${attributeName}_foreign_idx`).then((result) => {
            if (result.length === 0) {
                const query = this.QueryGenerator.attributesToSQL(attributes);
                const sql = this.QueryGenerator.changeColumnQuery(tableName, query);

                return this.sequelize.query(sql, options);
            }
        });
    } else {
        const query = this.QueryGenerator.attributesToSQL(attributes);
        const sql = this.QueryGenerator.changeColumnQuery(tableName, query);

        return this.sequelize.query(sql, options);
    }
}

It is not a long term solution as some code has been duplicated. I hope it helps

@sushantdhiman I don’t think it works for unique constraints 😕

Any news on this?

I had a similar problem. My solution was setting names to index. Probably the autogenerated name was too long. An autogenerated name is built from fields’s names. After setting the name all work fine

indexes: [
    {
        fields: ['barCode'],
        unique: true
    },
    {
        fields: [
    	    'productGroupId',
    	    'attributeValue_1',
    	    'attributeValue_2',
    	    'attributeValue_3'
        ],
        unique: true,
        name: 'products_product_group_id_attribute_values'
    }
];