From 43b90f7ec3a846095708d53cd8b13e8eec063ad4 Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Sun, 14 Jul 2024 14:29:49 -0700 Subject: [PATCH 1/2] fix: `createManyAndReturn` doesn't work for polymorphic models --- packages/runtime/src/enhancements/delegate.ts | 41 +++++++++++- .../src/enhancements/policy/handler.ts | 2 +- packages/runtime/src/enhancements/proxy.ts | 4 +- .../with-delegate/enhanced-client.test.ts | 34 +++++++++- tests/regression/tests/issue-1576.test.ts | 63 +++++++++++++++++++ 5 files changed, 137 insertions(+), 7 deletions(-) create mode 100644 tests/regression/tests/issue-1576.test.ts diff --git a/packages/runtime/src/enhancements/delegate.ts b/packages/runtime/src/enhancements/delegate.ts index d3dd5b83c..d17861218 100644 --- a/packages/runtime/src/enhancements/delegate.ts +++ b/packages/runtime/src/enhancements/delegate.ts @@ -367,9 +367,46 @@ export class DelegateProxyHandler extends DefaultPrismaProxyHandler { return this.doCreate(tx, this.model, { data: item }); }) ); + return { count: r.length }; + }); + } + + override createManyAndReturn(args: { data: any; select?: any; skipDuplicates?: boolean }): Promise { + if (!args) { + throw prismaClientValidationError(this.prisma, this.options.prismaModule, 'query argument is required'); + } + if (!args.data) { + throw prismaClientValidationError( + this.prisma, + this.options.prismaModule, + 'data field is required in query argument' + ); + } + + if (!this.involvesDelegateModel(this.model)) { + return super.createManyAndReturn(args); + } - // filter out undefined value (due to skipping duplicates) - return { count: r.filter((item) => !!item).length }; + if (this.isDelegateOrDescendantOfDelegate(this.model) && args.skipDuplicates) { + throw prismaClientValidationError( + this.prisma, + this.options.prismaModule, + '`createManyAndReturn` with `skipDuplicates` set to true is not supported for delegated models' + ); + } + + // `createManyAndReturn` doesn't support nested create, which is needed for creating entities + // inheriting a delegate base, so we need to convert it to a regular `create` here. + // Note that the main difference is `create` doesn't support `skipDuplicates` as + // `createManyAndReturn` does. + + return this.queryUtils.transaction(this.prisma, async (tx) => { + const r = await Promise.all( + enumerate(args.data).map(async (item) => { + return this.doCreate(tx, this.model, { data: item, select: args.select }); + }) + ); + return r; }); } diff --git a/packages/runtime/src/enhancements/policy/handler.ts b/packages/runtime/src/enhancements/policy/handler.ts index 4256582ab..4c5e5667f 100644 --- a/packages/runtime/src/enhancements/policy/handler.ts +++ b/packages/runtime/src/enhancements/policy/handler.ts @@ -461,7 +461,7 @@ export class PolicyProxyHandler implements Pr }); } - createManyAndReturn(args: { select: any; include: any; data: any; skipDuplicates?: boolean }) { + createManyAndReturn(args: { data: any; select?: any; skipDuplicates?: boolean }) { if (!args) { throw prismaClientValidationError(this.prisma, this.prismaModule, 'query argument is required'); } diff --git a/packages/runtime/src/enhancements/proxy.ts b/packages/runtime/src/enhancements/proxy.ts index 2e7e2e825..f99cdfab4 100644 --- a/packages/runtime/src/enhancements/proxy.ts +++ b/packages/runtime/src/enhancements/proxy.ts @@ -35,7 +35,7 @@ export interface PrismaProxyHandler { createMany(args: { data: any; skipDuplicates?: boolean }): Promise; - createManyAndReturn(args: { data: any; select: any; include: any; skipDuplicates?: boolean }): Promise; + createManyAndReturn(args: { data: any; select?: any; skipDuplicates?: boolean }): Promise; update(args: any): Promise; @@ -124,7 +124,7 @@ export class DefaultPrismaProxyHandler implements PrismaProxyHandler { return this.deferred<{ count: number }>('createMany', args, false); } - createManyAndReturn(args: { data: any; select: any; include: any; skipDuplicates?: boolean }) { + createManyAndReturn(args: { data: any; select?: any; skipDuplicates?: boolean }) { return this.deferred('createManyAndReturn', args); } diff --git a/tests/integration/tests/enhancements/with-delegate/enhanced-client.test.ts b/tests/integration/tests/enhancements/with-delegate/enhanced-client.test.ts index 1a8f996df..e4a7ca124 100644 --- a/tests/integration/tests/enhancements/with-delegate/enhanced-client.test.ts +++ b/tests/integration/tests/enhancements/with-delegate/enhanced-client.test.ts @@ -129,14 +129,44 @@ describe('Polymorphism Test', () => { db.ratedVideo.createMany({ data: { viewCount: 1, duration: 100, url: 'xyz', rating: 100 } }) ).resolves.toMatchObject({ count: 1 }); + await expect( + db.ratedVideo.createManyAndReturn({ data: { viewCount: 1, duration: 100, url: 'xyz', rating: 100 } }) + ).resolves.toEqual( + expect.arrayContaining([ + expect.objectContaining({ + assetType: 'Video', + videoType: 'RatedVideo', + viewCount: 1, + duration: 100, + url: 'xyz', + rating: 100, + }), + ]) + ); + await expect( db.ratedVideo.createMany({ data: [ { viewCount: 2, duration: 200, url: 'xyz', rating: 100 }, - { viewCount: 3, duration: 300, url: 'xyz', rating: 100 }, + { viewCount: 3, duration: 300, url: 'xyz', rating: 200 }, ], }) ).resolves.toMatchObject({ count: 2 }); + + await expect( + db.ratedVideo.createManyAndReturn({ + data: [ + { viewCount: 2, duration: 200, url: 'xyz', rating: 100 }, + { viewCount: 3, duration: 300, url: 'xyz', rating: 200 }, + ], + select: { videoType: true, viewCount: true, rating: true }, + }) + ).resolves.toEqual( + expect.arrayContaining([ + { videoType: 'RatedVideo', viewCount: 2, rating: 100 }, + { videoType: 'RatedVideo', viewCount: 3, rating: 200 }, + ]) + ); }); it('create many polymorphic relation', async () => { @@ -154,7 +184,7 @@ describe('Polymorphism Test', () => { data: { viewCount: 1, duration: 100, url: 'xyz', rating: 100 }, }); await expect( - db.user.createMany({ data: [{ id: 2, assets: { connect: { id: video2.id } } }, { id: 3 }] }) + db.user.createMany({ data: [{ id: 3, assets: { connect: { id: video2.id } } }, { id: 3 }] }) ).resolves.toMatchObject({ count: 2 }); }); diff --git a/tests/regression/tests/issue-1576.test.ts b/tests/regression/tests/issue-1576.test.ts new file mode 100644 index 000000000..d00b853d1 --- /dev/null +++ b/tests/regression/tests/issue-1576.test.ts @@ -0,0 +1,63 @@ +import { loadSchema } from '@zenstackhq/testtools'; +describe('issue 1576', () => { + it('regression', async () => { + const { enhance } = await loadSchema( + ` +model Profile { + id Int @id @default(autoincrement()) + name String + items Item[] + type String + @@delegate(type) + @@allow('all', true) +} + +model GoldProfile extends Profile { + ticket Int +} + +model Item { + id Int @id @default(autoincrement()) + profileId Int + profile Profile @relation(fields: [profileId], references: [id]) + type String + @@delegate(type) + @@allow('all', true) +} + +model GoldItem extends Item { + inventory Boolean +} + ` + ); + + const db = enhance(); + + const profile = await db.goldProfile.create({ + data: { + name: 'hello', + ticket: 5, + }, + }); + + await expect( + db.goldItem.createManyAndReturn({ + data: [ + { + profileId: profile.id, + inventory: true, + }, + { + profileId: profile.id, + inventory: true, + }, + ], + }) + ).resolves.toEqual( + expect.arrayContaining([ + expect.objectContaining({ profileId: profile.id, type: 'GoldItem', inventory: true }), + expect.objectContaining({ profileId: profile.id, type: 'GoldItem', inventory: true }), + ]) + ); + }); +}); From 135ac761c3e4f3702b72bca419225ed2a6cbed49 Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Sun, 14 Jul 2024 14:57:35 -0700 Subject: [PATCH 2/2] update --- .../tests/enhancements/with-delegate/enhanced-client.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/tests/enhancements/with-delegate/enhanced-client.test.ts b/tests/integration/tests/enhancements/with-delegate/enhanced-client.test.ts index e4a7ca124..73b25c1ca 100644 --- a/tests/integration/tests/enhancements/with-delegate/enhanced-client.test.ts +++ b/tests/integration/tests/enhancements/with-delegate/enhanced-client.test.ts @@ -184,7 +184,7 @@ describe('Polymorphism Test', () => { data: { viewCount: 1, duration: 100, url: 'xyz', rating: 100 }, }); await expect( - db.user.createMany({ data: [{ id: 3, assets: { connect: { id: video2.id } } }, { id: 3 }] }) + db.user.createMany({ data: [{ id: 2, assets: { connect: { id: video2.id } } }, { id: 3 }] }) ).resolves.toMatchObject({ count: 2 }); });