question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

onDelete and sync() depends on order of associations

See original GitHub issue

This seems like it may be related to some issues already reported, but I couldn’t quite find it. Apologies if it’s a duplicate.

What you are doing?

Here are two minimal models, and two orders of adding associations between them.

const Sequelize = require("sequelize");
const sequelize = new Sequelize(/*...*/);

let a = sequelize.define("a", {
  id: { type: Sequelize.INTEGER, allowNull: false, primaryKey: true, autoIncrement: true },
}, {freezeTableName: true, timestamps: false});
let b = sequelize.define("b", {
  id: { type: Sequelize.INTEGER, allowNull: false, primaryKey: true, autoIncrement: true },
  aId: { type: Sequelize.INTEGER, allowNull: false }
}, {freezeTableName: true, timestamps: false});

if (process.argv[2] == "1") {
  a.hasMany(b);
  b.belongsTo(a);
} else {
  b.belongsTo(a);
  a.hasMany(b);
}
sequelize.sync({force: true, logging: console.log}).then(() => process.exit(0));```

What do you expect to happen?

The created foreign key probably shouldn’t depend on whether we’re in case 1 or 2.

What is actually happening?

$ node test.js 1 | grep CREATE
Executing (default): CREATE TABLE IF NOT EXISTS "a" ("id"   SERIAL , PRIMARY KEY ("id"));
Executing (default): CREATE TABLE IF NOT EXISTS "b" ("id"   SERIAL , "aId" INTEGER NOT NULL 
REFERENCES "a" ("id") ON DELETE CASCADE ON UPDATE CASCADE, PRIMARY KEY ("id"));

$ node test.js 2 | grep CREATE
Executing (default): CREATE TABLE IF NOT EXISTS "a" ("id"   SERIAL , PRIMARY KEY ("id"));
Executing (default): CREATE TABLE IF NOT EXISTS "b" ("id"   SERIAL , "aId" INTEGER NOT NULL 
REFERENCES "a" ("id") ON DELETE NO ACTION ON UPDATE CASCADE, PRIMARY KEY ("id"));

Note that the foreign key has ON DELETE CASCADE in case 1, but ON DELETE NO ACTION in case 2. I think the problem is that hasMany (and hasOne?) default to onDelete: "CASCADE", while belongsTo (and belongsToMany?) default to onDelete: "NO ACTION".

Dialect: postgres Database version: 9.3.14 Sequelize version: 3.24.2

Issue Analytics

  • State:open
  • Created 7 years ago
  • Comments:8 (1 by maintainers)

github_iconTop GitHub Comments

2reactions
ryan-harriscommented, Apr 21, 2020

I believe the problem is that belongTo’s default value of ON DELETE in the code is SET NULL or NO ACTION. The documentation clearly states that for 1:1 and 1:m associations the default value for delete should be SET NULL or CASCADE (depending if the foreign key allows null). The other 1:1 and 1:m associations (hasOne, hasMany) have the correct default value for ON DELETE.

The proper solution might also involve not setting an association’s options to the defaults until sync() is called, so that with a pair of associations, the 2nd association evaluated can overwrite those options if they weren’t explicitly defined by the user on the 1st association. That way I don’t have to define all the same options on both associations of a pair, not knowing which model/association would get evaluated first (using ‘sequelize init:models’ the index.js loads the models and their associations in an ordering that isn’t guaranteed, so right now I have to define the options on both Models to be safe)

1reaction
ajones513commented, Feb 25, 2018

I’ve hit this too, my reproduction case FWIW:

const Sequelize = require('sequelize');
const sequelize = new Sequelize('db', 'user', 'password', {
  host: 'localhost',
  dialect: 'mysql',
  operatorsAliases: false
});

const a = sequelize.define('a');
const b = sequelize.define('b');
const c = sequelize.define('c');

// Gives a.bId 'ON DELETE CASCADE'
b.hasMany(a, { foreignKey: { allowNull: false } });
a.belongsTo(b, { foreignKey: { allowNull: false } });

// Gives c.bId 'ON DELETE NO ACTION'
c.belongsTo(b, { foreignKey: { allowNull: false } });
b.hasMany(c, { foreignKey: { allowNull: false } });

sequelize.sync({ force: true });

Dialect: mysql Database version: 5.7.21 Sequelize version: 4.34.0

Read more comments on GitHub >

github_iconTop Results From Across the Web

Sequelize.js onDelete: 'cascade' is not deleting records ...
I had to put onDelete: 'CASCADE' not only on the association definition, but as well in the migration file. // in models/user.js User.associate ......
Read more >
Associations - Sequelize
The order in which the association is defined is relevant. ... This way, calling Bar.sync() after the above will yield the following SQL...
Read more >
Associations - Manual | Sequelize
Cyclic dependencies & Disabling constraints. Adding constraints between tables means that tables must be created in the database in a certain order, when...
Read more >
Working with Associations - Doctrine Object Relational ...
Changes to associations in your code are not synchronized to the database directly, only when calling EntityManager#flush() .
Read more >
Loose foreign keys - GitLab Docs
The LooseForeignKeys::CleanupWorker has its database query builder which depends on Arel . The feature doesn't reference any application-specific ActiveRecord ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found