Skip to content

Commit f0e2923

Browse files
committed
feat(many-to-many): generate join table
* for m-n-relations
1 parent a876bec commit f0e2923

File tree

6 files changed

+185
-4
lines changed

6 files changed

+185
-4
lines changed

__tests__/dbml.test.ts

+41
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {
22
datamodelDbml,
33
datamodelDbmlComments,
44
datamodelDbmlDefaults,
5+
datamodelDbmlManyToMany,
56
datamodelDbmlRelations,
67
} from './fixtures/dbml.datamodel';
78
import { generateDMMF } from './utils/generateDMMF';
@@ -142,4 +143,44 @@ Ref: Post.authorId > User.id`;
142143

143144
expect(dbml).toEqual(expectedDbml);
144145
});
146+
147+
test('generating dbml schema with many-to-many relationship', async () => {
148+
const dmmf = await generateDMMF(datamodelDbmlManyToMany);
149+
150+
const expectedDbml = `${autoGeneratedComment}
151+
152+
Table Post {
153+
id Int [pk, increment]
154+
categories Category
155+
}
156+
157+
Table Category {
158+
id Int [pk, increment]
159+
posts Post
160+
}
161+
162+
Table Author {
163+
id Int [pk, increment]
164+
books Book
165+
}
166+
167+
Table Book {
168+
id Int [pk, increment]
169+
authors Author
170+
}
171+
172+
Table CategoryToPost {
173+
categoryId Int [ref: > Category.id]
174+
postId Int [ref: > Post.id]
175+
}
176+
177+
Table AuthorToBook {
178+
bookId Int [ref: > Book.id]
179+
authorId Int [ref: > Author.id]
180+
}`;
181+
182+
const dbml = generateDBMLSchema(dmmf);
183+
184+
expect(dbml).toEqual(expectedDbml);
185+
});
145186
});

__tests__/fixtures/dbml.datamodel.ts

+22
Original file line numberDiff line numberDiff line change
@@ -79,3 +79,25 @@ export const datamodelDbmlComments = /* Prisma */ `
7979
USER
8080
}
8181
`;
82+
83+
export const datamodelDbmlManyToMany = /* Prisma */ `
84+
model Post {
85+
id Int @id @default(autoincrement())
86+
categories Category[]
87+
}
88+
89+
model Category {
90+
id Int @id @default(autoincrement())
91+
posts Post[]
92+
}
93+
94+
model Author {
95+
id Int @id @default(autoincrement())
96+
books Book[]
97+
}
98+
99+
model Book {
100+
id Int @id @default(autoincrement())
101+
authors Author[]
102+
}
103+
`;

prisma/dbml/schema.dbml

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
//// ------------------------------------------------------
2+
//// THIS FILE WAS AUTOMATICALLY GENERATED (DO NOT MODIFY)
3+
//// ------------------------------------------------------
4+
5+
Table Post {
6+
id Int [pk, increment]
7+
categories Category
8+
}
9+
10+
Table Category {
11+
id Int [pk, increment]
12+
posts Post
13+
}
14+
15+
Table Author {
16+
id Int [pk, increment]
17+
books Book
18+
}
19+
20+
Table Book {
21+
id String [pk]
22+
authors Author
23+
}
24+
25+
Table CategoryToPost {
26+
categoryId Int [ref: > Category.id]
27+
postId Int [ref: > Post.id]
28+
}
29+
30+
Table AuthorToBook {
31+
bookId String [ref: > Book.id]
32+
authorId Int [ref: > Author.id]
33+
}

prisma/schema.prisma

+1-3
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,7 @@ generator client {
1111
}
1212

1313
generator dbml {
14-
provider = "node ./dist/generator.js"
15-
output = "../dbml"
16-
outputName = "awesome.dbml"
14+
provider = "node ./dist/generator.js"
1715
}
1816

1917
model User {

src/generator/dbml.ts

+9-1
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,23 @@ import { DMMF } from '@prisma/generator-helper';
22
import { generateTables } from './table';
33
import { generateEnums } from './enums';
44
import { generateRelations } from './relations';
5+
import { generateManyToManyTables } from './many-to-many-tables';
56

67
export const autoGeneratedComment = `//// ------------------------------------------------------
78
//// THIS FILE WAS AUTOMATICALLY GENERATED (DO NOT MODIFY)
89
//// ------------------------------------------------------`;
910

1011
export function generateDBMLSchema(dmmf: DMMF.Document): string {
1112
const tables = generateTables(dmmf.datamodel.models);
13+
const manyToManyTables = generateManyToManyTables(dmmf.datamodel.models);
1214
const enums = generateEnums(dmmf.datamodel.enums);
1315
const refs = generateRelations(dmmf.datamodel.models);
1416

15-
return [autoGeneratedComment, ...tables, ...enums, ...refs].join('\n\n');
17+
return [
18+
autoGeneratedComment,
19+
...tables,
20+
...manyToManyTables,
21+
...enums,
22+
...refs,
23+
].join('\n\n');
1624
}

src/generator/many-to-many-tables.ts

+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import { DMMF } from '@prisma/generator-helper';
2+
3+
export function generateManyToManyTables(models: DMMF.Model[]): string[] {
4+
const manyToManyFields = filterManyToManyRelationFields(models);
5+
if (manyToManyFields.length === 0) {
6+
return [];
7+
}
8+
9+
return generateTables(manyToManyFields, models);
10+
}
11+
12+
function generateTables(
13+
manyToManyFields: DMMF.Field[],
14+
models: DMMF.Model[],
15+
manyToManyTables: string[] = []
16+
): string[] {
17+
const manyFirst = manyToManyFields.shift();
18+
if (!manyFirst) {
19+
return manyToManyTables;
20+
}
21+
22+
const manySecond = manyToManyFields.find(
23+
(field) => field.relationName === manyFirst.relationName
24+
)!;
25+
26+
manyToManyTables.push(
27+
`Table ${manyFirst?.relationName} {\n` +
28+
`${generateJoinFields([manyFirst, manySecond], models)}` +
29+
'\n}'
30+
);
31+
32+
return generateTables(
33+
manyToManyFields.filter(
34+
(field) => field.relationName !== manyFirst.relationName
35+
),
36+
models,
37+
manyToManyTables
38+
);
39+
}
40+
41+
function generateJoinFields(field: DMMF.Field[], models: DMMF.Model[]): string {
42+
return field.map((field) => joinField(field, models)).join('\n');
43+
}
44+
45+
function joinField(field: DMMF.Field, models: DMMF.Model[]): string {
46+
return ` ${field.type.toLowerCase()}Id ${getJoinIdType(
47+
field,
48+
models
49+
)} [ref: > ${field.type}.${field.relationToFields![0]}]`;
50+
}
51+
52+
function getJoinIdType(joinField: DMMF.Field, models: DMMF.Model[]): string {
53+
const joinIdField = models
54+
.filter((model) => model.name === joinField.type)
55+
.map(
56+
(model) =>
57+
model.fields.find(
58+
(field) => field.name === joinField.relationToFields![0]
59+
)!
60+
)[0];
61+
62+
return joinIdField.type;
63+
}
64+
65+
function filterManyToManyRelationFields(models: DMMF.Model[]): DMMF.Field[] {
66+
return models
67+
.map((model) =>
68+
model.fields
69+
.filter(
70+
(field) =>
71+
field.relationName &&
72+
field.isList &&
73+
field.relationFromFields?.length === 0 &&
74+
field.relationToFields?.length
75+
)
76+
.map((field) => field)
77+
)
78+
.flat();
79+
}

0 commit comments

Comments
 (0)