diff --git a/packages/core/src/hydration/ObjectHydrator.ts b/packages/core/src/hydration/ObjectHydrator.ts index d1149eb6db32..b10292092a56 100644 --- a/packages/core/src/hydration/ObjectHydrator.ts +++ b/packages/core/src/hydration/ObjectHydrator.ts @@ -24,9 +24,10 @@ export class ObjectHydrator extends Hydrator { */ hydrate(entity: T, meta: EntityMetadata, data: EntityData, factory: EntityFactory, type: 'full' | 'returning' | 'reference', newEntity = false, convertCustomTypes = false, schema?: string): void { const hydrate = this.getEntityHydrator(meta, type); + const running = this.running; this.running = true; Utils.callCompiledFunction(hydrate, entity, data, factory, newEntity, convertCustomTypes, schema); - this.running = false; + this.running = running; } /** @@ -34,9 +35,10 @@ export class ObjectHydrator extends Hydrator { */ hydrateReference(entity: T, meta: EntityMetadata, data: EntityData, factory: EntityFactory, convertCustomTypes = false, schema?: string): void { const hydrate = this.getEntityHydrator(meta, 'reference'); + const running = this.running; this.running = true; Utils.callCompiledFunction(hydrate, entity, data, factory, false, convertCustomTypes, schema); - this.running = false; + this.running = running; } /** diff --git a/packages/core/src/unit-of-work/ChangeSetPersister.ts b/packages/core/src/unit-of-work/ChangeSetPersister.ts index 81f5e1f8fe37..016eaa79df06 100644 --- a/packages/core/src/unit-of-work/ChangeSetPersister.ts +++ b/packages/core/src/unit-of-work/ChangeSetPersister.ts @@ -282,6 +282,9 @@ export class ChangeSetPersister { return; } + // skip entity references as they don't have version values loaded + changeSets = changeSets.filter(cs => helper(cs.entity).__initialized); + const $or = changeSets.map(cs => { const cond = Utils.getPrimaryKeyCond(cs.originalEntity as T, meta.primaryKeys.concat(...meta.concurrencyCheckKeys)) as FilterQuery; diff --git a/tests/issues/GH4122.test.ts b/tests/issues/GH4122.test.ts index ef32b6cfa35c..c64192c7f6e8 100644 --- a/tests/issues/GH4122.test.ts +++ b/tests/issues/GH4122.test.ts @@ -5,8 +5,10 @@ import { Ref, OneToOne, OptionalProps, + SimpleLogger, } from '@mikro-orm/core'; import { MikroORM } from '@mikro-orm/sqlite'; +import { mockLogger } from '../helpers'; @Entity() export class Book { @@ -22,6 +24,9 @@ export class Book { @OneToOne({ entity: () => Book, mappedBy: 'prequel', ref: true, nullable: true }) sequel?: Ref; + @Property({ version: true }) + version!: number; + @Property() title!: string; @@ -33,23 +38,46 @@ beforeAll(async () => { orm = await MikroORM.init({ dbName: ':memory:', entities: [Book], + loggerFactory: options => new SimpleLogger(options), }); await orm.schema.refreshDatabase(); }); afterAll(() => orm.close(true)); -test('4122', async () => { - const book1 = orm.em.create(Book, { id: 'book1', title: 'book1' }); - const book2 = orm.em.create(Book, { id: 'book2', title: 'book2', prequel: 'book1' }); +beforeEach(async () => { + await orm.schema.clearDatabase(); + orm.em.create(Book, { id: 'book1', title: 'book1' }); + orm.em.create(Book, { id: 'book2', title: 'book2', prequel: 'book1' }); await orm.em.flush(); orm.em.clear(); +}); + +test('updating versioned reference (4121)', async () => { + const refetchedBook1 = await orm.em.findOneOrFail(Book, { id: 'book1' }); + refetchedBook1.title = 'updatedBook1'; + refetchedBook1.sequel!.unwrap().title = 'updatedBook2'; + await expect(orm.em.flush()).resolves.not.toThrow(); +}); + +test('extra updates (4121)', async () => { + const refetchedBook1 = await orm.em.findOneOrFail(Book, { id: 'book1' }); + refetchedBook1.title = 'updatedBook1'; + const mock = mockLogger(orm); + await expect(orm.em.flush()).resolves.not.toThrow(); + expect(mock.mock.calls).toEqual([ + ['[query] begin'], + ["[query] update `book` set `title` = 'updatedBook1', `version` = `version` + 1 where `id` = 'book1' and `version` = 1"], + ["[query] select `b0`.`id`, `b0`.`version` from `book` as `b0` where `b0`.`id` in ('book1')"], + ['[query] commit'], + ]); +}); +test('4122', async () => { const qb = orm.em.createQueryBuilder(Book); await qb.update({ title: 'updatedTitle' }).where({ sequel: null }); - await orm.em.refresh(book1); - await orm.em.refresh(book2); + const [book1, book2] = await orm.em.find(Book, {}); expect(book1.title).toEqual('book1'); expect(book2.title).toEqual('updatedTitle'); });