Sequelize ON DELETE CASCADE - postgresql

I have two tables, house, renter.
A house has many renters, a renter only has one house. (house <->> renter).
In Renter:
Renter.associate = function (models) {
models.renter.belongsTo(models.house, { foreignKey: { name: 'house_id', allowNull: false } } );
};
Or:
Renter.associate = function (models) {
models.renter.belongsTo(models.house, { foreignKey: { name: 'house_id', allowNull: false, onDelete:'CASCADE' } } );
};
Both ways fail as explained below.
In House:
House.associate = function (models) {
models.house.hasMany(models.renter, { foreignKey: 'house_id'});
}
I also tried by omitting the hasMany association and only having the belongsTo part on renter...
when I delete a hosue:
await house1.destroy();
I get the following error:
SequelizeForeignKeyConstraintError: update or delete on table "house" violates foreign key constraint "renter_house_id_fkey" on table "renter"
When I look at the db logs, during the renter table creation it has this:
"house_id" INTEGER NOT NULL REFERENCES "house" ("id") ON DELETE NO ACTION ON UPDATE CASCADE
I need the DB to delete all renters when I delete a house via DB constraint ON DELETE CASCADE. How can I fix this? As far as the table def goes, I can see the ON DELETE NO ACTION being a problem, and I am not sure how to get sequelize to put CASCADE not NO ACTION.

Related

knex.js migrations create tables with relationship between each other fails

I've looked all over the place, there are quite a few examples, but nothing complete or working.
Use case:
Simple database structure, a few tables and some relationships. Set up node, knex and pg - in a docker container.
All works well.
Created the migration file for the first table (table A) - ok.
Added the second table (table B) and a 1:n relationship from table B to A. All good.
Added a 1:n relationship from table A to table B. And the script errored out.
table info:
exports.up = function(knex) {
return knex.schema
.createTable('user', t => {
t.uuid('user_id').primary()
t.string('name', 100).notNullable()
t.string('surname', 100)
t.string('email').notNullable().unique()
t.string('password')
t
.boolean('email_confirmed')
.notNullable()
.defaultTo(false)
t
.datetime('last_login', { precision: 6 })
.defaultTo(knex.fn.now(6))
t.string('language')
t.string('newsletter')
t.timestamps(true, true)
t
.uuid('company_id')
.references('company_id')
.inTable('company')
})
.createTable('company', t => {
t.uuid('company_id').primary()
t.string('address_id')
t.string('name', 100).notNullable()
t.string('phone')
t.timestamps(true, true)
t
.uuid('owner_user_id')
.references('user_id')
.inTable('user')
})
}
error:
migration failed with error: alter table "user" add constraint
"user_company_uuid_foreign" foreign key ("company_uuid") references
"company" ("company_id") - relation "company" does not exist
I'd say it tries to create a table and add the foreign key before creating the second table (which the FK references).
Any idea on how to solve this.
it is not a m:n relationship really. A company can and should only have 1 owner. A user has to belong to a company, otherwise it can't exist. If this is not solvable I can have the user.company_id field as a simple string w/o any relationship.
Having a different table for user_companies would be overkill.
Thanks!
You are correct that it is trying to create the reference before the table it is referencing exists. The easiest way would probably be to simply delay the creation of the foreign key until after the companies table has been created. i.e.
exports.up = async function(knex) {
await knex.schema.createTable('user', t => {
t.uuid('user_id').primary()
t.string('name', 100).notNullable()
t.string('surname', 100)
t.string('email').notNullable().unique()
t.string('password')
t.boolean('email_confirmed')
.notNullable()
.defaultTo(false)
t.datetime('last_login', { precision: 6 })
.defaultTo(knex.fn.now(6))
t.string('language')
t.string('newsletter')
t.timestamps(true, true)
t.uuid('company_id')
});
await knex.schema.createTable('company', t => {
t.uuid('company_id').primary()
t.string('address_id')
t.string('name', 100).notNullable()
t.string('phone')
t.timestamps(true, true)
t.uuid('owner_user_id')
.references('user_id')
.inTable('user')
});
await knex.schema.table('user', t => {
t.foreign('company_id')
.references('company_id')
.inTable('company')
});
}

