Skip to content
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
12 changes: 8 additions & 4 deletions packages/orm/src/client/query-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,26 +232,30 @@ export function getManyToManyRelation(schema: SchemaDef, model: string, field: s
if (!fieldDef.array || !fieldDef.relation?.opposite) {
return undefined;
}

// in case the m2m relation field is inherited from a delegate base, get the base model
const realModel = fieldDef.originModel ?? model;

const oppositeFieldDef = requireField(schema, fieldDef.type, fieldDef.relation.opposite);
if (oppositeFieldDef.array) {
// Prisma's convention for many-to-many relation:
// - model are sorted alphabetically by name
// - join table is named _<model1>To<model2>, unless an explicit name is provided by `@relation`
// - foreign keys are named A and B (based on the order of the model)
const sortedModelNames = [model, fieldDef.type].sort();
const sortedModelNames = [realModel, fieldDef.type].sort();

let orderedFK: [string, string];
if (model !== fieldDef.type) {
if (realModel !== fieldDef.type) {
// not a self-relation, model name's sort order determines fk order
orderedFK = sortedModelNames[0] === model ? ['A', 'B'] : ['B', 'A'];
orderedFK = sortedModelNames[0] === realModel ? ['A', 'B'] : ['B', 'A'];
} else {
// for self-relations, since model names are identical, relation field name's
// sort order determines fk order
const sortedFieldNames = [field, oppositeFieldDef.name].sort();
orderedFK = sortedFieldNames[0] === field ? ['A', 'B'] : ['B', 'A'];
}

const modelIdFields = requireIdFields(schema, model);
const modelIdFields = requireIdFields(schema, realModel);
invariant(modelIdFields.length === 1, 'Only single-field ID is supported for many-to-many relation');
const otherIdFields = requireIdFields(schema, fieldDef.type);
invariant(otherIdFields.length === 1, 'Only single-field ID is supported for many-to-many relation');
Expand Down
69 changes: 69 additions & 0 deletions tests/regression/test/issue-505.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { createTestClient } from '@zenstackhq/testtools';
import { describe, expect, it } from 'vitest';

describe('Regression tests for issues 505', () => {
it('verifies the issue', async () => {
const db = await createTestClient(
`
model Media {
id String @id @default(cuid())
type String
@@delegate(type)

messages Message[]
}

model TelegramPhoto extends Media {
tgFileId String @unique
width Int
height Int
}

model Message {
id String @id @default(cuid())
media Media[]
type String
@@delegate(type)
}

model TelegramMessage extends Message {
tgId BigInt @unique
}
`,
{ usePrismaPush: true },
);

const photo = await db.telegramPhoto.create({
data: {
tgFileId: 'file123',
width: 800,
height: 600,
},
});

const message = await db.telegramMessage.create({
data: {
tgId: BigInt(1),
media: {
connect: {
id: photo.id,
},
},
},
include: {
media: true,
},
});

expect(message).toMatchObject({
media: [photo],
});

const media = await db.media.findFirst({
include: { messages: true },
});
expect(media).toMatchObject({
messages: [expect.objectContaining({ tgId: BigInt(1), type: 'TelegramMessage' })],
});
});
});