From d28a8dbc1e59b255a5cb3a4920362fd15045a750 Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Tue, 19 Mar 2024 20:05:59 -0700 Subject: [PATCH] fix: nested `createMany` with `skipDuplicates` option is not handled correctly --- .../src/enhancements/policy/handler.ts | 5 +- .../tests/regression/issue-1162.test.ts | 56 +++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 tests/integration/tests/regression/issue-1162.test.ts diff --git a/packages/runtime/src/enhancements/policy/handler.ts b/packages/runtime/src/enhancements/policy/handler.ts index 808763ae8..35e3ff0c1 100644 --- a/packages/runtime/src/enhancements/policy/handler.ts +++ b/packages/runtime/src/enhancements/policy/handler.ts @@ -734,7 +734,10 @@ export class PolicyProxyHandler implements Pr ) => { for (const item of enumerate(args.data)) { if (args.skipDuplicates) { - if (await this.hasDuplicatedUniqueConstraint(model, item, db)) { + // get a reversed query to include fields inherited from upstream mutation, + // it'll be merged with the create payload for unique constraint checking + const reversedQuery = this.utils.buildReversedQuery(context); + if (await this.hasDuplicatedUniqueConstraint(model, { ...reversedQuery, ...item }, db)) { if (this.shouldLogQuery) { this.logger.info(`[policy] \`createMany\` skipping duplicate ${formatObject(item)}`); } diff --git a/tests/integration/tests/regression/issue-1162.test.ts b/tests/integration/tests/regression/issue-1162.test.ts new file mode 100644 index 000000000..fd7f0dded --- /dev/null +++ b/tests/integration/tests/regression/issue-1162.test.ts @@ -0,0 +1,56 @@ +import { loadSchema } from '@zenstackhq/testtools'; + +describe('issue 1162', () => { + it('regression', async () => { + const { enhance } = await loadSchema( + ` + model User { + id String @id @default(cuid()) + companies CompanyUser[] + @@allow('all', true) + } + + model Company { + id String @id @default(cuid()) + users CompanyUser[] + @@allow('all', true) + } + + model CompanyUser { + company Company @relation(fields: [companyId], references: [id], onDelete: Cascade) + companyId String + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + userId String + @@id([companyId, userId]) + @@allow('all', true) + } + `, + { logPrismaQuery: true } + ); + + const db = enhance(); + + await db.user.create({ data: { id: 'abc' } }); + await db.user.create({ data: { id: 'def' } }); + await db.company.create({ data: { id: '1', users: { create: { userId: 'abc' } } } }); + await expect( + db.company.update({ + where: { id: '1' }, + data: { + users: { + createMany: { + data: [{ userId: 'abc' }, { userId: 'def' }], + skipDuplicates: true, + }, + }, + }, + include: { users: true }, + }) + ).resolves.toMatchObject({ + users: expect.arrayContaining([ + { companyId: '1', userId: 'abc' }, + { companyId: '1', userId: 'def' }, + ]), + }); + }); +});