knex / postgresql updating a foreign key to null

I am using knex.js with postgresql
So I have a table with a nullable foreign key.
shortened version:
exports.up = function (knex, Promise) {
return knex.schema.createTable('Note', function (table) {
table.string('id').primary()
table
.string('sourceId')
.references('id')
.inTable('Source')
.onDelete('SET NULL')
.index()
})
}
exports.down = function (knex, Promise) {
return knex.schema.dropTable('Note')
}
I am able to create a Note with or without a sourceId. However, if I create a Note with a sourceId and then update it to set the sourceId to NULL, the update does not work. I do not get an error message, but the foreign key is not removed.
For example if I create a Note with:
{
id: '123',
sourceId: '456'
}
and then try to update it:
const result = await Note.query().updateAndFetchById(id, {
id: '123',
sourceId: null
})
The result I get is :
Note {
id: '123',
sourceId: '456'
}
I have no problem if I try to update other nullable values to null (as long as they are not foreign keys) and I can update the sourceId to a different source's id.
If I try to update a not nullable foreign key to null, I get an error. But in the above case, I get no error. It just doesn't update.
Any idea what might be going on here?

Change primary key and its (foreign) references in existing table - Knex.js / Postgres

My Postgres DB has 2 tables, with thousands of rows each, that were initially created with the following migration:
exports.up = async function(knex, Promise) {
// users
await knex.schema.createTable('users', table => {
table.increments('id');
table.timestamps(false, true);
table.text('uid').notNullable().unique(),
table.text('email').notNullable();
table.text('password');
table.text('first_name').notNullable();
table.text('last_name').notNullable();
table.text('subscription_id');
table.boolean('is_active').notNullable().defaultTo(true);
table.boolean('is_blocked').notNullable().defaultTo(false);
table.enum('role', ['member', 'admin', 'test_user']).notNullable().defaultTo('member');
});
await knex.schema.raw('create unique index users_lower_email_index on users (lower(email))');
// projects
await knex.schema.createTable('projects', table => {
table.increments('id');
table.timestamps(false, true);
table.text('name').notNullable();
table.integer('user_id').notNullable().references('users.id').onDelete('cascade');
table.text('data');
});
};
I need to change the foreign key on the projects table so that it references the uid column instead from the users table.
The constraints on the users table are:
I tried the following migration but I get the error:
migration failed with error: alter table "users" add column "uid" text - column "uid" of relation "users" already exists
My code:
exports.up = async function(knex, Promise) {
await knex.schema.alterTable('users', table => {
table.text('uid').primary('users_pkey');
})
await knex.schema.alterTable('projects', table => {
table.text('user_id').notNullable().references('users.uid').onDelete('cascade').alter();
});
};
I also tried table.text('uid').primary('users_pkey').alter(); but then I get:
migration failed with error: alter table "users" add constraint "users_pkey" primary key ("uid") - multiple primary keys for table "users" are not allowed
I will transfer all users in auth0 and I though its better if I use a UUID primary key for the users table.
Before you can change the primary key of users, you need to remove the existing one, then you should be able to drop and recreate the foreign key in projects:
exports.up = async (knex) => {
await knex.schema.alterTable('users', (table) => {
table.dropPrimary()
table.primary('uid')
})
await knex.schema.alterTable('projects', (table) => {
table.dropForeign('user_id')
table.foreign('user_id').references('users.uid').onDelete('cascade')
})
}

Sequelize upsert throws unique violation for schema with nullable unique column

