fix: Unique constraint should account for soft deleted records (#11048)

FIXES FRMW-2878

**What**
Currently, the `one-to-one` unique constraints does not account for deleted record. This prevents from inserting a new record wth the same fk if another one is deleted.

**Caveat**
`hasOne` with FK option is meant to be a special case, for example a many to one - one to many without defining the other side of the relation. In that case we don't handle this behaviour and keep it as it is
This commit is contained in:
Adrien de Peretti
2025-01-22 07:42:06 +00:00
committed by GitHub
parent ecc8efcb04
commit da3906efa4
22 changed files with 361 additions and 217 deletions
@@ -1,7 +1,5 @@
{
"namespaces": [
"public"
],
"namespaces": ["public"],
"name": "public",
"tables": [
{
@@ -161,9 +159,7 @@
},
{
"keyName": "fulfillment_address_pkey",
"columnNames": [
"id"
],
"columnNames": ["id"],
"composite": false,
"constraint": true,
"primary": true,
@@ -242,9 +238,7 @@
},
{
"keyName": "fulfillment_provider_pkey",
"columnNames": [
"id"
],
"columnNames": ["id"],
"composite": false,
"constraint": true,
"primary": true,
@@ -349,9 +343,7 @@
},
{
"keyName": "fulfillment_set_pkey",
"columnNames": [
"id"
],
"columnNames": ["id"],
"composite": false,
"constraint": true,
"primary": true,
@@ -465,9 +457,7 @@
},
{
"keyName": "service_zone_pkey",
"columnNames": [
"id"
],
"columnNames": ["id"],
"composite": false,
"constraint": true,
"primary": true,
@@ -478,13 +468,9 @@
"foreignKeys": {
"service_zone_fulfillment_set_id_foreign": {
"constraintName": "service_zone_fulfillment_set_id_foreign",
"columnNames": [
"fulfillment_set_id"
],
"columnNames": ["fulfillment_set_id"],
"localTableName": "public.service_zone",
"referencedColumnNames": [
"id"
],
"referencedColumnNames": ["id"],
"referencedTableName": "public.fulfillment_set",
"deleteRule": "cascade",
"updateRule": "cascade"
@@ -511,12 +497,7 @@
"primary": false,
"nullable": false,
"default": "'country'",
"enumItems": [
"country",
"province",
"city",
"zip"
],
"enumItems": ["country", "province", "city", "zip"],
"mappedType": "enum"
},
"country_code": {
@@ -656,9 +637,7 @@
},
{
"keyName": "geo_zone_pkey",
"columnNames": [
"id"
],
"columnNames": ["id"],
"composite": false,
"constraint": true,
"primary": true,
@@ -669,13 +648,9 @@
"foreignKeys": {
"geo_zone_service_zone_id_foreign": {
"constraintName": "geo_zone_service_zone_id_foreign",
"columnNames": [
"service_zone_id"
],
"columnNames": ["service_zone_id"],
"localTableName": "public.geo_zone",
"referencedColumnNames": [
"id"
],
"referencedColumnNames": ["id"],
"referencedTableName": "public.service_zone",
"deleteRule": "cascade",
"updateRule": "cascade"
@@ -768,9 +743,7 @@
},
{
"keyName": "shipping_option_type_pkey",
"columnNames": [
"id"
],
"columnNames": ["id"],
"composite": false,
"constraint": true,
"primary": true,
@@ -875,9 +848,7 @@
},
{
"keyName": "shipping_profile_pkey",
"columnNames": [
"id"
],
"columnNames": ["id"],
"composite": false,
"constraint": true,
"primary": true,
@@ -916,10 +887,7 @@
"primary": false,
"nullable": false,
"default": "'flat'",
"enumItems": [
"calculated",
"flat"
],
"enumItems": ["calculated", "flat"],
"mappedType": "enum"
},
"data": {
@@ -1013,9 +981,7 @@
"schema": "public",
"indexes": [
{
"columnNames": [
"shipping_option_type_id"
],
"columnNames": ["shipping_option_type_id"],
"composite": false,
"keyName": "shipping_option_shipping_option_type_id_unique",
"constraint": true,
@@ -1060,9 +1026,7 @@
},
{
"keyName": "shipping_option_pkey",
"columnNames": [
"id"
],
"columnNames": ["id"],
"composite": false,
"constraint": true,
"primary": true,
@@ -1073,52 +1037,36 @@
"foreignKeys": {
"shipping_option_service_zone_id_foreign": {
"constraintName": "shipping_option_service_zone_id_foreign",
"columnNames": [
"service_zone_id"
],
"columnNames": ["service_zone_id"],
"localTableName": "public.shipping_option",
"referencedColumnNames": [
"id"
],
"referencedColumnNames": ["id"],
"referencedTableName": "public.service_zone",
"deleteRule": "cascade",
"updateRule": "cascade"
},
"shipping_option_shipping_profile_id_foreign": {
"constraintName": "shipping_option_shipping_profile_id_foreign",
"columnNames": [
"shipping_profile_id"
],
"columnNames": ["shipping_profile_id"],
"localTableName": "public.shipping_option",
"referencedColumnNames": [
"id"
],
"referencedColumnNames": ["id"],
"referencedTableName": "public.shipping_profile",
"deleteRule": "set null",
"updateRule": "cascade"
},
"shipping_option_provider_id_foreign": {
"constraintName": "shipping_option_provider_id_foreign",
"columnNames": [
"provider_id"
],
"columnNames": ["provider_id"],
"localTableName": "public.shipping_option",
"referencedColumnNames": [
"id"
],
"referencedColumnNames": ["id"],
"referencedTableName": "public.fulfillment_provider",
"deleteRule": "set null",
"updateRule": "cascade"
},
"shipping_option_shipping_option_type_id_foreign": {
"constraintName": "shipping_option_shipping_option_type_id_foreign",
"columnNames": [
"shipping_option_type_id"
],
"columnNames": ["shipping_option_type_id"],
"localTableName": "public.shipping_option",
"referencedColumnNames": [
"id"
],
"referencedColumnNames": ["id"],
"referencedTableName": "public.shipping_option_type",
"updateRule": "cascade"
}
@@ -1152,16 +1100,7 @@
"autoincrement": false,
"primary": false,
"nullable": false,
"enumItems": [
"in",
"eq",
"ne",
"gt",
"gte",
"lt",
"lte",
"nin"
],
"enumItems": ["in", "eq", "ne", "gt", "gte", "lt", "lte", "nin"],
"mappedType": "enum"
},
"value": {
@@ -1238,9 +1177,7 @@
},
{
"keyName": "shipping_option_rule_pkey",
"columnNames": [
"id"
],
"columnNames": ["id"],
"composite": false,
"constraint": true,
"primary": true,
@@ -1251,13 +1188,9 @@
"foreignKeys": {
"shipping_option_rule_shipping_option_id_foreign": {
"constraintName": "shipping_option_rule_shipping_option_id_foreign",
"columnNames": [
"shipping_option_id"
],
"columnNames": ["shipping_option_id"],
"localTableName": "public.shipping_option_rule",
"referencedColumnNames": [
"id"
],
"referencedColumnNames": ["id"],
"referencedTableName": "public.shipping_option",
"deleteRule": "cascade",
"updateRule": "cascade"
@@ -1435,9 +1368,7 @@
"schema": "public",
"indexes": [
{
"columnNames": [
"provider_id"
],
"columnNames": ["provider_id"],
"composite": false,
"keyName": "fulfillment_provider_id_unique",
"constraint": true,
@@ -1445,9 +1376,7 @@
"unique": true
},
{
"columnNames": [
"delivery_address_id"
],
"columnNames": ["delivery_address_id"],
"composite": false,
"keyName": "fulfillment_delivery_address_id_unique",
"constraint": true,
@@ -1483,9 +1412,7 @@
},
{
"keyName": "fulfillment_pkey",
"columnNames": [
"id"
],
"columnNames": ["id"],
"composite": false,
"constraint": true,
"primary": true,
@@ -1496,39 +1423,27 @@
"foreignKeys": {
"fulfillment_provider_id_foreign": {
"constraintName": "fulfillment_provider_id_foreign",
"columnNames": [
"provider_id"
],
"columnNames": ["provider_id"],
"localTableName": "public.fulfillment",
"referencedColumnNames": [
"id"
],
"referencedColumnNames": ["id"],
"referencedTableName": "public.fulfillment_provider",
"deleteRule": "set null",
"updateRule": "cascade"
},
"fulfillment_shipping_option_id_foreign": {
"constraintName": "fulfillment_shipping_option_id_foreign",
"columnNames": [
"shipping_option_id"
],
"columnNames": ["shipping_option_id"],
"localTableName": "public.fulfillment",
"referencedColumnNames": [
"id"
],
"referencedColumnNames": ["id"],
"referencedTableName": "public.shipping_option",
"deleteRule": "set null",
"updateRule": "cascade"
},
"fulfillment_delivery_address_id_foreign": {
"constraintName": "fulfillment_delivery_address_id_foreign",
"columnNames": [
"delivery_address_id"
],
"columnNames": ["delivery_address_id"],
"localTableName": "public.fulfillment",
"referencedColumnNames": [
"id"
],
"referencedColumnNames": ["id"],
"referencedTableName": "public.fulfillment_address",
"deleteRule": "set null",
"updateRule": "cascade"
@@ -1639,9 +1554,7 @@
},
{
"keyName": "fulfillment_label_pkey",
"columnNames": [
"id"
],
"columnNames": ["id"],
"composite": false,
"constraint": true,
"primary": true,
@@ -1652,13 +1565,9 @@
"foreignKeys": {
"fulfillment_label_fulfillment_id_foreign": {
"constraintName": "fulfillment_label_fulfillment_id_foreign",
"columnNames": [
"fulfillment_id"
],
"columnNames": ["fulfillment_id"],
"localTableName": "public.fulfillment_label",
"referencedColumnNames": [
"id"
],
"referencedColumnNames": ["id"],
"referencedTableName": "public.fulfillment",
"deleteRule": "cascade",
"updateRule": "cascade"
@@ -1823,9 +1732,7 @@
},
{
"keyName": "fulfillment_item_pkey",
"columnNames": [
"id"
],
"columnNames": ["id"],
"composite": false,
"constraint": true,
"primary": true,
@@ -1836,13 +1743,9 @@
"foreignKeys": {
"fulfillment_item_fulfillment_id_foreign": {
"constraintName": "fulfillment_item_fulfillment_id_foreign",
"columnNames": [
"fulfillment_id"
],
"columnNames": ["fulfillment_id"],
"localTableName": "public.fulfillment_item",
"referencedColumnNames": [
"id"
],
"referencedColumnNames": ["id"],
"referencedTableName": "public.fulfillment",
"deleteRule": "cascade",
"updateRule": "cascade"
@@ -0,0 +1,40 @@
import { Migration } from "@mikro-orm/migrations"
export class Migration20250120115002 extends Migration {
async up(): Promise<void> {
this.addSql(
'alter table if exists "shipping_option" add constraint "shipping_option_provider_id_foreign" foreign key ("provider_id") references "fulfillment_provider" ("id") on update cascade on delete set null;'
)
this.addSql(
'alter table if exists "shipping_option" add constraint "shipping_option_shipping_option_type_id_foreign" foreign key ("shipping_option_type_id") references "shipping_option_type" ("id") on update cascade;'
)
this.addSql(
'CREATE INDEX IF NOT EXISTS "IDX_shipping_option_provider_id" ON "shipping_option" (provider_id) WHERE deleted_at IS NULL;'
)
this.addSql(
'alter table if exists "fulfillment" add constraint "fulfillment_provider_id_foreign" foreign key ("provider_id") references "fulfillment_provider" ("id") on update cascade on delete set null;'
)
this.addSql(
'alter table if exists "fulfillment" add constraint "fulfillment_delivery_address_id_foreign" foreign key ("delivery_address_id") references "fulfillment_address" ("id") on update cascade on delete set null;'
)
}
async down(): Promise<void> {
this.addSql(
'alter table if exists "shipping_option" drop constraint if exists "shipping_option_provider_id_foreign";'
)
this.addSql(
'alter table if exists "shipping_option" drop constraint if exists "shipping_option_shipping_option_type_id_foreign";'
)
this.addSql(
'alter table if exists "fulfillment" drop constraint if exists "fulfillment_provider_id_foreign";'
)
this.addSql(
'alter table if exists "fulfillment" drop constraint if exists "fulfillment_delivery_address_id_foreign";'
)
this.addSql('drop index if exists "IDX_shipping_option_provider_id";')
}
}