From 75653f0d857e74ab83f06290ffc8ed59f7e91f3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Ad=C3=A1mek?= Date: Sun, 2 Apr 2023 14:43:45 +0200 Subject: [PATCH] fix(core): fix hydration of 1:1 FK as PK property via joined strategy Closes #4160 --- packages/core/src/entity/EntityHelper.ts | 2 +- tests/issues/GH4160.test.ts | 94 ++++++++++++++++++++++++ 2 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 tests/issues/GH4160.test.ts diff --git a/packages/core/src/entity/EntityHelper.ts b/packages/core/src/entity/EntityHelper.ts index e45cf532f8b4..1fb1ddc5ed28 100644 --- a/packages/core/src/entity/EntityHelper.ts +++ b/packages/core/src/entity/EntityHelper.ts @@ -198,7 +198,7 @@ export class EntityHelper { helper(entity).__pk = helper(entity).getPrimaryKey()!; // the inverse side will be changed on the `value` too, so we need to clean-up and schedule orphan removal there too - if (value?.[prop2.name as string] != null && value?.[prop2.name as string] !== entity) { + if (!prop.primary && value?.[prop2.name as string] != null && Reference.unwrapReference(value?.[prop2.name as string]) !== entity) { const other = Reference.unwrapReference(value![prop2.name as string]); delete helper(other).__data[prop.name]; diff --git a/tests/issues/GH4160.test.ts b/tests/issues/GH4160.test.ts new file mode 100644 index 000000000000..798d3d218ecc --- /dev/null +++ b/tests/issues/GH4160.test.ts @@ -0,0 +1,94 @@ +import { Collection, Entity, LoadStrategy, ManyToOne, OneToMany, OneToOne, OptionalProps, PrimaryKey, Property, Rel } from '@mikro-orm/core'; +import { MikroORM } from '@mikro-orm/better-sqlite'; + +@Entity() +class Account { + + [OptionalProps]?: 'client'; + + @PrimaryKey() + id!: string; + + @Property() + name!: string; + + @OneToOne(() => Client, c => c.account) + client!: Rel; + +} + +@Entity() +class Client { + + @OneToOne({ primary: true, entity: () => Account }) + account!: Account; + + @OneToMany(() => Brand, brand => brand.client, { orphanRemoval: true }) + brands = new Collection(this); + +} + +@Entity() +class Brand { + + @PrimaryKey() + id!: string; + + @ManyToOne(() => Client) + client!: Client; + +} + +let orm: MikroORM; + +beforeAll(async () => { + orm = await MikroORM.init({ + entities: [Client, Account, Brand], + dbName: ':memory:', + }); + await orm.schema.refreshDatabase(); + + const account1 = orm.em.create(Account, { name: 'Account 1', id: '1' }); + const client1 = orm.em.create(Client, { account: account1 }); + orm.em.create(Brand, { id: '1', client: client1 }); + orm.em.create(Brand, { id: '2', client: client1 }); + await orm.em.flush(); +}); + +beforeEach(async () => { + orm.em.clear(); +}); + +afterAll(async () => { + await orm.close(true); +}); + +it('populate with select-in strategy', async () => { + const brands1 = await orm.em.find(Brand, {}, { + populate: ['client.account'], + strategy: LoadStrategy.SELECT_IN, + }); + + expect(brands1[0].client.account).toMatchObject({ id: '1', name: 'Account 1' }); + expect(brands1[1].client.account).toMatchObject({ id: '1', name: 'Account 1' }); +}); + +it('populate with joined strategy', async () => { + const brands1 = await orm.em.find(Brand, {}, { + populate: ['client.account'], + strategy: LoadStrategy.JOINED, + }); + + expect(brands1[0].client.account).toMatchObject({ id: '1', name: 'Account 1' }); + expect(brands1[1].client.account).toMatchObject({ id: '1', name: 'Account 1' }); +}); + +it('populate with query builder', async () => { + const brands2 = await orm.em + .createQueryBuilder(Brand, 'brand') + .leftJoinAndSelect('brand.client', 'client') + .leftJoinAndSelect('client.account', 'account'); + + expect(brands2[0].client.account).toMatchObject({ id: '1', name: 'Account 1' }); + expect(brands2[1].client.account).toMatchObject({ id: '1', name: 'Account 1' }); +});