From 31d6d77c65dcf9ad3bb782af59f3a919845e531c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Ad=C3=A1mek?= Date: Tue, 1 Nov 2022 17:02:59 +0100 Subject: [PATCH] feat(core): add `EntityRepository.upsert()` shortcut --- packages/core/src/entity/EntityRepository.ts | 26 ++++++++++++++++++++ tests/EntityRepository.test.ts | 3 +++ 2 files changed, 29 insertions(+) diff --git a/packages/core/src/entity/EntityRepository.ts b/packages/core/src/entity/EntityRepository.ts index f116ac4d9aa6..0b2d77268a04 100644 --- a/packages/core/src/entity/EntityRepository.ts +++ b/packages/core/src/entity/EntityRepository.ts @@ -52,6 +52,32 @@ export class EntityRepository { return this.em.findOneOrFail(this.entityName, where, options); } + /** + * Creates or updates the entity, based on whether it is already present in the database. + * This method performs an `insert on conflict merge` query ensuring the database is in sync, returning a managed + * entity instance. The method accepts either `entityName` together with the entity `data`, or just entity instance. + * + * ```ts + * // insert into "author" ("age", "email") values (33, 'foo@bar.com') on conflict ("email") do update set "age" = 41 + * const author = await em.getRepository(Author).upsert({ email: 'foo@bar.com', age: 33 }); + * ``` + * + * The entity data needs to contain either the primary key, or any other unique property. Let's consider the following example, where `Author.email` is a unique property: + * + * ```ts + * // insert into "author" ("age", "email") values (33, 'foo@bar.com') on conflict ("email") do update set "age" = 41 + * // select "id" from "author" where "email" = 'foo@bar.com' + * const author = await em.getRepository(Author).upsert({ email: 'foo@bar.com', age: 33 }); + * ``` + * + * Depending on the driver support, this will either use a returning query, or a separate select query, to fetch the primary key if it's missing from the `data`. + * + * If the entity is already present in current context, there won't be any queries - instead, the entity data will be assigned and an explicit `flush` will be required for those changes to be persisted. + */ + async upsert(entityOrData?: EntityData | T, options?: NativeInsertUpdateOptions): Promise { + return this.em.upsert(this.entityName, entityOrData, options); + } + /** * Finds all entities matching your `where` query. You can pass additional options via the `options` parameter. */ diff --git a/tests/EntityRepository.test.ts b/tests/EntityRepository.test.ts index 1438ce591831..f03abc0e3b42 100644 --- a/tests/EntityRepository.test.ts +++ b/tests/EntityRepository.test.ts @@ -15,6 +15,7 @@ const methods = { qb: jest.fn(), findOne: jest.fn(), findOneOrFail: jest.fn(), + upsert: jest.fn(), find: jest.fn(), findAndCount: jest.fn(), remove: jest.fn(), @@ -62,6 +63,8 @@ describe('EntityRepository', () => { expect(methods.findOne.mock.calls[0]).toEqual([Publisher, 'bar', undefined]); await repo.findOneOrFail('bar'); expect(methods.findOneOrFail.mock.calls[0]).toEqual([Publisher, 'bar', undefined]); + await repo.upsert({ name: 'bar', id: '1' }); + expect(methods.upsert.mock.calls[0]).toEqual([Publisher, { name: 'bar', id: '1' }, undefined]); await repo.createQueryBuilder(); expect(methods.createQueryBuilder.mock.calls[0]).toEqual([Publisher, undefined]); await repo.qb();