Skip to content

Commit 8438e2b

Browse files
authored
fix: createManyAndReturn doesn't work for polymorphic models (#1590)
1 parent 6439fd6 commit 8438e2b

File tree

5 files changed

+136
-6
lines changed

5 files changed

+136
-6
lines changed

packages/runtime/src/enhancements/delegate.ts

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -367,9 +367,46 @@ export class DelegateProxyHandler extends DefaultPrismaProxyHandler {
367367
return this.doCreate(tx, this.model, { data: item });
368368
})
369369
);
370+
return { count: r.length };
371+
});
372+
}
373+
374+
override createManyAndReturn(args: { data: any; select?: any; skipDuplicates?: boolean }): Promise<unknown[]> {
375+
if (!args) {
376+
throw prismaClientValidationError(this.prisma, this.options.prismaModule, 'query argument is required');
377+
}
378+
if (!args.data) {
379+
throw prismaClientValidationError(
380+
this.prisma,
381+
this.options.prismaModule,
382+
'data field is required in query argument'
383+
);
384+
}
385+
386+
if (!this.involvesDelegateModel(this.model)) {
387+
return super.createManyAndReturn(args);
388+
}
370389

371-
// filter out undefined value (due to skipping duplicates)
372-
return { count: r.filter((item) => !!item).length };
390+
if (this.isDelegateOrDescendantOfDelegate(this.model) && args.skipDuplicates) {
391+
throw prismaClientValidationError(
392+
this.prisma,
393+
this.options.prismaModule,
394+
'`createManyAndReturn` with `skipDuplicates` set to true is not supported for delegated models'
395+
);
396+
}
397+
398+
// `createManyAndReturn` doesn't support nested create, which is needed for creating entities
399+
// inheriting a delegate base, so we need to convert it to a regular `create` here.
400+
// Note that the main difference is `create` doesn't support `skipDuplicates` as
401+
// `createManyAndReturn` does.
402+
403+
return this.queryUtils.transaction(this.prisma, async (tx) => {
404+
const r = await Promise.all(
405+
enumerate(args.data).map(async (item) => {
406+
return this.doCreate(tx, this.model, { data: item, select: args.select });
407+
})
408+
);
409+
return r;
373410
});
374411
}
375412

packages/runtime/src/enhancements/policy/handler.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -461,7 +461,7 @@ export class PolicyProxyHandler<DbClient extends DbClientContract> implements Pr
461461
});
462462
}
463463

464-
createManyAndReturn(args: { select: any; include: any; data: any; skipDuplicates?: boolean }) {
464+
createManyAndReturn(args: { data: any; select?: any; skipDuplicates?: boolean }) {
465465
if (!args) {
466466
throw prismaClientValidationError(this.prisma, this.prismaModule, 'query argument is required');
467467
}

packages/runtime/src/enhancements/proxy.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export interface PrismaProxyHandler {
3535

3636
createMany(args: { data: any; skipDuplicates?: boolean }): Promise<BatchResult>;
3737

38-
createManyAndReturn(args: { data: any; select: any; include: any; skipDuplicates?: boolean }): Promise<unknown[]>;
38+
createManyAndReturn(args: { data: any; select?: any; skipDuplicates?: boolean }): Promise<unknown[]>;
3939

4040
update(args: any): Promise<unknown>;
4141

@@ -124,7 +124,7 @@ export class DefaultPrismaProxyHandler implements PrismaProxyHandler {
124124
return this.deferred<{ count: number }>('createMany', args, false);
125125
}
126126

127-
createManyAndReturn(args: { data: any; select: any; include: any; skipDuplicates?: boolean }) {
127+
createManyAndReturn(args: { data: any; select?: any; skipDuplicates?: boolean }) {
128128
return this.deferred<unknown[]>('createManyAndReturn', args);
129129
}
130130

tests/integration/tests/enhancements/with-delegate/enhanced-client.test.ts

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,14 +129,44 @@ describe('Polymorphism Test', () => {
129129
db.ratedVideo.createMany({ data: { viewCount: 1, duration: 100, url: 'xyz', rating: 100 } })
130130
).resolves.toMatchObject({ count: 1 });
131131

132+
await expect(
133+
db.ratedVideo.createManyAndReturn({ data: { viewCount: 1, duration: 100, url: 'xyz', rating: 100 } })
134+
).resolves.toEqual(
135+
expect.arrayContaining([
136+
expect.objectContaining({
137+
assetType: 'Video',
138+
videoType: 'RatedVideo',
139+
viewCount: 1,
140+
duration: 100,
141+
url: 'xyz',
142+
rating: 100,
143+
}),
144+
])
145+
);
146+
132147
await expect(
133148
db.ratedVideo.createMany({
134149
data: [
135150
{ viewCount: 2, duration: 200, url: 'xyz', rating: 100 },
136-
{ viewCount: 3, duration: 300, url: 'xyz', rating: 100 },
151+
{ viewCount: 3, duration: 300, url: 'xyz', rating: 200 },
137152
],
138153
})
139154
).resolves.toMatchObject({ count: 2 });
155+
156+
await expect(
157+
db.ratedVideo.createManyAndReturn({
158+
data: [
159+
{ viewCount: 2, duration: 200, url: 'xyz', rating: 100 },
160+
{ viewCount: 3, duration: 300, url: 'xyz', rating: 200 },
161+
],
162+
select: { videoType: true, viewCount: true, rating: true },
163+
})
164+
).resolves.toEqual(
165+
expect.arrayContaining([
166+
{ videoType: 'RatedVideo', viewCount: 2, rating: 100 },
167+
{ videoType: 'RatedVideo', viewCount: 3, rating: 200 },
168+
])
169+
);
140170
});
141171

142172
it('create many polymorphic relation', async () => {
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { loadSchema } from '@zenstackhq/testtools';
2+
describe('issue 1576', () => {
3+
it('regression', async () => {
4+
const { enhance } = await loadSchema(
5+
`
6+
model Profile {
7+
id Int @id @default(autoincrement())
8+
name String
9+
items Item[]
10+
type String
11+
@@delegate(type)
12+
@@allow('all', true)
13+
}
14+
15+
model GoldProfile extends Profile {
16+
ticket Int
17+
}
18+
19+
model Item {
20+
id Int @id @default(autoincrement())
21+
profileId Int
22+
profile Profile @relation(fields: [profileId], references: [id])
23+
type String
24+
@@delegate(type)
25+
@@allow('all', true)
26+
}
27+
28+
model GoldItem extends Item {
29+
inventory Boolean
30+
}
31+
`
32+
);
33+
34+
const db = enhance();
35+
36+
const profile = await db.goldProfile.create({
37+
data: {
38+
name: 'hello',
39+
ticket: 5,
40+
},
41+
});
42+
43+
await expect(
44+
db.goldItem.createManyAndReturn({
45+
data: [
46+
{
47+
profileId: profile.id,
48+
inventory: true,
49+
},
50+
{
51+
profileId: profile.id,
52+
inventory: true,
53+
},
54+
],
55+
})
56+
).resolves.toEqual(
57+
expect.arrayContaining([
58+
expect.objectContaining({ profileId: profile.id, type: 'GoldItem', inventory: true }),
59+
expect.objectContaining({ profileId: profile.id, type: 'GoldItem', inventory: true }),
60+
])
61+
);
62+
});
63+
});

0 commit comments

Comments
 (0)