I'm facing an unique constraint violation issue when doing an upsert, because the UPDATE query built by sequelize ignores the partial index constraint defined by the model (unless it doesn't matter). I'm new to node+sequelize so I might be missing something obvious, but I went through all the potential places for finding the appropriate answers, inclusive of the sequelize code, but I'm not able to find the answer I'm looking for. Really appreciate your help!
My current versions:
"pg": "7.9.0",
"sequelize": "5.21.3"
I have a model that consists of a primary key: id and two other unique indexes of which one of them is a nullable field.
module.exports.Entities = sequelize.define('entities', {
id: {type: Sequelize.UUID, defaultValue: Sequelize.UUIDV4, allowNull: false, primaryKey: true},
cId: {type: Sequelize.STRING, allowNull: false},
pId: {type: Sequelize.UUID, allowNull: false},
eKey: {type: Sequelize.INTEGER, allowNull: true}
}, {
indexes: [
{
name: 'unique_c_id_p_id',
fields: ['c_id', 'p_id'],
unique: true
},
{
name: 'unique_e_key',
fields: ['e_key'],
unique: true,
where: {
eKey: {
[Op.not]: null
}
}
}
]
})
and the table itself looks like below:
CREATE TABLE public.entities (
id UUID DEFAULT uuid_generate_v4 (),
c_id UUID NOT NULL,
p_id UUID NOT NULL,
e_key INTEGER DEFAULT NULL,
CONSTRAINT ENTITY_SERVICE_PKEY PRIMARY KEY (id),
CONSTRAINT unique_c_id_p_id UNIQUE (c_id, p_id)
);
CREATE UNIQUE INDEX unique_e_key ON public.entities (e_key) WHERE e_key IS NOT NULL;
The upsert method call looks like:
module.exports.upsert = async (Model, values) => Model.upsert(values, {returning: true})
I pass the above Entities model, and the below value as arguments to this function.
{
"id"="3169d4e2-8e2d-451e-8be0-40c0b28e2aa9",
"c_id"="00000000-0000-0000-0000-000000000000",
"p_id"="78bce392-4a15-4a8a-986b-c9398787345f",
"e_key"= null
}
Issue: SequelizeUniqueConstraintError
Sequelize tries to do an insert followed by an update query when we attempt to update an existing record using the upsert method.
The insert query shows a conflict, since the record exists already, and sequelize upsert call proceeds on to invoke the update query.
However, the query that it builds to UPDATE looks something like below:
"SQL statement UPDATE entities SET id='3169d4e2-8e2d-451e-8be0-40c0b28e2aa9',c_id='00000000-0000-0000-0000-000000000000',p_id='78bce392-4a15-4a8a-986b-c9398787345f',e_key=NULL
WHERE (id = '3169d4e2-8e2d-451e-8be0-40c0b28e2aa9'
OR e_key IS NULL
OR (c_id = '00000000-0000-0000-0000-000000000000' AND p_id = '78bce392-4a15-4a8a-986b-c9398787345f'))
RETURNING id\nPL/pgSQL function pg_temp_5.sequelize_upsert() line 1 at SQL statement"
Now, I do understand the reason why it's throwing the unique constraint violation, since in the above query's WHERE clause sequelize calls OR e_key IS NULL since e_key = null and that could potentially return more than 1 record, and the SET is trying to update the same value for all those records that were returned thereby violating the primaryKey constraints, unique constraints etc.
What I would like to understand is that:
Why does sequelize not exclude the e_key unique constraint based on the partial index defined given that it picks the WHERE clause attributes based on the constraints defined in the Model & it's indexes?
Is there anything that I could do to get past this issue?
Or, am I missing something obvious that I could fix and try?
Really appreciate you taking your time to read and respond. Thanks!

Sequelize Duplicate Key Constraint Violation

I am trying to add a many to many relationship through an explicitly created junction table using Sequelize and Postgresql.
The tables on either side of the relationship are associated like this:
Shop.belongsToMany(models.user, {through: 'visits' })
User.belongsToMany(models.shop, {through: 'visits' })
And the visits junction table primary key is defined like this:
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true // Automatically gets converted to SERIAL for postgres
}
When I try and insert into visits I get the following error:
ERROR: duplicate key value violates unique constraint "visits_shopId_userId_key"
DETAIL: Key ("shopId", "userId")=(1, 12) already exists.
After doing a pg_dump, I have tried to remove the composite key constraint by adding constraint: false to the models, but I still get the error.
(I have dropped the tables and resynced several times during the debugging process)
After digging around the Sequelize issues, it turns out that removing the constraint on the N:M composite key is an easy fix.
The through key can take an object with the unique: false property:
Shop.belongsToMany(models.user, {
through: {
model: 'visits',
unique: false
},
constraints: false
});