knex / postgresql updating a foreign key to null - postgresql

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?

Related

GORM Error on insert or update on table violates foreign key constraint

I have the following set of GORM models, with 2 orders of one-to-many relations:
type Order struct {
ID string `gorm:"column:id"`
ClientID string `gorm:"primaryKey;column:client_id"`
Name string `gorm:"column:name"`
Albums []Album `gorm:"foreignKey:RequestClientID"`
}
type Album struct {
AlbumID string `gorm:"primaryKey;column:album_id"`
RequestClientID string `gorm:"foreignKey:ClientID;column:request_client_id"`
Pictures []Picture `gorm:"foreignKey:AlbumID"`
}
type Picture struct {
PictureID string `gorm:"primaryKey;column:picture_id"`
AlbumID string `gorm:"foreignKey:AlbumID;column:album_id"`
Description string `gorm:"column:description"`
}
When I attempt to insert data as follows, I get the error pq: insert or update on table "albums" violates foreign key constraint "fk_orders_albums".
test := Order{
ID: "abc",
ClientID: "client1",
Name: "Roy",
Albums: []Album{
{
AlbumID: "al_1",
Pictures: []Picture{
{
PictureID: "pic_1",
Description: "test pic",
},
},
},
},
}
gormDB.Save(&test)
I followed the solution on this similar question, but can't seem to get it to work: Golang: Gorm Error on insert or update on table violates foreign key contraint
Based on your entity model, Your schema would be like this:
Orders table is parent table, didn't depend to any table
Albums table has foreign key request_client_id which refer to orders table column client_id
Picture table has foreign key album_id which is refer to albums table column album_id
Based on my exploration in gorm documentation here, object in struct will be examine as first association. so, your struct will execute insert to albums first which it violate foreign key schema (expect: insert to orders table should be executed before albums).
But if you want to force using your schema, you can use gorm Association feature.
Here is the idea of using Association:
Expected Query in high level:
Insert to orders table
Insert to albums table
Insert to pictures table
Association Ideas:
Let Albums field value in Orders struct empty
Append Association Albums to Orders model
Since there have another association in Albums, use configuration FullSaveAssociation
Here is the following code:
picture := []Picture{
{
PictureID: "pic_1",
Description: "test pic",
},
}
albums := []Album{
{
ID: "al_1",
Pictures: picture,
RequestClientID: "",
},
}
orders := Order{
ID: "abc",
ClientID: "client1",
Name: "Roy",
}
if err := gormDB.Save(orders).Error; err != nil {
return
}
if err := gormDB.Session(&gorm.Session{FullSaveAssociations: true}).Model(&orders).Association("Albums").Append(albums); err != nil {
return
}
Full code could be found here:
Came across the solution I was looking for while looking into the great answer #giyuu provided. Since I had not saved the item before, I needed to use GORM's Create method:
err = gormDB.Create(&test).Error
Then, when I want to perform an update on any of these values, I use the Save method, with FullSaveAssociations enabled:
err = gormDB.Session(&gorm.Session{FullSaveAssociations: true}).Save(&test).Error

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 ON DELETE CASCADE

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.

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!

How to insert data with explicit id property which has null value?

I have a js object with explicit id property. Like this:
const data = {
user_id: null,
user_email: faker.internet.email()
};
The value of user_id is null and there is a users table using user_id as its primary key.
I want to insert this data correctly and I hope knex can obey the primary key increment rule.
Here is my code:
async function insert(user: any) {
return await knex('users')
.insert(user)
.returning('*');
}
When I try to insert this data, got an error:
error: null value in column "user_id" violates not-null constraint
How can I solve this?
You can set the value of user_id to undefined. Then it will insert the data and
obey the primary key increment rule.
const data = {
user_id: undefined,
user_email: faker.internet.email()
};
Check the inserted row in users table. The value of user_id is 1