ormbridge 0.0.0-stage → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +459 -2
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +20 -0
- package/dist/cli.js.map +1 -0
- package/dist/cliRunner.d.ts +20 -0
- package/dist/cliRunner.js +363 -0
- package/dist/cliRunner.js.map +1 -0
- package/dist/config.d.ts +44 -0
- package/dist/config.js +220 -0
- package/dist/config.js.map +1 -0
- package/dist/convert.d.ts +20 -0
- package/dist/convert.js +61 -0
- package/dist/convert.js.map +1 -0
- package/dist/emitters/django.d.ts +4 -0
- package/dist/emitters/django.js +501 -0
- package/dist/emitters/django.js.map +1 -0
- package/dist/emitters/doctrine.d.ts +20 -0
- package/dist/emitters/doctrine.js +1134 -0
- package/dist/emitters/doctrine.js.map +1 -0
- package/dist/emitters/gorm.d.ts +38 -0
- package/dist/emitters/gorm.js +1507 -0
- package/dist/emitters/gorm.js.map +1 -0
- package/dist/emitters/graphene.d.ts +4 -0
- package/dist/emitters/graphene.js +431 -0
- package/dist/emitters/graphene.js.map +1 -0
- package/dist/emitters/laravel.d.ts +33 -0
- package/dist/emitters/laravel.js +1695 -0
- package/dist/emitters/laravel.js.map +1 -0
- package/dist/emitters/prisma.d.ts +21 -0
- package/dist/emitters/prisma.js +1058 -0
- package/dist/emitters/prisma.js.map +1 -0
- package/dist/emitters/typeorm.d.ts +9 -0
- package/dist/emitters/typeorm.js +838 -0
- package/dist/emitters/typeorm.js.map +1 -0
- package/dist/emitters/typescriptInterfaces.d.ts +15 -0
- package/dist/emitters/typescriptInterfaces.js +350 -0
- package/dist/emitters/typescriptInterfaces.js.map +1 -0
- package/dist/formats.d.ts +82 -0
- package/dist/formats.js +238 -0
- package/dist/formats.js.map +1 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/io.d.ts +48 -0
- package/dist/io.js +343 -0
- package/dist/io.js.map +1 -0
- package/dist/ir.d.ts +197 -0
- package/dist/ir.js +10 -0
- package/dist/ir.js.map +1 -0
- package/dist/naming.d.ts +11 -0
- package/dist/naming.js +88 -0
- package/dist/naming.js.map +1 -0
- package/dist/parsers/django.d.ts +14 -0
- package/dist/parsers/django.js +1161 -0
- package/dist/parsers/django.js.map +1 -0
- package/dist/parsers/doctrine.d.ts +12 -0
- package/dist/parsers/doctrine.js +1984 -0
- package/dist/parsers/doctrine.js.map +1 -0
- package/dist/parsers/goSyntax.d.ts +105 -0
- package/dist/parsers/goSyntax.js +435 -0
- package/dist/parsers/goSyntax.js.map +1 -0
- package/dist/parsers/gorm.d.ts +16 -0
- package/dist/parsers/gorm.js +1857 -0
- package/dist/parsers/gorm.js.map +1 -0
- package/dist/parsers/laravel.d.ts +17 -0
- package/dist/parsers/laravel.js +2824 -0
- package/dist/parsers/laravel.js.map +1 -0
- package/dist/parsers/phpSyntax.d.ts +124 -0
- package/dist/parsers/phpSyntax.js +429 -0
- package/dist/parsers/phpSyntax.js.map +1 -0
- package/dist/parsers/prisma.d.ts +12 -0
- package/dist/parsers/prisma.js +964 -0
- package/dist/parsers/prisma.js.map +1 -0
- package/dist/parsers/pythonSyntax.d.ts +38 -0
- package/dist/parsers/pythonSyntax.js +120 -0
- package/dist/parsers/pythonSyntax.js.map +1 -0
- package/dist/parsers/typeorm.d.ts +12 -0
- package/dist/parsers/typeorm.js +1614 -0
- package/dist/parsers/typeorm.js.map +1 -0
- package/dist/parsers/typescriptSyntax.d.ts +57 -0
- package/dist/parsers/typescriptSyntax.js +200 -0
- package/dist/parsers/typescriptSyntax.js.map +1 -0
- package/dist/prismaOnlyConstructs.d.ts +7 -0
- package/dist/prismaOnlyConstructs.js +76 -0
- package/dist/prismaOnlyConstructs.js.map +1 -0
- package/dist/result.d.ts +20 -0
- package/dist/result.js +18 -0
- package/dist/result.js.map +1 -0
- package/dist/transforms.d.ts +13 -0
- package/dist/transforms.js +142 -0
- package/dist/transforms.js.map +1 -0
- package/package.json +83 -4
|
@@ -0,0 +1,1058 @@
|
|
|
1
|
+
import { findModel } from '../ir.js';
|
|
2
|
+
import { toCamelCase, toSnakeCase } from '../naming.js';
|
|
3
|
+
export const PRISMA_PROVIDERS = [
|
|
4
|
+
'postgresql',
|
|
5
|
+
'mysql',
|
|
6
|
+
'sqlite',
|
|
7
|
+
'sqlserver',
|
|
8
|
+
'mongodb',
|
|
9
|
+
'cockroachdb',
|
|
10
|
+
];
|
|
11
|
+
/**
|
|
12
|
+
* Native types (`@db.*`) each provider accepts per Prisma scalar type. Checked against `prisma validate`
|
|
13
|
+
* (Prisma 6.19 and 7.10); SQLite has none. Names only, arguments are passed through as written.
|
|
14
|
+
*/
|
|
15
|
+
const NATIVE_TYPES = {
|
|
16
|
+
postgresql: {
|
|
17
|
+
String: [
|
|
18
|
+
'Text',
|
|
19
|
+
'Char',
|
|
20
|
+
'VarChar',
|
|
21
|
+
'Bit',
|
|
22
|
+
'VarBit',
|
|
23
|
+
'Uuid',
|
|
24
|
+
'Xml',
|
|
25
|
+
'Inet',
|
|
26
|
+
'Citext',
|
|
27
|
+
],
|
|
28
|
+
Boolean: ['Boolean'],
|
|
29
|
+
DateTime: ['Timestamp', 'Timestamptz', 'Date', 'Time', 'Timetz'],
|
|
30
|
+
Int: ['Integer', 'SmallInt', 'Oid'],
|
|
31
|
+
BigInt: ['BigInt'],
|
|
32
|
+
Float: ['DoublePrecision', 'Real'],
|
|
33
|
+
Decimal: ['Decimal', 'Money'],
|
|
34
|
+
Json: ['Json', 'JsonB'],
|
|
35
|
+
Bytes: ['ByteA'],
|
|
36
|
+
},
|
|
37
|
+
cockroachdb: {
|
|
38
|
+
String: [
|
|
39
|
+
'String',
|
|
40
|
+
'Char',
|
|
41
|
+
'CatalogSingleChar',
|
|
42
|
+
'Bit',
|
|
43
|
+
'VarBit',
|
|
44
|
+
'Uuid',
|
|
45
|
+
'Inet',
|
|
46
|
+
],
|
|
47
|
+
Boolean: ['Bool'],
|
|
48
|
+
DateTime: ['Timestamp', 'Timestamptz', 'Time', 'Timetz', 'Date'],
|
|
49
|
+
Int: ['Int4', 'Int2', 'Oid'],
|
|
50
|
+
BigInt: ['Int8'],
|
|
51
|
+
Float: ['Float8', 'Float4'],
|
|
52
|
+
Decimal: ['Decimal'],
|
|
53
|
+
Json: ['JsonB'],
|
|
54
|
+
Bytes: ['Bytes'],
|
|
55
|
+
},
|
|
56
|
+
mysql: {
|
|
57
|
+
String: ['VarChar', 'Char', 'TinyText', 'Text', 'MediumText', 'LongText'],
|
|
58
|
+
Boolean: ['TinyInt', 'Bit'],
|
|
59
|
+
DateTime: ['DateTime', 'Date', 'Time', 'Timestamp'],
|
|
60
|
+
Int: [
|
|
61
|
+
'Int',
|
|
62
|
+
'UnsignedInt',
|
|
63
|
+
'SmallInt',
|
|
64
|
+
'UnsignedSmallInt',
|
|
65
|
+
'MediumInt',
|
|
66
|
+
'UnsignedMediumInt',
|
|
67
|
+
'TinyInt',
|
|
68
|
+
'UnsignedTinyInt',
|
|
69
|
+
'Year',
|
|
70
|
+
],
|
|
71
|
+
BigInt: ['BigInt', 'UnsignedBigInt'],
|
|
72
|
+
Float: ['Float', 'Double'],
|
|
73
|
+
Decimal: ['Decimal'],
|
|
74
|
+
Json: ['Json'],
|
|
75
|
+
Bytes: [
|
|
76
|
+
'LongBlob',
|
|
77
|
+
'Binary',
|
|
78
|
+
'VarBinary',
|
|
79
|
+
'TinyBlob',
|
|
80
|
+
'Blob',
|
|
81
|
+
'MediumBlob',
|
|
82
|
+
'Bit',
|
|
83
|
+
],
|
|
84
|
+
},
|
|
85
|
+
sqlserver: {
|
|
86
|
+
String: [
|
|
87
|
+
'Char',
|
|
88
|
+
'NChar',
|
|
89
|
+
'VarChar',
|
|
90
|
+
'NVarChar',
|
|
91
|
+
'Text',
|
|
92
|
+
'NText',
|
|
93
|
+
'Xml',
|
|
94
|
+
'UniqueIdentifier',
|
|
95
|
+
],
|
|
96
|
+
Boolean: ['Bit'],
|
|
97
|
+
DateTime: [
|
|
98
|
+
'DateTime',
|
|
99
|
+
'DateTime2',
|
|
100
|
+
'SmallDateTime',
|
|
101
|
+
'Date',
|
|
102
|
+
'Time',
|
|
103
|
+
'DateTimeOffset',
|
|
104
|
+
],
|
|
105
|
+
Int: ['Int', 'SmallInt', 'TinyInt'],
|
|
106
|
+
BigInt: ['BigInt'],
|
|
107
|
+
Float: ['Real', 'Float', 'Money', 'SmallMoney'],
|
|
108
|
+
Decimal: ['Decimal'],
|
|
109
|
+
Bytes: ['Binary', 'VarBinary', 'Image'],
|
|
110
|
+
},
|
|
111
|
+
mongodb: {
|
|
112
|
+
String: ['String', 'ObjectId'],
|
|
113
|
+
Boolean: ['Bool'],
|
|
114
|
+
DateTime: ['Date', 'Timestamp'],
|
|
115
|
+
Int: ['Int'],
|
|
116
|
+
BigInt: ['Long'],
|
|
117
|
+
Float: ['Double'],
|
|
118
|
+
Json: ['Json'],
|
|
119
|
+
Bytes: ['ObjectId', 'BinData'],
|
|
120
|
+
},
|
|
121
|
+
sqlite: {},
|
|
122
|
+
};
|
|
123
|
+
/** Index access methods (`type:`) accepted per provider. */
|
|
124
|
+
const INDEX_METHODS = {
|
|
125
|
+
postgresql: ['BTree', 'Hash', 'Gist', 'Gin', 'SpGist', 'Brin'],
|
|
126
|
+
cockroachdb: ['BTree', 'Gin'],
|
|
127
|
+
};
|
|
128
|
+
const ON_DELETE_NAMES = {
|
|
129
|
+
cascade: 'Cascade',
|
|
130
|
+
setNull: 'SetNull',
|
|
131
|
+
restrict: 'Restrict',
|
|
132
|
+
noAction: 'NoAction',
|
|
133
|
+
setDefault: 'SetDefault',
|
|
134
|
+
};
|
|
135
|
+
export function emitPrisma(schema, options) {
|
|
136
|
+
const context = {
|
|
137
|
+
schema,
|
|
138
|
+
options,
|
|
139
|
+
warnings: [],
|
|
140
|
+
pairCounts: countRelationPairs(schema),
|
|
141
|
+
};
|
|
142
|
+
const blocks = [];
|
|
143
|
+
const schemaNames = collectSchemaNames(schema);
|
|
144
|
+
const hasViews = schema.models.some((model) => model.isView === true);
|
|
145
|
+
if (options.header) {
|
|
146
|
+
blocks.push(emitHeader(context, schemaNames, hasViews));
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
warnAboutMissingHeader(context, schemaNames, hasViews);
|
|
150
|
+
}
|
|
151
|
+
for (const enumDefinition of schema.enums) {
|
|
152
|
+
if (supportsEnums(options.provider)) {
|
|
153
|
+
blocks.push(emitEnum(context, enumDefinition));
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
context.warnings.push(`enum ${enumDefinition.name}: ${options.provider} has no enum support in Prisma; the enum was dropped and fields that use it are written as String.`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
const reverseLines = new Map();
|
|
160
|
+
const modelLines = new Map();
|
|
161
|
+
for (const model of schema.models) {
|
|
162
|
+
modelLines.set(model.name, buildForwardLines(context, model, reverseLines));
|
|
163
|
+
}
|
|
164
|
+
for (const model of schema.models) {
|
|
165
|
+
const lines = [...(modelLines.get(model.name) ?? [])];
|
|
166
|
+
const usedNames = new Set(lines.map((line) => line.name));
|
|
167
|
+
for (const reverseLine of reverseLines.get(model.name) ?? []) {
|
|
168
|
+
lines.push({
|
|
169
|
+
...reverseLine,
|
|
170
|
+
name: uniqueName(reverseLine.name, usedNames),
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
blocks.push(emitModel(context, model, lines));
|
|
174
|
+
}
|
|
175
|
+
return { text: `${blocks.join('\n\n')}\n`, warnings: context.warnings };
|
|
176
|
+
}
|
|
177
|
+
// ---------------------------------------------------------------------------
|
|
178
|
+
// Header and enums
|
|
179
|
+
// ---------------------------------------------------------------------------
|
|
180
|
+
/** Database schema names used by models and enums, in first-use order. */
|
|
181
|
+
function collectSchemaNames(schema) {
|
|
182
|
+
const names = new Set();
|
|
183
|
+
for (const enumDefinition of schema.enums) {
|
|
184
|
+
if (enumDefinition.schema !== undefined) {
|
|
185
|
+
names.add(enumDefinition.schema);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
for (const model of schema.models) {
|
|
189
|
+
if (model.schema !== undefined) {
|
|
190
|
+
names.add(model.schema);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return [...names];
|
|
194
|
+
}
|
|
195
|
+
function supportsEnums(provider) {
|
|
196
|
+
return provider !== 'sqlserver';
|
|
197
|
+
}
|
|
198
|
+
/** Primary-key constraint names (`map:`) exist on PostgreSQL, CockroachDB and SQL Server only. */
|
|
199
|
+
function supportsPrimaryKeyNames(provider) {
|
|
200
|
+
return (provider === 'postgresql' ||
|
|
201
|
+
provider === 'cockroachdb' ||
|
|
202
|
+
provider === 'sqlserver');
|
|
203
|
+
}
|
|
204
|
+
/** Named foreign keys are rejected by SQLite and MongoDB. */
|
|
205
|
+
function supportsForeignKeyNames(provider) {
|
|
206
|
+
return provider !== 'sqlite' && provider !== 'mongodb';
|
|
207
|
+
}
|
|
208
|
+
function supportsSchemas(provider) {
|
|
209
|
+
return (provider === 'postgresql' ||
|
|
210
|
+
provider === 'cockroachdb' ||
|
|
211
|
+
provider === 'sqlserver');
|
|
212
|
+
}
|
|
213
|
+
function emitHeader(context, schemaNames, hasViews) {
|
|
214
|
+
const provider = context.options.provider;
|
|
215
|
+
const isV7 = context.options.prismaVersion === 7;
|
|
216
|
+
const generatorLines = isV7
|
|
217
|
+
? [' provider = "prisma-client"', ' output = "../generated/prisma"']
|
|
218
|
+
: [' provider = "prisma-client-js"'];
|
|
219
|
+
if (hasViews) {
|
|
220
|
+
generatorLines.push(' previewFeatures = ["views"]');
|
|
221
|
+
// Keep the equals signs aligned the way `prisma format` writes them.
|
|
222
|
+
generatorLines.forEach((line, position) => {
|
|
223
|
+
generatorLines[position] = line.replace(/^(\s+\w+)\s*=/, (_match, key) => `${key.padEnd(17)} =`);
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
const datasourceLines = [` provider = "${provider}"`];
|
|
227
|
+
if (!isV7) {
|
|
228
|
+
datasourceLines.push(' url = env("DATABASE_URL")');
|
|
229
|
+
}
|
|
230
|
+
if (schemaNames.length > 0 && supportsSchemas(provider)) {
|
|
231
|
+
datasourceLines.push(` schemas = [${schemaNames.map((name) => JSON.stringify(name)).join(', ')}]`);
|
|
232
|
+
}
|
|
233
|
+
else if (schemaNames.length > 0) {
|
|
234
|
+
context.warnings.push(`Provider "${provider}" does not support multiple database schemas; @@schema (${schemaNames.join(', ')}) was written but Prisma will reject it.`);
|
|
235
|
+
}
|
|
236
|
+
if (isV7 && provider === 'mongodb') {
|
|
237
|
+
context.warnings.push('Prisma 7 header with provider "mongodb": the schema validates, but check the Prisma 7 release notes for MongoDB support before upgrading.');
|
|
238
|
+
}
|
|
239
|
+
return [
|
|
240
|
+
'generator client {',
|
|
241
|
+
...generatorLines,
|
|
242
|
+
'}',
|
|
243
|
+
'',
|
|
244
|
+
'datasource db {',
|
|
245
|
+
...datasourceLines,
|
|
246
|
+
'}',
|
|
247
|
+
].join('\n');
|
|
248
|
+
}
|
|
249
|
+
/** Without a header there is nowhere to switch on preview features or list schemas, so say so. */
|
|
250
|
+
function warnAboutMissingHeader(context, schemaNames, hasViews) {
|
|
251
|
+
if (hasViews) {
|
|
252
|
+
context.warnings.push('The schema contains views; add previewFeatures = ["views"] to your generator block, Prisma rejects view blocks without it.');
|
|
253
|
+
}
|
|
254
|
+
if (schemaNames.length > 0) {
|
|
255
|
+
context.warnings.push(`The schema uses @@schema (${schemaNames.join(', ')}); list them in the datasource block as schemas = [...].`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
function emitEnum(context, enumDefinition) {
|
|
259
|
+
const lines = [`enum ${enumDefinition.name} {`];
|
|
260
|
+
for (const value of enumDefinition.values) {
|
|
261
|
+
const mapAttr = value.dbValue === value.name
|
|
262
|
+
? ''
|
|
263
|
+
: ` @map(${JSON.stringify(value.dbValue)})`;
|
|
264
|
+
lines.push(` ${value.name}${mapAttr}`);
|
|
265
|
+
}
|
|
266
|
+
const blockAttributes = [];
|
|
267
|
+
if (enumDefinition.dbName !== undefined &&
|
|
268
|
+
enumDefinition.dbName !== enumDefinition.name) {
|
|
269
|
+
blockAttributes.push(`@@map(${JSON.stringify(enumDefinition.dbName)})`);
|
|
270
|
+
}
|
|
271
|
+
if (enumDefinition.schema !== undefined) {
|
|
272
|
+
if (supportsSchemas(context.options.provider)) {
|
|
273
|
+
blockAttributes.push(`@@schema(${JSON.stringify(enumDefinition.schema)})`);
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
context.warnings.push(`enum ${enumDefinition.name}: ${context.options.provider} has no database schemas; @@schema("${enumDefinition.schema}") was dropped.`);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
if (blockAttributes.length > 0) {
|
|
280
|
+
lines.push('', ...blockAttributes.map((attribute) => ` ${attribute}`));
|
|
281
|
+
}
|
|
282
|
+
lines.push('}');
|
|
283
|
+
return lines.join('\n');
|
|
284
|
+
}
|
|
285
|
+
// ---------------------------------------------------------------------------
|
|
286
|
+
// Naming helpers
|
|
287
|
+
// ---------------------------------------------------------------------------
|
|
288
|
+
function displayName(context, rawName) {
|
|
289
|
+
return context.options.camelFields ? toCamelCase(rawName) : rawName;
|
|
290
|
+
}
|
|
291
|
+
function uniqueName(baseName, usedNames) {
|
|
292
|
+
let candidate = baseName;
|
|
293
|
+
let suffix = 2;
|
|
294
|
+
while (usedNames.has(candidate)) {
|
|
295
|
+
candidate = `${baseName}${suffix}`;
|
|
296
|
+
suffix += 1;
|
|
297
|
+
}
|
|
298
|
+
usedNames.add(candidate);
|
|
299
|
+
return candidate;
|
|
300
|
+
}
|
|
301
|
+
function pairKey(firstModel, secondModel) {
|
|
302
|
+
return [firstModel, secondModel].sort().join('|');
|
|
303
|
+
}
|
|
304
|
+
function countRelationPairs(schema) {
|
|
305
|
+
const counts = new Map();
|
|
306
|
+
for (const model of schema.models) {
|
|
307
|
+
const targets = [
|
|
308
|
+
...model.relations.map((relation) => relation.targetModel),
|
|
309
|
+
...(model.compositeForeignKeys ?? []).map((key) => key.targetModel),
|
|
310
|
+
];
|
|
311
|
+
for (const targetModel of targets) {
|
|
312
|
+
const key = pairKey(model.name, targetModel);
|
|
313
|
+
counts.set(key, (counts.get(key) ?? 0) + 1);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
return counts;
|
|
317
|
+
}
|
|
318
|
+
/** Prisma needs an explicit relation name when two models are linked more than once. */
|
|
319
|
+
function relationNameFor(context, model, relationName, targetModel) {
|
|
320
|
+
const isSelfReference = model.name === targetModel;
|
|
321
|
+
const count = context.pairCounts.get(pairKey(model.name, targetModel)) ?? 0;
|
|
322
|
+
if (isSelfReference || count > 1) {
|
|
323
|
+
return `${model.name}_${relationName}`;
|
|
324
|
+
}
|
|
325
|
+
return undefined;
|
|
326
|
+
}
|
|
327
|
+
function isPostgresFamily(provider) {
|
|
328
|
+
return provider === 'postgresql' || provider === 'cockroachdb';
|
|
329
|
+
}
|
|
330
|
+
const POSTGRES_RANGE_TYPES = {
|
|
331
|
+
int: 'int4range',
|
|
332
|
+
bigInt: 'int8range',
|
|
333
|
+
decimal: 'numrange',
|
|
334
|
+
date: 'daterange',
|
|
335
|
+
dateTime: 'tstzrange',
|
|
336
|
+
};
|
|
337
|
+
function rangeTypeName(field) {
|
|
338
|
+
return POSTGRES_RANGE_TYPES[field.rangeOf ?? 'int'] ?? 'int4range';
|
|
339
|
+
}
|
|
340
|
+
function formatNative(name, args) {
|
|
341
|
+
return `@db.${name}${args.length === 0 ? '' : `(${args.join(', ')})`}`;
|
|
342
|
+
}
|
|
343
|
+
/** Provider-specific native type written when the field carries no native type of its own. */
|
|
344
|
+
function defaultNative(provider, field) {
|
|
345
|
+
const postgres = isPostgresFamily(provider);
|
|
346
|
+
switch (field.type) {
|
|
347
|
+
case 'string': {
|
|
348
|
+
const length = field.maxLength;
|
|
349
|
+
if (length === undefined) {
|
|
350
|
+
return undefined;
|
|
351
|
+
}
|
|
352
|
+
if (provider === 'postgresql' || provider === 'mysql') {
|
|
353
|
+
return `@db.VarChar(${length})`;
|
|
354
|
+
}
|
|
355
|
+
if (provider === 'cockroachdb') {
|
|
356
|
+
return `@db.String(${length})`;
|
|
357
|
+
}
|
|
358
|
+
return provider === 'sqlserver' && length <= 4000
|
|
359
|
+
? `@db.NVarChar(${length})`
|
|
360
|
+
: undefined;
|
|
361
|
+
}
|
|
362
|
+
case 'text':
|
|
363
|
+
if (provider === 'postgresql' || provider === 'mysql') {
|
|
364
|
+
return '@db.Text';
|
|
365
|
+
}
|
|
366
|
+
return provider === 'sqlserver' ? '@db.NVarChar(Max)' : undefined;
|
|
367
|
+
case 'uuid':
|
|
368
|
+
if (postgres) {
|
|
369
|
+
return '@db.Uuid';
|
|
370
|
+
}
|
|
371
|
+
return provider === 'sqlserver' ? '@db.UniqueIdentifier' : undefined;
|
|
372
|
+
case 'decimal':
|
|
373
|
+
return provider !== 'sqlite' &&
|
|
374
|
+
provider !== 'mongodb' &&
|
|
375
|
+
field.maxDigits !== undefined &&
|
|
376
|
+
field.decimalPlaces !== undefined
|
|
377
|
+
? `@db.Decimal(${field.maxDigits}, ${field.decimalPlaces})`
|
|
378
|
+
: undefined;
|
|
379
|
+
case 'dateTime':
|
|
380
|
+
return postgres ? '@db.Timestamptz(6)' : undefined;
|
|
381
|
+
case 'date':
|
|
382
|
+
return provider === 'sqlite' || provider === 'mongodb'
|
|
383
|
+
? undefined
|
|
384
|
+
: '@db.Date';
|
|
385
|
+
case 'time':
|
|
386
|
+
if (provider === 'sqlserver') {
|
|
387
|
+
return '@db.Time';
|
|
388
|
+
}
|
|
389
|
+
return provider === 'sqlite' || provider === 'mongodb'
|
|
390
|
+
? undefined
|
|
391
|
+
: '@db.Time(6)';
|
|
392
|
+
case 'ipAddress':
|
|
393
|
+
return postgres ? '@db.Inet' : undefined;
|
|
394
|
+
case 'json':
|
|
395
|
+
return provider === 'sqlserver' ? '@db.NVarChar(Max)' : undefined;
|
|
396
|
+
default:
|
|
397
|
+
return undefined;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
/** Prisma scalar type for an IR field, before native types are considered. */
|
|
401
|
+
function baseTypeOf(context, field) {
|
|
402
|
+
const provider = context.options.provider;
|
|
403
|
+
if (field.enumName !== undefined) {
|
|
404
|
+
return supportsEnums(provider) ? field.enumName : 'String';
|
|
405
|
+
}
|
|
406
|
+
switch (field.type) {
|
|
407
|
+
case 'string':
|
|
408
|
+
case 'text':
|
|
409
|
+
case 'uuid':
|
|
410
|
+
return 'String';
|
|
411
|
+
case 'int':
|
|
412
|
+
return 'Int';
|
|
413
|
+
case 'bigInt':
|
|
414
|
+
case 'duration':
|
|
415
|
+
return 'BigInt';
|
|
416
|
+
case 'float':
|
|
417
|
+
return 'Float';
|
|
418
|
+
case 'decimal':
|
|
419
|
+
return 'Decimal';
|
|
420
|
+
case 'boolean':
|
|
421
|
+
return 'Boolean';
|
|
422
|
+
case 'dateTime':
|
|
423
|
+
case 'date':
|
|
424
|
+
case 'time':
|
|
425
|
+
return 'DateTime';
|
|
426
|
+
case 'json':
|
|
427
|
+
// SQL Server has no JSON type in Prisma; the column is stored as text.
|
|
428
|
+
return provider === 'sqlserver' ? 'String' : 'Json';
|
|
429
|
+
case 'bytes':
|
|
430
|
+
return 'Bytes';
|
|
431
|
+
case 'ipAddress':
|
|
432
|
+
return 'String';
|
|
433
|
+
case 'hstore':
|
|
434
|
+
return 'Json';
|
|
435
|
+
case 'range':
|
|
436
|
+
return isPostgresFamily(provider)
|
|
437
|
+
? `Unsupported(${JSON.stringify(rangeTypeName(field))})`
|
|
438
|
+
: 'String';
|
|
439
|
+
case 'unsupported':
|
|
440
|
+
return `Unsupported(${JSON.stringify(field.unsupportedType ?? '')})`;
|
|
441
|
+
default:
|
|
442
|
+
return 'String';
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* Checks a native type carried over from the source against what the target provider accepts for the
|
|
447
|
+
* Prisma scalar type. Returns the `@db.*` attribute, or undefined (with a warning when a label is given).
|
|
448
|
+
*/
|
|
449
|
+
function resolveNativeType(context, field, baseType, label) {
|
|
450
|
+
const nativeType = field.nativeType;
|
|
451
|
+
if (nativeType === undefined) {
|
|
452
|
+
return undefined;
|
|
453
|
+
}
|
|
454
|
+
const provider = context.options.provider;
|
|
455
|
+
const accepted = NATIVE_TYPES[provider][baseType] ?? [];
|
|
456
|
+
if (accepted.includes(nativeType.name)) {
|
|
457
|
+
return formatNative(nativeType.name, nativeType.args);
|
|
458
|
+
}
|
|
459
|
+
if (label !== undefined) {
|
|
460
|
+
context.warnings.push(provider === 'sqlite'
|
|
461
|
+
? `${label}: SQLite has no native column types; @db.${nativeType.name} was dropped.`
|
|
462
|
+
: `${label}: the native type @db.${nativeType.name} is not available for ${baseType} on ${provider}; the default column type was used instead.`);
|
|
463
|
+
}
|
|
464
|
+
return undefined;
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* Maps an IR field to a Prisma type plus native attribute. A label turns on warnings about native types
|
|
468
|
+
* that the target provider rejects (leave it out when only the type is needed, as for foreign keys).
|
|
469
|
+
*/
|
|
470
|
+
function prismaTypeOf(context, field, label) {
|
|
471
|
+
const baseType = baseTypeOf(context, field);
|
|
472
|
+
if (field.type === 'range' || field.type === 'unsupported') {
|
|
473
|
+
return { type: baseType };
|
|
474
|
+
}
|
|
475
|
+
if (field.enumName !== undefined) {
|
|
476
|
+
return { type: baseType };
|
|
477
|
+
}
|
|
478
|
+
// A Json column stored as text on SQL Server cannot keep a JSON native type.
|
|
479
|
+
const jsonAsText = field.type === 'json' && context.options.provider === 'sqlserver';
|
|
480
|
+
const carried = jsonAsText
|
|
481
|
+
? undefined
|
|
482
|
+
: resolveNativeType(context, field, baseType, label);
|
|
483
|
+
const nativeAttr = carried ?? defaultNative(context.options.provider, field);
|
|
484
|
+
return nativeAttr === undefined
|
|
485
|
+
? { type: baseType }
|
|
486
|
+
: { type: baseType, nativeAttr };
|
|
487
|
+
}
|
|
488
|
+
function defaultAttrOf(context, field, label) {
|
|
489
|
+
const defaultValue = field.default;
|
|
490
|
+
if (defaultValue === undefined) {
|
|
491
|
+
return undefined;
|
|
492
|
+
}
|
|
493
|
+
switch (defaultValue.kind) {
|
|
494
|
+
case 'autoIncrement':
|
|
495
|
+
if (field.type !== 'int' && field.type !== 'bigInt') {
|
|
496
|
+
return undefined;
|
|
497
|
+
}
|
|
498
|
+
if (context.options.provider === 'mongodb') {
|
|
499
|
+
context.warnings.push(`${label}: MongoDB does not support autoincrement(); the default was dropped (use @default(auto()) on an ObjectId key).`);
|
|
500
|
+
return undefined;
|
|
501
|
+
}
|
|
502
|
+
if (context.options.provider === 'cockroachdb' && field.type === 'int') {
|
|
503
|
+
context.warnings.push(`${label}: CockroachDB allows autoincrement() only on BigInt; the Int default was written as sequence().`);
|
|
504
|
+
return '@default(sequence())';
|
|
505
|
+
}
|
|
506
|
+
return '@default(autoincrement())';
|
|
507
|
+
case 'now':
|
|
508
|
+
return field.isAutoUpdated ? undefined : '@default(now())';
|
|
509
|
+
case 'uuid':
|
|
510
|
+
return defaultValue.version === undefined
|
|
511
|
+
? '@default(uuid())'
|
|
512
|
+
: `@default(uuid(${defaultValue.version}))`;
|
|
513
|
+
case 'clientGenerated':
|
|
514
|
+
return `@default(${defaultValue.generator}(${defaultValue.args ?? ''}))`;
|
|
515
|
+
case 'dbExpression':
|
|
516
|
+
return defaultValue.isFunction === true
|
|
517
|
+
? `@default(${defaultValue.expression})`
|
|
518
|
+
: `@default(dbgenerated(${JSON.stringify(defaultValue.expression)}))`;
|
|
519
|
+
case 'enumValue': {
|
|
520
|
+
if (supportsEnums(context.options.provider)) {
|
|
521
|
+
return `@default(${defaultValue.value})`;
|
|
522
|
+
}
|
|
523
|
+
const member = context.schema.enums
|
|
524
|
+
.find((candidate) => candidate.name === field.enumName)
|
|
525
|
+
?.values.find((value) => value.name === defaultValue.value);
|
|
526
|
+
return `@default(${JSON.stringify(member?.dbValue ?? defaultValue.value)})`;
|
|
527
|
+
}
|
|
528
|
+
case 'literal':
|
|
529
|
+
return typeof defaultValue.value === 'string'
|
|
530
|
+
? `@default(${JSON.stringify(defaultValue.value)})`
|
|
531
|
+
: `@default(${String(defaultValue.value)})`;
|
|
532
|
+
default:
|
|
533
|
+
return undefined;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
/** Resolves the target column of a relation: an explicit to_field, otherwise the primary key. */
|
|
537
|
+
function referencedKey(context, target, toField, depth = 0) {
|
|
538
|
+
if (depth > 5) {
|
|
539
|
+
return undefined;
|
|
540
|
+
}
|
|
541
|
+
if (toField !== undefined) {
|
|
542
|
+
const explicit = target.fields.find((field) => field.name === toField);
|
|
543
|
+
if (explicit !== undefined) {
|
|
544
|
+
const mapped = prismaTypeOf(context, explicit);
|
|
545
|
+
return {
|
|
546
|
+
fieldName: displayName(context, explicit.name),
|
|
547
|
+
type: mapped.type,
|
|
548
|
+
...(mapped.nativeAttr === undefined
|
|
549
|
+
? {}
|
|
550
|
+
: { nativeAttr: mapped.nativeAttr }),
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
const primaryField = target.fields.find((field) => field.isPrimaryKey);
|
|
555
|
+
if (primaryField !== undefined) {
|
|
556
|
+
const mapped = prismaTypeOf(context, primaryField);
|
|
557
|
+
return {
|
|
558
|
+
fieldName: displayName(context, primaryField.name),
|
|
559
|
+
type: mapped.type,
|
|
560
|
+
...(mapped.nativeAttr === undefined
|
|
561
|
+
? {}
|
|
562
|
+
: { nativeAttr: mapped.nativeAttr }),
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
const primaryRelation = target.relations.find((relation) => relation.isPrimaryKey === true);
|
|
566
|
+
if (primaryRelation !== undefined) {
|
|
567
|
+
const chained = findModel(context.schema, primaryRelation.targetModel);
|
|
568
|
+
if (chained !== undefined) {
|
|
569
|
+
const chainedKey = referencedKey(context, chained, primaryRelation.toField, depth + 1);
|
|
570
|
+
if (chainedKey !== undefined) {
|
|
571
|
+
return {
|
|
572
|
+
...chainedKey,
|
|
573
|
+
fieldName: scalarNameOf(context, primaryRelation),
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
return undefined;
|
|
579
|
+
}
|
|
580
|
+
/** Name of the scalar foreign-key field that backs a relation. */
|
|
581
|
+
function scalarNameOf(context, relation) {
|
|
582
|
+
const scalarName = displayName(context, relation.columnName);
|
|
583
|
+
const relationName = displayName(context, relation.name);
|
|
584
|
+
if (scalarName !== relationName) {
|
|
585
|
+
return scalarName;
|
|
586
|
+
}
|
|
587
|
+
return context.options.camelFields ? `${scalarName}Id` : `${scalarName}_id`;
|
|
588
|
+
}
|
|
589
|
+
// ---------------------------------------------------------------------------
|
|
590
|
+
// Model lines
|
|
591
|
+
// ---------------------------------------------------------------------------
|
|
592
|
+
function buildForwardLines(context, model, reverseLines) {
|
|
593
|
+
const lines = [];
|
|
594
|
+
for (const field of model.fields) {
|
|
595
|
+
lines.push(buildFieldLine(context, model, field));
|
|
596
|
+
}
|
|
597
|
+
for (const relation of model.relations) {
|
|
598
|
+
const target = findModel(context.schema, relation.targetModel);
|
|
599
|
+
if (target === undefined) {
|
|
600
|
+
context.warnings.push(`${model.name}.${relation.name}: target model "${relation.targetModel}" does not exist in the schema; the relation was skipped.`);
|
|
601
|
+
continue;
|
|
602
|
+
}
|
|
603
|
+
if (relation.kind === 'manyToMany') {
|
|
604
|
+
addImplicitManyToMany(context, model, relation, reverseLines, lines);
|
|
605
|
+
}
|
|
606
|
+
else {
|
|
607
|
+
addForeignKey(context, model, relation, target, reverseLines, lines);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
for (const key of model.compositeForeignKeys ?? []) {
|
|
611
|
+
addCompositeForeignKey(context, model, key, reverseLines, lines);
|
|
612
|
+
}
|
|
613
|
+
return lines;
|
|
614
|
+
}
|
|
615
|
+
/** Reports the places where a field type or column feature has no Prisma equivalent. */
|
|
616
|
+
function warnAboutSpecialField(context, model, field) {
|
|
617
|
+
const label = `${model.name}.${field.name}`;
|
|
618
|
+
const provider = context.options.provider;
|
|
619
|
+
const postgres = isPostgresFamily(provider);
|
|
620
|
+
if (field.type === 'duration') {
|
|
621
|
+
context.warnings.push(`${label}: Prisma has no interval type; the duration was written as BigInt (microseconds).`);
|
|
622
|
+
}
|
|
623
|
+
if (field.type === 'hstore') {
|
|
624
|
+
context.warnings.push(`${label}: Prisma has no hstore type; the field was written as Json.`);
|
|
625
|
+
}
|
|
626
|
+
if (field.type === 'range') {
|
|
627
|
+
context.warnings.push(postgres
|
|
628
|
+
? `${label}: range columns are written as Unsupported("${rangeTypeName(field)}"), which Prisma Client cannot read or filter.`
|
|
629
|
+
: `${label}: range columns exist only on PostgreSQL; the field was written as String.`);
|
|
630
|
+
}
|
|
631
|
+
if (field.type === 'unsupported') {
|
|
632
|
+
context.warnings.push(`${label}: Unsupported("${field.unsupportedType ?? ''}") is kept as an opaque column; Prisma Client cannot read, filter or write it.`);
|
|
633
|
+
}
|
|
634
|
+
if (field.generated !== undefined) {
|
|
635
|
+
context.warnings.push(`${label}: the generated column expression ${field.generated.expression} has no Prisma equivalent; ` +
|
|
636
|
+
`it was written as a regular column, so Prisma will try to write to it.`);
|
|
637
|
+
}
|
|
638
|
+
if (field.type === 'json' && provider === 'sqlserver') {
|
|
639
|
+
context.warnings.push(`${label}: SQL Server has no Json type in Prisma; the field was written as String @db.NVarChar(Max).`);
|
|
640
|
+
}
|
|
641
|
+
if (field.type === 'decimal' && provider === 'mongodb') {
|
|
642
|
+
context.warnings.push(`${label}: MongoDB does not support Decimal in Prisma; Prisma will reject this field.`);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
function buildFieldLine(context, model, field) {
|
|
646
|
+
const label = `${model.name}.${field.name}`;
|
|
647
|
+
warnAboutSpecialField(context, model, field);
|
|
648
|
+
const mapped = prismaTypeOf(context, field, label);
|
|
649
|
+
let typeText = mapped.type;
|
|
650
|
+
let nativeAttr = mapped.nativeAttr;
|
|
651
|
+
const isUnsupported = typeText.startsWith('Unsupported(');
|
|
652
|
+
const depth = field.arrayDepth ?? 0;
|
|
653
|
+
let isList = false;
|
|
654
|
+
if (depth > 0) {
|
|
655
|
+
const provider = context.options.provider;
|
|
656
|
+
const supportsLists = provider === 'postgresql' ||
|
|
657
|
+
provider === 'cockroachdb' ||
|
|
658
|
+
provider === 'mongodb';
|
|
659
|
+
if (supportsLists && depth === 1 && !isUnsupported) {
|
|
660
|
+
isList = true;
|
|
661
|
+
typeText = `${typeText}[]`;
|
|
662
|
+
if (field.isNullable) {
|
|
663
|
+
context.warnings.push(`${label}: Prisma lists cannot be optional; the nullable array was written as a required list.`);
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
else {
|
|
667
|
+
context.warnings.push(supportsLists
|
|
668
|
+
? `${label}: Prisma supports only one-dimensional scalar lists; the ${depth}-dimensional array was written as Json.`
|
|
669
|
+
: `${label}: ${provider} has no scalar list type in Prisma; the array was written as Json.`);
|
|
670
|
+
const jsonFallback = context.options.provider !== 'sqlserver';
|
|
671
|
+
typeText = jsonFallback ? 'Json' : 'String';
|
|
672
|
+
nativeAttr = jsonFallback ? undefined : '@db.NVarChar(Max)';
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
const attrs = [];
|
|
676
|
+
if (field.isPrimaryKey) {
|
|
677
|
+
attrs.push(idAttribute(context, model));
|
|
678
|
+
}
|
|
679
|
+
let defaultAttr;
|
|
680
|
+
if (depth > 0 && isList) {
|
|
681
|
+
defaultAttr =
|
|
682
|
+
field.default?.kind === 'literal' && field.default.value === '[]'
|
|
683
|
+
? '@default([])'
|
|
684
|
+
: undefined;
|
|
685
|
+
}
|
|
686
|
+
else if (field.type === 'range') {
|
|
687
|
+
defaultAttr = undefined;
|
|
688
|
+
}
|
|
689
|
+
else if (isUnsupported) {
|
|
690
|
+
defaultAttr =
|
|
691
|
+
field.default?.kind === 'dbExpression'
|
|
692
|
+
? defaultAttrOf(context, field, label)
|
|
693
|
+
: undefined;
|
|
694
|
+
}
|
|
695
|
+
else {
|
|
696
|
+
defaultAttr = defaultAttrOf(context, field, label);
|
|
697
|
+
}
|
|
698
|
+
if (defaultAttr !== undefined) {
|
|
699
|
+
attrs.push(defaultAttr);
|
|
700
|
+
}
|
|
701
|
+
if (field.isUnique && !field.isPrimaryKey && field.type !== 'range') {
|
|
702
|
+
attrs.push(field.uniqueName === undefined
|
|
703
|
+
? '@unique'
|
|
704
|
+
: `@unique(map: ${JSON.stringify(field.uniqueName)})`);
|
|
705
|
+
}
|
|
706
|
+
if (field.isAutoUpdated) {
|
|
707
|
+
attrs.push('@updatedAt');
|
|
708
|
+
}
|
|
709
|
+
const name = displayName(context, field.name);
|
|
710
|
+
if (field.columnName !== name) {
|
|
711
|
+
attrs.push(`@map(${JSON.stringify(field.columnName)})`);
|
|
712
|
+
}
|
|
713
|
+
if (nativeAttr !== undefined) {
|
|
714
|
+
attrs.push(nativeAttr);
|
|
715
|
+
}
|
|
716
|
+
if (field.isIgnored === true) {
|
|
717
|
+
attrs.push('@ignore');
|
|
718
|
+
}
|
|
719
|
+
const optionalMark = field.isNullable && !isList ? '?' : '';
|
|
720
|
+
return { name, type: `${typeText}${optionalMark}`, attrs };
|
|
721
|
+
}
|
|
722
|
+
/** `@id`, with the constraint name when the source gave one and the key is a single column. */
|
|
723
|
+
function idAttribute(context, model) {
|
|
724
|
+
if (model.primaryKeyName === undefined ||
|
|
725
|
+
model.compositePrimaryKey !== undefined) {
|
|
726
|
+
return '@id';
|
|
727
|
+
}
|
|
728
|
+
return withPrimaryKeyName(context, model, '@id', (name) => `@id(map: ${JSON.stringify(name)})`);
|
|
729
|
+
}
|
|
730
|
+
/** Applies the primary-key constraint name when the provider supports it, otherwise warns. */
|
|
731
|
+
function withPrimaryKeyName(context, model, plain, named) {
|
|
732
|
+
if (model.primaryKeyName === undefined) {
|
|
733
|
+
return plain;
|
|
734
|
+
}
|
|
735
|
+
if (supportsPrimaryKeyNames(context.options.provider)) {
|
|
736
|
+
return named(model.primaryKeyName);
|
|
737
|
+
}
|
|
738
|
+
context.warnings.push(`${model.name}: ${context.options.provider} does not support named primary keys; the constraint name "${model.primaryKeyName}" was dropped.`);
|
|
739
|
+
return plain;
|
|
740
|
+
}
|
|
741
|
+
/** The arguments after `fields` / `references`: referential actions and the constraint name. */
|
|
742
|
+
function relationTail(context, label, onDelete, onUpdate, constraintName) {
|
|
743
|
+
const provider = context.options.provider;
|
|
744
|
+
const action = (value) => {
|
|
745
|
+
if (provider === 'sqlserver' && value === 'restrict') {
|
|
746
|
+
context.warnings.push(`${label}: SQL Server has no Restrict referential action; NoAction was written instead.`);
|
|
747
|
+
return ON_DELETE_NAMES.noAction;
|
|
748
|
+
}
|
|
749
|
+
return ON_DELETE_NAMES[value];
|
|
750
|
+
};
|
|
751
|
+
const tail = [`onDelete: ${action(onDelete)}`];
|
|
752
|
+
if (onUpdate !== undefined) {
|
|
753
|
+
tail.push(`onUpdate: ${action(onUpdate)}`);
|
|
754
|
+
}
|
|
755
|
+
if (constraintName !== undefined) {
|
|
756
|
+
if (supportsForeignKeyNames(provider)) {
|
|
757
|
+
tail.push(`map: ${JSON.stringify(constraintName)}`);
|
|
758
|
+
}
|
|
759
|
+
else {
|
|
760
|
+
context.warnings.push(`${label}: ${provider} does not support named foreign keys; the constraint name "${constraintName}" was dropped.`);
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
return tail;
|
|
764
|
+
}
|
|
765
|
+
function addForeignKey(context, model, relation, target, reverseLines, lines) {
|
|
766
|
+
const key = referencedKey(context, target, relation.toField);
|
|
767
|
+
if (key === undefined) {
|
|
768
|
+
context.warnings.push(`${model.name}.${relation.name}: target model "${target.name}" has no single-column primary key to reference ` +
|
|
769
|
+
`(composite keys cannot be referenced here); the relation was skipped.`);
|
|
770
|
+
return;
|
|
771
|
+
}
|
|
772
|
+
const scalarName = scalarNameOf(context, relation);
|
|
773
|
+
const relationName = displayName(context, relation.name);
|
|
774
|
+
const optionalMark = relation.isNullable ? '?' : '';
|
|
775
|
+
const scalarAttrs = [];
|
|
776
|
+
if (relation.isPrimaryKey === true) {
|
|
777
|
+
scalarAttrs.push(idAttribute(context, model));
|
|
778
|
+
}
|
|
779
|
+
else if (relation.kind === 'oneToOne') {
|
|
780
|
+
scalarAttrs.push('@unique');
|
|
781
|
+
}
|
|
782
|
+
if (relation.columnName !== scalarName) {
|
|
783
|
+
scalarAttrs.push(`@map(${JSON.stringify(relation.columnName)})`);
|
|
784
|
+
}
|
|
785
|
+
if (key.nativeAttr !== undefined) {
|
|
786
|
+
scalarAttrs.push(key.nativeAttr);
|
|
787
|
+
}
|
|
788
|
+
lines.push({
|
|
789
|
+
name: scalarName,
|
|
790
|
+
type: `${key.type}${optionalMark}`,
|
|
791
|
+
attrs: scalarAttrs,
|
|
792
|
+
});
|
|
793
|
+
const explicitName = relationNameFor(context, model, relation.name, relation.targetModel);
|
|
794
|
+
const relationArgs = [];
|
|
795
|
+
if (explicitName !== undefined) {
|
|
796
|
+
relationArgs.push(JSON.stringify(explicitName));
|
|
797
|
+
}
|
|
798
|
+
relationArgs.push(`fields: [${scalarName}]`, `references: [${key.fieldName}]`, ...relationTail(context, `${model.name}.${relation.name}`, relation.onDelete, relation.onUpdate, relation.constraintName));
|
|
799
|
+
lines.push({
|
|
800
|
+
name: relationName,
|
|
801
|
+
type: `${target.name}${optionalMark}`,
|
|
802
|
+
attrs: [`@relation(${relationArgs.join(', ')})`],
|
|
803
|
+
});
|
|
804
|
+
const defaultReverse = relation.kind === 'oneToOne'
|
|
805
|
+
? toSnakeCase(model.name)
|
|
806
|
+
: `${toSnakeCase(model.name)}_set`;
|
|
807
|
+
const reverseName = displayName(context, relation.relatedName ?? defaultReverse);
|
|
808
|
+
const reverseType = relation.kind === 'oneToOne' ? `${model.name}?` : `${model.name}[]`;
|
|
809
|
+
const reverseAttrs = explicitName === undefined
|
|
810
|
+
? []
|
|
811
|
+
: [`@relation(${JSON.stringify(explicitName)})`];
|
|
812
|
+
addReverseLine(reverseLines, target.name, {
|
|
813
|
+
name: reverseName,
|
|
814
|
+
type: reverseType,
|
|
815
|
+
attrs: reverseAttrs,
|
|
816
|
+
});
|
|
817
|
+
}
|
|
818
|
+
/** Writes a multi-column foreign key; the local columns are ordinary fields of the model. */
|
|
819
|
+
function addCompositeForeignKey(context, model, key, reverseLines, lines) {
|
|
820
|
+
const label = `${model.name}.${key.name}`;
|
|
821
|
+
const target = findModel(context.schema, key.targetModel);
|
|
822
|
+
if (target === undefined) {
|
|
823
|
+
context.warnings.push(`${label}: target model "${key.targetModel}" does not exist in the schema; the composite foreign key was skipped.`);
|
|
824
|
+
return;
|
|
825
|
+
}
|
|
826
|
+
const missingLocal = key.fields.find((name) => !model.fields.some((field) => field.name === name));
|
|
827
|
+
const missingTarget = key.references.find((name) => !target.fields.some((field) => field.name === name));
|
|
828
|
+
if (missingLocal !== undefined ||
|
|
829
|
+
missingTarget !== undefined ||
|
|
830
|
+
key.fields.length !== key.references.length) {
|
|
831
|
+
context.warnings.push(`${label}: the composite foreign key (${key.fields.join(', ')}) -> ${target.name}(${key.references.join(', ')}) ` +
|
|
832
|
+
`uses fields that do not exist or do not pair up; it was skipped.`);
|
|
833
|
+
return;
|
|
834
|
+
}
|
|
835
|
+
const optionalMark = key.isNullable ? '?' : '';
|
|
836
|
+
const explicitName = relationNameFor(context, model, key.name, key.targetModel);
|
|
837
|
+
const relationArgs = [];
|
|
838
|
+
if (explicitName !== undefined) {
|
|
839
|
+
relationArgs.push(JSON.stringify(explicitName));
|
|
840
|
+
}
|
|
841
|
+
relationArgs.push(`fields: [${key.fields.map((name) => displayName(context, name)).join(', ')}]`, `references: [${key.references.map((name) => displayName(context, name)).join(', ')}]`, ...relationTail(context, label, key.onDelete, key.onUpdate, key.constraintName));
|
|
842
|
+
lines.push({
|
|
843
|
+
name: displayName(context, key.name),
|
|
844
|
+
type: `${target.name}${optionalMark}`,
|
|
845
|
+
attrs: [`@relation(${relationArgs.join(', ')})`],
|
|
846
|
+
});
|
|
847
|
+
const defaultReverse = key.kind === 'oneToOne'
|
|
848
|
+
? toSnakeCase(model.name)
|
|
849
|
+
: `${toSnakeCase(model.name)}_set`;
|
|
850
|
+
addReverseLine(reverseLines, target.name, {
|
|
851
|
+
name: displayName(context, key.relatedName ?? defaultReverse),
|
|
852
|
+
type: key.kind === 'oneToOne' ? `${model.name}?` : `${model.name}[]`,
|
|
853
|
+
attrs: explicitName === undefined
|
|
854
|
+
? []
|
|
855
|
+
: [`@relation(${JSON.stringify(explicitName)})`],
|
|
856
|
+
});
|
|
857
|
+
}
|
|
858
|
+
function addImplicitManyToMany(context, model, relation, reverseLines, lines) {
|
|
859
|
+
const explicitName = relationNameFor(context, model, relation.name, relation.targetModel);
|
|
860
|
+
const attrs = explicitName === undefined
|
|
861
|
+
? []
|
|
862
|
+
: [`@relation(${JSON.stringify(explicitName)})`];
|
|
863
|
+
lines.push({
|
|
864
|
+
name: displayName(context, relation.name),
|
|
865
|
+
type: `${relation.targetModel}[]`,
|
|
866
|
+
attrs,
|
|
867
|
+
});
|
|
868
|
+
const reverseName = displayName(context, relation.relatedName ?? `${toSnakeCase(model.name)}_set`);
|
|
869
|
+
addReverseLine(reverseLines, relation.targetModel, {
|
|
870
|
+
name: reverseName,
|
|
871
|
+
type: `${model.name}[]`,
|
|
872
|
+
attrs: [...attrs],
|
|
873
|
+
});
|
|
874
|
+
}
|
|
875
|
+
function addReverseLine(reverseLines, targetModel, line) {
|
|
876
|
+
const existing = reverseLines.get(targetModel) ?? [];
|
|
877
|
+
existing.push(line);
|
|
878
|
+
reverseLines.set(targetModel, existing);
|
|
879
|
+
}
|
|
880
|
+
// ---------------------------------------------------------------------------
|
|
881
|
+
// Model blocks
|
|
882
|
+
// ---------------------------------------------------------------------------
|
|
883
|
+
function emitModel(context, model, lines) {
|
|
884
|
+
const body = formatLines(lines);
|
|
885
|
+
const blockAttributes = [];
|
|
886
|
+
if (model.compositePrimaryKey !== undefined &&
|
|
887
|
+
model.compositePrimaryKey.length > 0) {
|
|
888
|
+
const keyFields = resolveIndexFields(context, model, model.compositePrimaryKey).join(', ');
|
|
889
|
+
blockAttributes.push(withPrimaryKeyName(context, model, `@@id([${keyFields}])`, (name) => `@@id([${keyFields}], map: ${JSON.stringify(name)})`));
|
|
890
|
+
}
|
|
891
|
+
for (const index of model.indexes) {
|
|
892
|
+
blockAttributes.push(formatIndex(context, model, index));
|
|
893
|
+
}
|
|
894
|
+
if (model.tableName !== model.name) {
|
|
895
|
+
blockAttributes.push(`@@map(${JSON.stringify(model.tableName)})`);
|
|
896
|
+
}
|
|
897
|
+
if (model.isIgnored === true) {
|
|
898
|
+
blockAttributes.push('@@ignore');
|
|
899
|
+
}
|
|
900
|
+
if (model.schema !== undefined) {
|
|
901
|
+
if (supportsSchemas(context.options.provider)) {
|
|
902
|
+
blockAttributes.push(`@@schema(${JSON.stringify(model.schema)})`);
|
|
903
|
+
}
|
|
904
|
+
else {
|
|
905
|
+
context.warnings.push(`${model.name}: ${context.options.provider} has no database schemas; @@schema("${model.schema}") was dropped.`);
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
const hasPrimaryKey = model.fields.some((field) => field.isPrimaryKey) ||
|
|
909
|
+
model.relations.some((relation) => relation.isPrimaryKey === true) ||
|
|
910
|
+
(model.compositePrimaryKey !== undefined &&
|
|
911
|
+
model.compositePrimaryKey.length > 0);
|
|
912
|
+
if (context.options.provider === 'mongodb' && hasPrimaryKey) {
|
|
913
|
+
const keyedOnId = model.compositePrimaryKey === undefined &&
|
|
914
|
+
model.fields.some((field) => field.isPrimaryKey && field.columnName === '_id');
|
|
915
|
+
if (!keyedOnId) {
|
|
916
|
+
context.warnings.push(`${model.name}: MongoDB needs a single id field mapped to _id (@map("_id")), usually String @db.ObjectId @default(auto()); ` +
|
|
917
|
+
`the key was written as for a SQL database, so Prisma will reject it.`);
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
if (!hasPrimaryKey && model.isView !== true) {
|
|
921
|
+
context.warnings.push(`${model.name}: the model has no primary key; Prisma requires one (@id or @@id).`);
|
|
922
|
+
}
|
|
923
|
+
if (model.isView === true && !hasPrimaryKey) {
|
|
924
|
+
const hasUnique = model.fields.some((field) => field.isUnique) ||
|
|
925
|
+
model.indexes.some((index) => index.isUnique);
|
|
926
|
+
if (!hasUnique) {
|
|
927
|
+
context.warnings.push(`${model.name}: the view has no @id or @unique field; Prisma Client can only read it through findMany-style queries that need a unique key for cursors and relations.`);
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
const keyword = model.isView === true ? 'view' : 'model';
|
|
931
|
+
const parts = [`${keyword} ${model.name} {`, ...body];
|
|
932
|
+
if (blockAttributes.length > 0) {
|
|
933
|
+
parts.push('', ...blockAttributes.map((attribute) => ` ${attribute}`));
|
|
934
|
+
}
|
|
935
|
+
parts.push('}');
|
|
936
|
+
return parts.join('\n');
|
|
937
|
+
}
|
|
938
|
+
function formatLines(lines) {
|
|
939
|
+
if (lines.length === 0) {
|
|
940
|
+
return [];
|
|
941
|
+
}
|
|
942
|
+
const nameWidth = Math.max(...lines.map((line) => line.name.length));
|
|
943
|
+
const typeWidth = Math.max(...lines.map((line) => line.type.length));
|
|
944
|
+
return lines.map((line) => ` ${line.name.padEnd(nameWidth)} ${line.type.padEnd(typeWidth)} ${line.attrs.join(' ')}`.trimEnd());
|
|
945
|
+
}
|
|
946
|
+
function resolveIndexFields(context, model, fieldNames) {
|
|
947
|
+
return fieldNames.map((fieldName) => {
|
|
948
|
+
const relation = model.relations.find((candidate) => candidate.name === fieldName);
|
|
949
|
+
if (relation !== undefined) {
|
|
950
|
+
return scalarNameOf(context, relation);
|
|
951
|
+
}
|
|
952
|
+
const field = model.fields.find((candidate) => candidate.name === fieldName);
|
|
953
|
+
if (field === undefined) {
|
|
954
|
+
context.warnings.push(`${model.name}: an index references "${fieldName}", which is not a field of the model; it was written as-is.`);
|
|
955
|
+
}
|
|
956
|
+
return displayName(context, fieldName);
|
|
957
|
+
});
|
|
958
|
+
}
|
|
959
|
+
/** Writes one index entry with the per-field options the provider accepts. */
|
|
960
|
+
function formatIndexEntry(context, label, name, options) {
|
|
961
|
+
if (options === undefined) {
|
|
962
|
+
return name;
|
|
963
|
+
}
|
|
964
|
+
const provider = context.options.provider;
|
|
965
|
+
const parts = [];
|
|
966
|
+
if (options.sort !== undefined) {
|
|
967
|
+
parts.push(`sort: ${options.sort === 'desc' ? 'Desc' : 'Asc'}`);
|
|
968
|
+
}
|
|
969
|
+
if (options.length !== undefined) {
|
|
970
|
+
if (provider === 'mysql') {
|
|
971
|
+
parts.push(`length: ${options.length}`);
|
|
972
|
+
}
|
|
973
|
+
else {
|
|
974
|
+
context.warnings.push(`${label}: the prefix length on "${name}" is only supported on MySQL and was dropped.`);
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
if (options.ops !== undefined) {
|
|
978
|
+
if (provider === 'postgresql') {
|
|
979
|
+
parts.push(`ops: ${options.ops}`);
|
|
980
|
+
}
|
|
981
|
+
else {
|
|
982
|
+
context.warnings.push(`${label}: the operator class on "${name}" is only supported on PostgreSQL and was dropped.`);
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
return parts.length === 0 ? name : `${name}(${parts.join(', ')})`;
|
|
986
|
+
}
|
|
987
|
+
/** SQL Server cannot index NVarChar(Max) and MySQL needs a prefix length on TEXT columns. */
|
|
988
|
+
function warnAboutUnindexableColumns(context, model, index, label) {
|
|
989
|
+
const provider = context.options.provider;
|
|
990
|
+
if (provider !== 'sqlserver' && provider !== 'mysql') {
|
|
991
|
+
return;
|
|
992
|
+
}
|
|
993
|
+
for (const fieldName of index.fields) {
|
|
994
|
+
const field = model.fields.find((candidate) => candidate.name === fieldName);
|
|
995
|
+
if (field === undefined) {
|
|
996
|
+
continue;
|
|
997
|
+
}
|
|
998
|
+
const native = prismaTypeOf(context, field).nativeAttr;
|
|
999
|
+
if (provider === 'sqlserver' && native === '@db.NVarChar(Max)') {
|
|
1000
|
+
context.warnings.push(`${label}: "${fieldName}" is NVarChar(Max), which SQL Server cannot index or make unique; Prisma will reject the index.`);
|
|
1001
|
+
}
|
|
1002
|
+
const isMysqlText = provider === 'mysql' &&
|
|
1003
|
+
native !== undefined &&
|
|
1004
|
+
/^@db\.(Tiny|Medium|Long)?Text$/.test(native) &&
|
|
1005
|
+
index.fieldOptions?.[fieldName]?.length === undefined &&
|
|
1006
|
+
index.kind !== 'fulltext';
|
|
1007
|
+
if (isMysqlText) {
|
|
1008
|
+
context.warnings.push(`${label}: "${fieldName}" is a TEXT column; MySQL needs a prefix length (e.g. ${fieldName}(length: 191)) to index it, and Prisma will reject the index.`);
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
function formatIndex(context, model, index) {
|
|
1013
|
+
const provider = context.options.provider;
|
|
1014
|
+
const label = `${model.name} index (${index.fields.join(', ')})`;
|
|
1015
|
+
const fullText = index.kind === 'fulltext';
|
|
1016
|
+
let attribute = index.isUnique ? '@@unique' : '@@index';
|
|
1017
|
+
if (fullText) {
|
|
1018
|
+
if (provider === 'mysql' || provider === 'mongodb') {
|
|
1019
|
+
attribute = '@@fulltext';
|
|
1020
|
+
}
|
|
1021
|
+
else {
|
|
1022
|
+
context.warnings.push(`${label}: ${provider} has no @@fulltext index in Prisma; it was written as an ordinary @@index.`);
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
warnAboutUnindexableColumns(context, model, index, label);
|
|
1026
|
+
const resolved = resolveIndexFields(context, model, index.fields);
|
|
1027
|
+
const entries = resolved.map((name, position) => {
|
|
1028
|
+
const options = index.fieldOptions?.[index.fields[position] ?? ''];
|
|
1029
|
+
if (attribute === '@@fulltext' && options !== undefined) {
|
|
1030
|
+
context.warnings.push(`${label}: per-field options on "${name}" are not supported in @@fulltext and were dropped.`);
|
|
1031
|
+
return name;
|
|
1032
|
+
}
|
|
1033
|
+
return formatIndexEntry(context, label, name, options);
|
|
1034
|
+
});
|
|
1035
|
+
const args = [`[${entries.join(', ')}]`];
|
|
1036
|
+
if (index.method !== undefined && attribute !== '@@fulltext') {
|
|
1037
|
+
if (INDEX_METHODS[provider]?.includes(index.method) === true) {
|
|
1038
|
+
args.push(`type: ${index.method}`);
|
|
1039
|
+
}
|
|
1040
|
+
else if (index.method !== 'BTree') {
|
|
1041
|
+
// BTree is every database's default index type, so dropping it loses nothing.
|
|
1042
|
+
context.warnings.push(`${label}: the index type ${index.method} is not supported on ${provider} and was dropped.`);
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
if (index.clustered !== undefined) {
|
|
1046
|
+
if (provider === 'sqlserver') {
|
|
1047
|
+
args.push(`clustered: ${String(index.clustered)}`);
|
|
1048
|
+
}
|
|
1049
|
+
else {
|
|
1050
|
+
context.warnings.push(`${label}: clustered indexes are only supported on SQL Server and the setting was dropped.`);
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
if (index.name !== undefined) {
|
|
1054
|
+
args.push(`map: ${JSON.stringify(index.name)}`);
|
|
1055
|
+
}
|
|
1056
|
+
return `${attribute}(${args.join(', ')})`;
|
|
1057
|
+
}
|
|
1058
|
+
//# sourceMappingURL=prisma.js.map
|