Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: createManyAndReturn doesn't work for polymorphic models #1590

Merged
merged 2 commits into from
Jul 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 39 additions & 2 deletions packages/runtime/src/enhancements/delegate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<unknown[]> {
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;
});
}

Expand Down
2 changes: 1 addition & 1 deletion packages/runtime/src/enhancements/policy/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -461,7 +461,7 @@ export class PolicyProxyHandler<DbClient extends DbClientContract> 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');
}
Expand Down
4 changes: 2 additions & 2 deletions packages/runtime/src/enhancements/proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export interface PrismaProxyHandler {

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

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

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

Expand Down Expand Up @@ -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<unknown[]>('createManyAndReturn', args);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand Down
63 changes: 63 additions & 0 deletions tests/regression/tests/issue-1576.test.ts
Original file line number Diff line number Diff line change
@@ -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 }),
])
);
});
});
Loading