Skip to content

Commit

Permalink
fix(core): detect early deletes for compound unique constraints
Browse files Browse the repository at this point in the history
Closes #4305
  • Loading branch information
B4nan committed May 2, 2023
1 parent f102511 commit f9530e4
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 3 deletions.
2 changes: 2 additions & 0 deletions packages/core/src/typings.ts
Expand Up @@ -398,6 +398,7 @@ export class EntityMetadata<T = any> {
return !prop.inherited && prop.hydrate !== false && !discriminator && !prop.embedded && !onlyGetter;
});
this.selfReferencing = this.relations.some(prop => [this.className, this.root.className].includes(prop.type));
this.hasUniqueProps = this.uniques.length + this.uniqueProps.length > 0;
this.virtual = !!this.expression;

if (this.virtual) {
Expand Down Expand Up @@ -546,6 +547,7 @@ export interface EntityMetadata<T = any> {
filters: Dictionary<FilterDef>;
comment?: string;
selfReferencing?: boolean;
hasUniqueProps?: boolean;
readonly?: boolean;
polymorphs?: EntityMetadata[];
root: EntityMetadata<T>;
Expand Down
31 changes: 28 additions & 3 deletions packages/core/src/unit-of-work/UnitOfWork.ts
Expand Up @@ -586,17 +586,42 @@ export class UnitOfWork {
private expandUniqueProps<T extends object>(entity: T): string[] {
const wrapped = helper(entity);

return wrapped.__meta.uniqueProps.map(prop => {
if (entity[prop.name]) {
if (!wrapped.__meta.hasUniqueProps) {
return [];
}

const simpleUniqueHashes = wrapped.__meta.uniqueProps.map(prop => {
if (entity[prop.name] != null) {
return prop.reference === ReferenceType.SCALAR || prop.mapToPk ? entity[prop.name] : helper(entity[prop.name]).getSerializedPrimaryKey();
}

if (wrapped.__originalEntityData?.[prop.name as string]) {
if (wrapped.__originalEntityData?.[prop.name as string] != null) {
return Utils.getPrimaryKeyHash(Utils.asArray(wrapped.__originalEntityData![prop.name as string]));
}

return undefined;
}).filter(i => i) as string[];

const compoundUniqueHashes = wrapped.__meta.uniques.map(unique => {
const props = Utils.asArray(unique.properties);

if (props.every(prop => entity[prop] != null)) {
return Utils.getPrimaryKeyHash(props.map(p => {
const prop = wrapped.__meta.properties[p];
return prop.reference === ReferenceType.SCALAR || prop.mapToPk ? entity[prop.name] : helper(entity[prop.name]).getSerializedPrimaryKey();
}) as any);
}

if (props.every(prop => wrapped.__originalEntityData?.[prop as string] != null)) {
return Utils.getPrimaryKeyHash(props.map(p => {
return wrapped.__originalEntityData![p as string];
}));
}

return undefined;
}).filter(i => i) as string[];

return simpleUniqueHashes.concat(compoundUniqueHashes);
}

private initIdentifier<T extends object>(entity: T): void {
Expand Down
69 changes: 69 additions & 0 deletions tests/issues/GH4305.test.ts
@@ -0,0 +1,69 @@
import { Collection, Entity, ManyToOne, OneToMany, PrimaryKey, Property, Unique } from '@mikro-orm/core';
import { MikroORM } from '@mikro-orm/sqlite';

@Entity()
class Author {

@PrimaryKey()
id!: number;

@OneToMany(() => Book, book => book.author, { orphanRemoval: true })
books = new Collection<Book>(this);

}

@Unique({ properties: ['type', 'title'] })
@Entity()
class Book {

@PrimaryKey()
id!: number;

@ManyToOne(() => Author)
author!: Author;

@Property()
type!: string;

@Property()
title!: string;

@Property()
color!: string;

}

let orm: MikroORM;

beforeAll(async () => {
orm = await MikroORM.init({
entities: [Author],
dbName: `:memory:`,
});

await orm.schema.createSchema();

const author = new Author();
const book1 = new Book();
book1.title = 'book1';
book1.type = 't1';
book1.color = 'c1';
author.books.add(book1);
await orm.em.persistAndFlush(author);
orm.em.clear();
});

afterAll(() => orm.close(true));

test('#4305', async () => {
const author = await orm.em.findOne(Author, { id: 1 }, {
populate: ['books'],
});

const newBook = new Book();
newBook.title = 'book1';
newBook.type = 't1';
newBook.color = 'c2';
author!.books.set([newBook]);
await orm.em.flush();
});

0 comments on commit f9530e4

Please sign in to comment.