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 08:42:06 +01:00
committed by GitHub
parent ecc8efcb04
commit da3906efa4
22 changed files with 361 additions and 217 deletions

View File

@@ -3094,6 +3094,7 @@ describe("Entity builder", () => {
owner: true,
kind: "1:1",
cascade: ["persist", "soft-remove"],
unique: false,
},
user_id: {
columnType: "text",
@@ -3208,6 +3209,7 @@ describe("Entity builder", () => {
name: "email",
entity: "Email",
fieldName: "email_id",
unique: false,
},
email_id: {
columnType: "text",
@@ -3320,6 +3322,7 @@ describe("Entity builder", () => {
entity: "Email",
nullable: true,
fieldName: "emails_id",
unique: false,
},
emails_id: {
columnType: "text",
@@ -3423,6 +3426,7 @@ describe("Entity builder", () => {
entity: "Email",
mappedBy: "owner",
fieldName: "email_id",
unique: false,
},
email_id: {
columnType: "text",
@@ -3530,6 +3534,7 @@ describe("Entity builder", () => {
cascade: ["persist", "soft-remove"],
mappedBy: "user",
fieldName: "email_id",
unique: false,
},
email_id: {
columnType: "text",
@@ -3707,6 +3712,7 @@ describe("Entity builder", () => {
cascade: ["persist", "soft-remove"],
mappedBy: "user",
fieldName: "email_id",
unique: false,
},
email_id: {
columnType: "text",
@@ -3791,6 +3797,7 @@ describe("Entity builder", () => {
kind: "1:1",
cascade: ["persist", "soft-remove"],
fieldName: "user_id",
unique: false,
},
user_id: {
columnType: "text",
@@ -4125,8 +4132,8 @@ describe("Entity builder", () => {
expect(settingMetadata.indexes).toEqual([
{
expression:
'CREATE INDEX IF NOT EXISTS "IDX_setting_user_id" ON "setting" (user_id) WHERE deleted_at IS NULL',
name: "IDX_setting_user_id",
'CREATE UNIQUE INDEX IF NOT EXISTS "IDX_setting_user_id_unique" ON "setting" (user_id) WHERE deleted_at IS NULL',
name: "IDX_setting_user_id_unique",
},
{
expression:
@@ -4739,6 +4746,7 @@ describe("Entity builder", () => {
mappedBy: "email",
onDelete: undefined,
owner: true,
unique: false,
},
user_id: {
kind: "scalar",
@@ -4953,6 +4961,7 @@ describe("Entity builder", () => {
onDelete: undefined,
mappedBy: "email",
owner: true,
unique: false,
},
user_id: {
kind: "scalar",
@@ -5572,6 +5581,7 @@ describe("Entity builder", () => {
mappedBy: "email",
onDelete: undefined,
owner: true,
unique: false,
},
user_id: {
kind: "scalar",
@@ -5772,6 +5782,7 @@ describe("Entity builder", () => {
mappedBy: "email",
onDelete: undefined,
owner: true,
unique: false,
},
user_id: {
kind: "scalar",
@@ -5995,6 +6006,7 @@ describe("Entity builder", () => {
nullable: false,
onDelete: undefined,
owner: true,
unique: false,
},
parent_id: {
name: "parent_id",

View File

@@ -178,6 +178,7 @@ export function defineHasOneRelationship(
*/
export function defineHasOneWithFKRelationship(
MikroORMEntity: EntityConstructor<any>,
entity: DmlEntity<any, any>,
relationship: RelationshipMetadata,
{ relatedModelName }: { relatedModelName: string },
cascades: EntityCascades<string[], string[]>
@@ -198,6 +199,7 @@ export function defineHasOneWithFKRelationship(
fieldName: foreignKeyName,
...(relationship.nullable ? { nullable: relationship.nullable } : {}),
...(mappedBy ? { mappedBy } : {}),
unique: false,
//orphanRemoval: true,
} as OneToOneOptions<any, any>
@@ -508,6 +510,10 @@ export function defineBelongsToRelationship(
mappedBy: mappedBy,
fieldName: foreignKeyName,
owner: true,
/**
* If we decide to support non soft deletable then this should be true and the unique index id should be removed
*/
unique: false,
// orphanRemoval: true,
}
@@ -523,6 +529,7 @@ export function defineBelongsToRelationship(
{
on: [foreignKeyName],
where: "deleted_at IS NULL",
unique: true,
},
])
@@ -821,6 +828,7 @@ export function defineRelationship(
case "hasOneWithFK":
defineHasOneWithFKRelationship(
MikroORMEntity,
entity,
relationship,
relatedEntityInfo,
cascades

View File

@@ -59,7 +59,7 @@ export function defineMikroOrmCliConfig(
user: "postgres",
password: "",
...(options as any),
entities,
entities: entities.filter(Boolean),
migrations: {
generator: CustomTsMigrationGenerator,
...options.migrations,