Skip to content
This repository was archived by the owner on Mar 1, 2026. It is now read-only.

Commit dbe7257

Browse files
authored
fix(orm): fix query errors with many-to-many relations between delegate models (#506)
fixes #505
1 parent ea66833 commit dbe7257

File tree

2 files changed

+77
-4
lines changed

2 files changed

+77
-4
lines changed

packages/orm/src/client/query-utils.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -232,26 +232,30 @@ export function getManyToManyRelation(schema: SchemaDef, model: string, field: s
232232
if (!fieldDef.array || !fieldDef.relation?.opposite) {
233233
return undefined;
234234
}
235+
236+
// in case the m2m relation field is inherited from a delegate base, get the base model
237+
const realModel = fieldDef.originModel ?? model;
238+
235239
const oppositeFieldDef = requireField(schema, fieldDef.type, fieldDef.relation.opposite);
236240
if (oppositeFieldDef.array) {
237241
// Prisma's convention for many-to-many relation:
238242
// - model are sorted alphabetically by name
239243
// - join table is named _<model1>To<model2>, unless an explicit name is provided by `@relation`
240244
// - foreign keys are named A and B (based on the order of the model)
241-
const sortedModelNames = [model, fieldDef.type].sort();
245+
const sortedModelNames = [realModel, fieldDef.type].sort();
242246

243247
let orderedFK: [string, string];
244-
if (model !== fieldDef.type) {
248+
if (realModel !== fieldDef.type) {
245249
// not a self-relation, model name's sort order determines fk order
246-
orderedFK = sortedModelNames[0] === model ? ['A', 'B'] : ['B', 'A'];
250+
orderedFK = sortedModelNames[0] === realModel ? ['A', 'B'] : ['B', 'A'];
247251
} else {
248252
// for self-relations, since model names are identical, relation field name's
249253
// sort order determines fk order
250254
const sortedFieldNames = [field, oppositeFieldDef.name].sort();
251255
orderedFK = sortedFieldNames[0] === field ? ['A', 'B'] : ['B', 'A'];
252256
}
253257

254-
const modelIdFields = requireIdFields(schema, model);
258+
const modelIdFields = requireIdFields(schema, realModel);
255259
invariant(modelIdFields.length === 1, 'Only single-field ID is supported for many-to-many relation');
256260
const otherIdFields = requireIdFields(schema, fieldDef.type);
257261
invariant(otherIdFields.length === 1, 'Only single-field ID is supported for many-to-many relation');
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { createTestClient } from '@zenstackhq/testtools';
2+
import { describe, expect, it } from 'vitest';
3+
4+
describe('Regression tests for issues 505', () => {
5+
it('verifies the issue', async () => {
6+
const db = await createTestClient(
7+
`
8+
model Media {
9+
id String @id @default(cuid())
10+
type String
11+
@@delegate(type)
12+
13+
messages Message[]
14+
}
15+
16+
model TelegramPhoto extends Media {
17+
tgFileId String @unique
18+
width Int
19+
height Int
20+
}
21+
22+
model Message {
23+
id String @id @default(cuid())
24+
media Media[]
25+
type String
26+
@@delegate(type)
27+
}
28+
29+
model TelegramMessage extends Message {
30+
tgId BigInt @unique
31+
}
32+
`,
33+
{ usePrismaPush: true },
34+
);
35+
36+
const photo = await db.telegramPhoto.create({
37+
data: {
38+
tgFileId: 'file123',
39+
width: 800,
40+
height: 600,
41+
},
42+
});
43+
44+
const message = await db.telegramMessage.create({
45+
data: {
46+
tgId: BigInt(1),
47+
media: {
48+
connect: {
49+
id: photo.id,
50+
},
51+
},
52+
},
53+
include: {
54+
media: true,
55+
},
56+
});
57+
58+
expect(message).toMatchObject({
59+
media: [photo],
60+
});
61+
62+
const media = await db.media.findFirst({
63+
include: { messages: true },
64+
});
65+
expect(media).toMatchObject({
66+
messages: [expect.objectContaining({ tgId: BigInt(1), type: 'TelegramMessage' })],
67+
});
68+
});
69+
});

0 commit comments

Comments
 (0)