Skip to content

Commit 67d8fd7

Browse files
committed
feat(indexes): include block level unique indexes
1 parent 46dceb2 commit 67d8fd7

File tree

7 files changed

+190
-10
lines changed

7 files changed

+190
-10
lines changed

__tests__/fixtures/table.datamodel.ts

+45
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { datamodelDbml } from './dbml.datamodel';
2+
import { datamodelOneToOneAndManyToOne } from './relations.datamodel';
13
export const datamodelSingleTable = /* Prisma */ `
24
model User {
35
id Int @id @default(autoincrement())
@@ -13,3 +15,46 @@ export const datamodelTableWithStringDefaults = /* Prisma */ `
1315
color String @default("blue")
1416
}
1517
`;
18+
19+
export const datamodelTableWithSingleCompositeUniqueIndex = /* Prisma */ `
20+
model A {
21+
id Int @id @default(autoincrement())
22+
b String
23+
24+
@@unique([b])
25+
}
26+
`;
27+
28+
export const datamodelTableWithThreeFieldsCompositeUniqueIndex = /* Prisma */ `
29+
model A {
30+
id Int @id @default(autoincrement())
31+
b String
32+
c Int
33+
d DateTime
34+
35+
@@unique([d, b, c])
36+
}
37+
`;
38+
39+
export const datamodelTableWithOneCompositeUniqueIndex = /* Prisma */ `
40+
model Token {
41+
id Int @id @default(autoincrement())
42+
device String
43+
operatingSystem String
44+
45+
@@unique([device, operatingSystem])
46+
}
47+
`;
48+
49+
export const datamodelTableWithTwoCompositeUniqueIndex = /* Prisma */ `
50+
model A {
51+
id Int @id @default(autoincrement())
52+
b String
53+
c String
54+
d Int
55+
e DateTime
56+
57+
@@unique([b, d])
58+
@@unique([e, c])
59+
}
60+
`;

__tests__/table.test.ts

+87
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import {
22
datamodelSingleTable,
3+
datamodelTableWithOneCompositeUniqueIndex,
4+
datamodelTableWithSingleCompositeUniqueIndex,
35
datamodelTableWithStringDefaults,
6+
datamodelTableWithThreeFieldsCompositeUniqueIndex,
7+
datamodelTableWithTwoCompositeUniqueIndex,
48
} from './fixtures/table.datamodel';
59
import { generateDMMF } from './utils/generateDMMF';
610
import { generateTables } from '../src/generator/table';
@@ -33,4 +37,87 @@ describe('Tables', () => {
3337
expect(enums.length).toEqual(1);
3438
expect(enums[0]).toMatch(expected);
3539
});
40+
41+
test('generate a table with single composite unique index', async () => {
42+
const dmmf = await generateDMMF(
43+
datamodelTableWithSingleCompositeUniqueIndex
44+
);
45+
46+
const expected = `Table A {
47+
id Int [pk, increment]
48+
b String [not null]
49+
50+
indexes {
51+
(b) [unique]
52+
}
53+
}`;
54+
55+
const enums = generateTables(dmmf.datamodel.models);
56+
57+
expect(enums.length).toEqual(1);
58+
expect(enums[0]).toMatch(expected);
59+
});
60+
61+
test('generate a table with three fields as composite unique index', async () => {
62+
const dmmf = await generateDMMF(
63+
datamodelTableWithThreeFieldsCompositeUniqueIndex
64+
);
65+
66+
const expected = `Table A {
67+
id Int [pk, increment]
68+
b String [not null]
69+
c Int [not null]
70+
d DateTime [not null]
71+
72+
indexes {
73+
(d, b, c) [unique]
74+
}
75+
}`;
76+
77+
const enums = generateTables(dmmf.datamodel.models);
78+
79+
expect(enums.length).toEqual(1);
80+
expect(enums[0]).toMatch(expected);
81+
});
82+
83+
test('generate a table with one composite unique index', async () => {
84+
const dmmf = await generateDMMF(datamodelTableWithOneCompositeUniqueIndex);
85+
86+
const expected = `Table Token {
87+
id Int [pk, increment]
88+
device String [not null]
89+
operatingSystem String [not null]
90+
91+
indexes {
92+
(device, operatingSystem) [unique]
93+
}
94+
}`;
95+
96+
const enums = generateTables(dmmf.datamodel.models);
97+
98+
expect(enums.length).toEqual(1);
99+
expect(enums[0]).toMatch(expected);
100+
});
101+
102+
test('generate a table with two composite unique index', async () => {
103+
const dmmf = await generateDMMF(datamodelTableWithTwoCompositeUniqueIndex);
104+
105+
const expected = `Table A {
106+
id Int [pk, increment]
107+
b String [not null]
108+
c String [not null]
109+
d Int [not null]
110+
e DateTime [not null]
111+
112+
indexes {
113+
(b, d) [unique]
114+
(e, c) [unique]
115+
}
116+
}`;
117+
118+
const enums = generateTables(dmmf.datamodel.models);
119+
120+
expect(enums.length).toEqual(1);
121+
expect(enums[0]).toMatch(expected);
122+
});
36123
});

prisma/dbml/schema.dbml

+10
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,16 @@ Table Category {
3838
posts Post
3939
}
4040

41+
Table Token {
42+
id Int [pk, increment]
43+
device String [not null]
44+
operatingSystem String [not null]
45+
46+
indexes {
47+
(device, operatingSystem) [unique]
48+
}
49+
}
50+
4151
Table CategoryToPost {
4252
categoriesId Int [ref: > Category.id]
4353
postsId Int [ref: > Post.id]

prisma/schema.prisma

+9-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ model User {
2828

2929
/// User profile
3030
model Profile {
31-
id Int @default(autoincrement()) @id
31+
id Int @id @default(autoincrement())
3232
bio String?
3333
user User @relation(fields: [userId], references: [id])
3434
userId Int @unique
@@ -50,6 +50,14 @@ model Category {
5050
posts Post[]
5151
}
5252

53+
model Token {
54+
id Int @id @default(autoincrement())
55+
device String
56+
operatingSystem String
57+
58+
@@unique([device, operatingSystem])
59+
}
60+
5361
/// user role
5462
enum Role {
5563
ADMIN /// allowed to do everything

src/cli/dbml-generator.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export async function generate(options: GeneratorOptions) {
1717
try {
1818
await mkdir(outputDir, { recursive: true });
1919

20-
// await writeFile('./test.json', JSON.stringify(options.dmmf.datamodel));
20+
await writeFile('./test.json', JSON.stringify(options.dmmf.datamodel));
2121

2222
const dbmlSchema = generateDBMLSchema(options.dmmf, allowManyToMany);
2323

src/generator/table.ts

+28-8
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,35 @@
1+
import { DBMLKeywords } from './../keywords';
12
import { DMMF } from '@prisma/generator-helper';
23

34
export function generateTables(models: DMMF.Model[]): string[] {
45
return models.map(
56
(model) =>
6-
`Table ${model.name} {\n` +
7+
`${DBMLKeywords.Table} ${model.name} {\n` +
78
generateFields(model.fields) +
9+
generateTableIndexes(model) +
810
generateTableDocumentation(model) +
911
'\n}'
1012
);
1113
}
1214

15+
const generateTableIndexes = (model: DMMF.Model): string => {
16+
const hasTableIndexes =
17+
model.idFields.length > 0 || model.uniqueFields.length > 0;
18+
return hasTableIndexes
19+
? `\n\n ${DBMLKeywords.Indexes} {\n${generateTableUniqueFields(
20+
model.uniqueFields
21+
)}\n }`
22+
: '';
23+
};
24+
25+
const generateTableUniqueFields = (uniqueFields: string[][]): string => {
26+
return uniqueFields
27+
.map(
28+
(composite) => ` (${composite.join(', ')}) [${DBMLKeywords.Unique}]`
29+
)
30+
.join('\n');
31+
};
32+
1333
const generateTableDocumentation = (model: DMMF.Model): string => {
1434
const doc = model.documentation;
1535
return doc ? `\n\n Note: '${doc}'` : '';
@@ -27,35 +47,35 @@ const generateFields = (fields: DMMF.Field[]): string => {
2747
const generateColumnDefinition = (field: DMMF.Field): string => {
2848
const columnDefinition = [];
2949
if (field.isId) {
30-
columnDefinition.push('pk');
50+
columnDefinition.push(DBMLKeywords.Pk);
3151
}
3252

3353
if ((field.default as DMMF.FieldDefault)?.name === 'autoincrement') {
34-
columnDefinition.push('increment');
54+
columnDefinition.push(DBMLKeywords.Increment);
3555
}
3656

3757
if ((field.default as DMMF.FieldDefault)?.name === 'now') {
3858
columnDefinition.push('default: `now()`');
3959
}
4060

4161
if (field.isUnique) {
42-
columnDefinition.push('unique');
62+
columnDefinition.push(DBMLKeywords.Unique);
4363
}
4464

4565
if (field.isRequired && !field.isId) {
46-
columnDefinition.push('not null');
66+
columnDefinition.push(DBMLKeywords.NotNull);
4767
}
4868

4969
if (field.hasDefaultValue && typeof field.default != 'object') {
5070
if (field.type === 'String' || field.kind === 'enum') {
51-
columnDefinition.push(`default: '${field.default}'`);
71+
columnDefinition.push(`${DBMLKeywords.Default}: '${field.default}'`);
5272
} else {
53-
columnDefinition.push(`default: ${field.default}`);
73+
columnDefinition.push(`${DBMLKeywords.Default}: ${field.default}`);
5474
}
5575
}
5676

5777
if (field.documentation) {
58-
columnDefinition.push(`note: '${field.documentation}'`);
78+
columnDefinition.push(`${DBMLKeywords.Note}: '${field.documentation}'`);
5979
}
6080

6181
if (columnDefinition.length) {

src/keywords.ts

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
export enum DBMLKeywords {
2+
Default = 'default',
3+
Increment = 'increment',
4+
Note = 'note',
5+
NotNull = 'not null',
6+
Table = 'Table',
7+
Unique = 'unique',
8+
Pk = 'pk',
9+
Indexes = 'indexes',
10+
}

0 commit comments

Comments
 (0)