@prisma-next/sql-contract-psl 0.3.0-dev.55 → 0.3.0-dev.64
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 +201 -0
- package/README.md +24 -0
- package/dist/index.d.mts +40 -2
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/interpreter-IXr5c7s7.mjs +1376 -0
- package/dist/interpreter-IXr5c7s7.mjs.map +1 -0
- package/dist/provider.d.mts +1 -1
- package/dist/provider.d.mts.map +1 -1
- package/dist/provider.mjs +4 -3
- package/dist/provider.mjs.map +1 -1
- package/package.json +6 -6
- package/src/default-function-registry.ts +510 -0
- package/src/interpreter.ts +629 -67
- package/src/provider.ts +3 -1
- package/dist/interpreter-_6-Xk1_m.mjs +0 -661
- package/dist/interpreter-_6-Xk1_m.mjs.map +0 -1
|
@@ -0,0 +1,1376 @@
|
|
|
1
|
+
import { defineContract } from "@prisma-next/sql-contract-ts/contract-builder";
|
|
2
|
+
import { assertDefined, invariant } from "@prisma-next/utils/assertions";
|
|
3
|
+
import { ifDefined } from "@prisma-next/utils/defined";
|
|
4
|
+
import { notOk, ok } from "@prisma-next/utils/result";
|
|
5
|
+
|
|
6
|
+
//#region src/default-function-registry.ts
|
|
7
|
+
function resolveSpanPositionFromBase(base, text, offset) {
|
|
8
|
+
const safeOffset = Math.min(Math.max(0, offset), text.length);
|
|
9
|
+
let line = base.start.line;
|
|
10
|
+
let column = base.start.column;
|
|
11
|
+
for (let index = 0; index < safeOffset; index += 1) {
|
|
12
|
+
const character = text[index] ?? "";
|
|
13
|
+
if (character === "\r") {
|
|
14
|
+
if (text[index + 1] === "\n" && index + 1 < safeOffset) index += 1;
|
|
15
|
+
line += 1;
|
|
16
|
+
column = 1;
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
if (character === "\n") {
|
|
20
|
+
line += 1;
|
|
21
|
+
column = 1;
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
column += 1;
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
offset: base.start.offset + safeOffset,
|
|
28
|
+
line,
|
|
29
|
+
column
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function createSpanFromBase(base, startOffset, endOffset, text) {
|
|
33
|
+
const safeStart = Math.max(0, Math.min(startOffset, text.length));
|
|
34
|
+
const safeEnd = Math.max(safeStart, Math.min(endOffset, text.length));
|
|
35
|
+
return {
|
|
36
|
+
start: resolveSpanPositionFromBase(base, text, safeStart),
|
|
37
|
+
end: resolveSpanPositionFromBase(base, text, safeEnd)
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function splitTopLevelArgs(raw) {
|
|
41
|
+
if (raw.trim().length === 0) return [];
|
|
42
|
+
const parts = [];
|
|
43
|
+
let depthParen = 0;
|
|
44
|
+
let depthBracket = 0;
|
|
45
|
+
let quote = null;
|
|
46
|
+
let start = 0;
|
|
47
|
+
for (let index = 0; index < raw.length; index += 1) {
|
|
48
|
+
const character = raw[index] ?? "";
|
|
49
|
+
if (quote) {
|
|
50
|
+
if (character === quote && raw[index - 1] !== "\\") quote = null;
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
if (character === "\"" || character === "'") {
|
|
54
|
+
quote = character;
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (character === "(") {
|
|
58
|
+
depthParen += 1;
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
if (character === ")") {
|
|
62
|
+
depthParen = Math.max(0, depthParen - 1);
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
if (character === "[") {
|
|
66
|
+
depthBracket += 1;
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
if (character === "]") {
|
|
70
|
+
depthBracket = Math.max(0, depthBracket - 1);
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
if (character === "," && depthParen === 0 && depthBracket === 0) {
|
|
74
|
+
parts.push({
|
|
75
|
+
raw: raw.slice(start, index),
|
|
76
|
+
start,
|
|
77
|
+
end: index
|
|
78
|
+
});
|
|
79
|
+
start = index + 1;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
parts.push({
|
|
83
|
+
raw: raw.slice(start),
|
|
84
|
+
start,
|
|
85
|
+
end: raw.length
|
|
86
|
+
});
|
|
87
|
+
return parts;
|
|
88
|
+
}
|
|
89
|
+
function parseDefaultFunctionCall(expression, expressionSpan) {
|
|
90
|
+
const trimmed = expression.trim();
|
|
91
|
+
const leadingWhitespace = expression.length - expression.trimStart().length;
|
|
92
|
+
const trailingWhitespace = expression.length - expression.trimEnd().length;
|
|
93
|
+
const contentEnd = expression.length - trailingWhitespace;
|
|
94
|
+
const openParen = trimmed.indexOf("(");
|
|
95
|
+
const closeParen = trimmed.lastIndexOf(")");
|
|
96
|
+
if (openParen <= 0 || closeParen !== trimmed.length - 1) return;
|
|
97
|
+
const functionName = trimmed.slice(0, openParen).trim();
|
|
98
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(functionName)) return;
|
|
99
|
+
const parts = splitTopLevelArgs(trimmed.slice(openParen + 1, closeParen));
|
|
100
|
+
const args = [];
|
|
101
|
+
for (const part of parts) {
|
|
102
|
+
const raw = part.raw.trim();
|
|
103
|
+
if (raw.length === 0) return;
|
|
104
|
+
const leadingPartWhitespace = part.raw.length - part.raw.trimStart().length;
|
|
105
|
+
const argStart = leadingWhitespace + openParen + 1 + part.start + leadingPartWhitespace;
|
|
106
|
+
const argEnd = argStart + raw.length;
|
|
107
|
+
args.push({
|
|
108
|
+
raw,
|
|
109
|
+
span: createSpanFromBase(expressionSpan, argStart, argEnd, expression)
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
name: functionName,
|
|
114
|
+
raw: trimmed,
|
|
115
|
+
args,
|
|
116
|
+
span: createSpanFromBase(expressionSpan, leadingWhitespace, contentEnd, expression)
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
function invalidArgumentDiagnostic(input) {
|
|
120
|
+
return {
|
|
121
|
+
ok: false,
|
|
122
|
+
diagnostic: {
|
|
123
|
+
code: "PSL_INVALID_DEFAULT_FUNCTION_ARGUMENT",
|
|
124
|
+
message: input.message,
|
|
125
|
+
sourceId: input.context.sourceId,
|
|
126
|
+
span: input.span
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
function executionGenerator(id, params) {
|
|
131
|
+
return {
|
|
132
|
+
ok: true,
|
|
133
|
+
value: {
|
|
134
|
+
kind: "execution",
|
|
135
|
+
generated: {
|
|
136
|
+
kind: "generator",
|
|
137
|
+
id,
|
|
138
|
+
...params ? { params } : {}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
function expectNoArgs(input) {
|
|
144
|
+
if (input.call.args.length === 0) return;
|
|
145
|
+
return invalidArgumentDiagnostic({
|
|
146
|
+
context: input.context,
|
|
147
|
+
span: input.call.span,
|
|
148
|
+
message: `Default function "${input.call.name}" does not accept arguments. Use ${input.usage}.`
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
function parseIntegerArgument(raw) {
|
|
152
|
+
const trimmed = raw.trim();
|
|
153
|
+
if (!/^-?\d+$/.test(trimmed)) return;
|
|
154
|
+
const value = Number(trimmed);
|
|
155
|
+
if (!Number.isInteger(value)) return;
|
|
156
|
+
return value;
|
|
157
|
+
}
|
|
158
|
+
function parseStringLiteral(raw) {
|
|
159
|
+
const match = raw.trim().match(/^(['"])(.*)\1$/s);
|
|
160
|
+
if (!match) return;
|
|
161
|
+
return match[2] ?? "";
|
|
162
|
+
}
|
|
163
|
+
function lowerAutoincrement(input) {
|
|
164
|
+
const maybeNoArgs = expectNoArgs({
|
|
165
|
+
call: input.call,
|
|
166
|
+
context: input.context,
|
|
167
|
+
usage: "`autoincrement()`"
|
|
168
|
+
});
|
|
169
|
+
if (maybeNoArgs) return maybeNoArgs;
|
|
170
|
+
return {
|
|
171
|
+
ok: true,
|
|
172
|
+
value: {
|
|
173
|
+
kind: "storage",
|
|
174
|
+
defaultValue: {
|
|
175
|
+
kind: "function",
|
|
176
|
+
expression: "autoincrement()"
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
function lowerNow(input) {
|
|
182
|
+
const maybeNoArgs = expectNoArgs({
|
|
183
|
+
call: input.call,
|
|
184
|
+
context: input.context,
|
|
185
|
+
usage: "`now()`"
|
|
186
|
+
});
|
|
187
|
+
if (maybeNoArgs) return maybeNoArgs;
|
|
188
|
+
return {
|
|
189
|
+
ok: true,
|
|
190
|
+
value: {
|
|
191
|
+
kind: "storage",
|
|
192
|
+
defaultValue: {
|
|
193
|
+
kind: "function",
|
|
194
|
+
expression: "now()"
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
function lowerUuid(input) {
|
|
200
|
+
if (input.call.args.length === 0) return executionGenerator("uuidv4");
|
|
201
|
+
if (input.call.args.length !== 1) return invalidArgumentDiagnostic({
|
|
202
|
+
context: input.context,
|
|
203
|
+
span: input.call.span,
|
|
204
|
+
message: "Default function \"uuid\" accepts at most one version argument: `uuid()`, `uuid(4)`, or `uuid(7)`."
|
|
205
|
+
});
|
|
206
|
+
const version = parseIntegerArgument(input.call.args[0]?.raw ?? "");
|
|
207
|
+
if (version === 4) return executionGenerator("uuidv4");
|
|
208
|
+
if (version === 7) return executionGenerator("uuidv7");
|
|
209
|
+
return invalidArgumentDiagnostic({
|
|
210
|
+
context: input.context,
|
|
211
|
+
span: input.call.args[0]?.span ?? input.call.span,
|
|
212
|
+
message: "Default function \"uuid\" supports only `uuid()`, `uuid(4)`, or `uuid(7)` in SQL PSL provider v1."
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
function lowerCuid(input) {
|
|
216
|
+
if (input.call.args.length === 0) return {
|
|
217
|
+
ok: false,
|
|
218
|
+
diagnostic: {
|
|
219
|
+
code: "PSL_UNKNOWN_DEFAULT_FUNCTION",
|
|
220
|
+
message: "Default function \"cuid()\" is not supported in SQL PSL provider v1. Use `cuid(2)` instead.",
|
|
221
|
+
sourceId: input.context.sourceId,
|
|
222
|
+
span: input.call.span
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
if (input.call.args.length !== 1) return invalidArgumentDiagnostic({
|
|
226
|
+
context: input.context,
|
|
227
|
+
span: input.call.span,
|
|
228
|
+
message: "Default function \"cuid\" accepts exactly one version argument: `cuid(2)`."
|
|
229
|
+
});
|
|
230
|
+
if (parseIntegerArgument(input.call.args[0]?.raw ?? "") === 2) return executionGenerator("cuid2");
|
|
231
|
+
return invalidArgumentDiagnostic({
|
|
232
|
+
context: input.context,
|
|
233
|
+
span: input.call.args[0]?.span ?? input.call.span,
|
|
234
|
+
message: "Default function \"cuid\" supports only `cuid(2)` in SQL PSL provider v1."
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
function lowerUlid(input) {
|
|
238
|
+
const maybeNoArgs = expectNoArgs({
|
|
239
|
+
call: input.call,
|
|
240
|
+
context: input.context,
|
|
241
|
+
usage: "`ulid()`"
|
|
242
|
+
});
|
|
243
|
+
if (maybeNoArgs) return maybeNoArgs;
|
|
244
|
+
return executionGenerator("ulid");
|
|
245
|
+
}
|
|
246
|
+
function lowerNanoid(input) {
|
|
247
|
+
if (input.call.args.length === 0) return executionGenerator("nanoid");
|
|
248
|
+
if (input.call.args.length !== 1) return invalidArgumentDiagnostic({
|
|
249
|
+
context: input.context,
|
|
250
|
+
span: input.call.span,
|
|
251
|
+
message: "Default function \"nanoid\" accepts at most one size argument: `nanoid()` or `nanoid(<2-255>)`."
|
|
252
|
+
});
|
|
253
|
+
const size = parseIntegerArgument(input.call.args[0]?.raw ?? "");
|
|
254
|
+
if (size !== void 0 && size >= 2 && size <= 255) return executionGenerator("nanoid", { size });
|
|
255
|
+
return invalidArgumentDiagnostic({
|
|
256
|
+
context: input.context,
|
|
257
|
+
span: input.call.args[0]?.span ?? input.call.span,
|
|
258
|
+
message: "Default function \"nanoid\" size argument must be an integer between 2 and 255."
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
function lowerDbgenerated(input) {
|
|
262
|
+
if (input.call.args.length !== 1) return invalidArgumentDiagnostic({
|
|
263
|
+
context: input.context,
|
|
264
|
+
span: input.call.span,
|
|
265
|
+
message: "Default function \"dbgenerated\" requires exactly one string argument: `dbgenerated(\"...\")`."
|
|
266
|
+
});
|
|
267
|
+
const rawExpression = parseStringLiteral(input.call.args[0]?.raw ?? "");
|
|
268
|
+
if (rawExpression === void 0) return invalidArgumentDiagnostic({
|
|
269
|
+
context: input.context,
|
|
270
|
+
span: input.call.args[0]?.span ?? input.call.span,
|
|
271
|
+
message: "Default function \"dbgenerated\" argument must be a string literal."
|
|
272
|
+
});
|
|
273
|
+
if (rawExpression.trim().length === 0) return invalidArgumentDiagnostic({
|
|
274
|
+
context: input.context,
|
|
275
|
+
span: input.call.args[0]?.span ?? input.call.span,
|
|
276
|
+
message: "Default function \"dbgenerated\" argument cannot be empty."
|
|
277
|
+
});
|
|
278
|
+
return {
|
|
279
|
+
ok: true,
|
|
280
|
+
value: {
|
|
281
|
+
kind: "storage",
|
|
282
|
+
defaultValue: {
|
|
283
|
+
kind: "function",
|
|
284
|
+
expression: rawExpression
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
const supportedFunctionUsageByName = {
|
|
290
|
+
autoincrement: ["autoincrement()"],
|
|
291
|
+
now: ["now()"],
|
|
292
|
+
uuid: [
|
|
293
|
+
"uuid()",
|
|
294
|
+
"uuid(4)",
|
|
295
|
+
"uuid(7)"
|
|
296
|
+
],
|
|
297
|
+
cuid: ["cuid(2)"],
|
|
298
|
+
ulid: ["ulid()"],
|
|
299
|
+
nanoid: ["nanoid()", "nanoid(n)"],
|
|
300
|
+
dbgenerated: ["dbgenerated(\"...\")"]
|
|
301
|
+
};
|
|
302
|
+
const unknownFunctionSuggestionsByName = {
|
|
303
|
+
cuid2: "Use `cuid(2)`.",
|
|
304
|
+
uuidv4: "Use `uuid()` or `uuid(4)`.",
|
|
305
|
+
uuidv7: "Use `uuid(7)`."
|
|
306
|
+
};
|
|
307
|
+
function formatSupportedFunctionList(registry) {
|
|
308
|
+
const signatures = Array.from(registry.keys()).sort().flatMap((functionName) => supportedFunctionUsageByName[functionName] ?? [`${functionName}()`]);
|
|
309
|
+
return signatures.length > 0 ? signatures.join(", ") : "none";
|
|
310
|
+
}
|
|
311
|
+
function createBuiltinDefaultFunctionRegistry() {
|
|
312
|
+
return new Map([
|
|
313
|
+
["autoincrement", lowerAutoincrement],
|
|
314
|
+
["now", lowerNow],
|
|
315
|
+
["uuid", lowerUuid],
|
|
316
|
+
["cuid", lowerCuid],
|
|
317
|
+
["ulid", lowerUlid],
|
|
318
|
+
["nanoid", lowerNanoid],
|
|
319
|
+
["dbgenerated", lowerDbgenerated]
|
|
320
|
+
]);
|
|
321
|
+
}
|
|
322
|
+
function lowerDefaultFunctionWithRegistry(input) {
|
|
323
|
+
const handler = input.registry.get(input.call.name);
|
|
324
|
+
if (handler) return handler({
|
|
325
|
+
call: input.call,
|
|
326
|
+
context: input.context
|
|
327
|
+
});
|
|
328
|
+
const supportedFunctionList = formatSupportedFunctionList(input.registry);
|
|
329
|
+
const suggestion = unknownFunctionSuggestionsByName[input.call.name];
|
|
330
|
+
return {
|
|
331
|
+
ok: false,
|
|
332
|
+
diagnostic: {
|
|
333
|
+
code: "PSL_UNKNOWN_DEFAULT_FUNCTION",
|
|
334
|
+
message: `Default function "${input.call.name}" is not supported in SQL PSL provider v1. Supported functions: ${supportedFunctionList}.${suggestion ? ` ${suggestion}` : ""}`,
|
|
335
|
+
sourceId: input.context.sourceId,
|
|
336
|
+
span: input.call.span
|
|
337
|
+
}
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
//#endregion
|
|
342
|
+
//#region src/interpreter.ts
|
|
343
|
+
const DEFAULT_POSTGRES_TARGET = {
|
|
344
|
+
kind: "target",
|
|
345
|
+
familyId: "sql",
|
|
346
|
+
targetId: "postgres",
|
|
347
|
+
id: "postgres",
|
|
348
|
+
version: "0.0.1",
|
|
349
|
+
capabilities: {}
|
|
350
|
+
};
|
|
351
|
+
const SCALAR_COLUMN_MAP = {
|
|
352
|
+
String: {
|
|
353
|
+
codecId: "pg/text@1",
|
|
354
|
+
nativeType: "text"
|
|
355
|
+
},
|
|
356
|
+
Boolean: {
|
|
357
|
+
codecId: "pg/bool@1",
|
|
358
|
+
nativeType: "bool"
|
|
359
|
+
},
|
|
360
|
+
Int: {
|
|
361
|
+
codecId: "pg/int4@1",
|
|
362
|
+
nativeType: "int4"
|
|
363
|
+
},
|
|
364
|
+
BigInt: {
|
|
365
|
+
codecId: "pg/int8@1",
|
|
366
|
+
nativeType: "int8"
|
|
367
|
+
},
|
|
368
|
+
Float: {
|
|
369
|
+
codecId: "pg/float8@1",
|
|
370
|
+
nativeType: "float8"
|
|
371
|
+
},
|
|
372
|
+
Decimal: {
|
|
373
|
+
codecId: "pg/numeric@1",
|
|
374
|
+
nativeType: "numeric"
|
|
375
|
+
},
|
|
376
|
+
DateTime: {
|
|
377
|
+
codecId: "pg/timestamptz@1",
|
|
378
|
+
nativeType: "timestamptz"
|
|
379
|
+
},
|
|
380
|
+
Json: {
|
|
381
|
+
codecId: "pg/jsonb@1",
|
|
382
|
+
nativeType: "jsonb"
|
|
383
|
+
},
|
|
384
|
+
Bytes: {
|
|
385
|
+
codecId: "pg/bytea@1",
|
|
386
|
+
nativeType: "bytea"
|
|
387
|
+
}
|
|
388
|
+
};
|
|
389
|
+
const GENERATED_ID_COLUMN_MAP = {
|
|
390
|
+
ulid: {
|
|
391
|
+
codecId: "sql/char@1",
|
|
392
|
+
nativeType: "character",
|
|
393
|
+
typeParams: { length: 26 }
|
|
394
|
+
},
|
|
395
|
+
uuidv7: {
|
|
396
|
+
codecId: "sql/char@1",
|
|
397
|
+
nativeType: "character",
|
|
398
|
+
typeParams: { length: 36 }
|
|
399
|
+
},
|
|
400
|
+
uuidv4: {
|
|
401
|
+
codecId: "sql/char@1",
|
|
402
|
+
nativeType: "character",
|
|
403
|
+
typeParams: { length: 36 }
|
|
404
|
+
},
|
|
405
|
+
cuid2: {
|
|
406
|
+
codecId: "sql/char@1",
|
|
407
|
+
nativeType: "character",
|
|
408
|
+
typeParams: { length: 24 }
|
|
409
|
+
}
|
|
410
|
+
};
|
|
411
|
+
function resolveGeneratedColumnDescriptor(executionDefault) {
|
|
412
|
+
if (executionDefault.kind !== "generator") return;
|
|
413
|
+
if (executionDefault.id === "nanoid") {
|
|
414
|
+
const rawSize = executionDefault.params?.["size"];
|
|
415
|
+
return {
|
|
416
|
+
codecId: "sql/char@1",
|
|
417
|
+
nativeType: "character",
|
|
418
|
+
typeParams: { length: typeof rawSize === "number" && Number.isInteger(rawSize) && rawSize >= 2 && rawSize <= 255 ? rawSize : 21 }
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
return GENERATED_ID_COLUMN_MAP[executionDefault.id];
|
|
422
|
+
}
|
|
423
|
+
const REFERENTIAL_ACTION_MAP = {
|
|
424
|
+
NoAction: "noAction",
|
|
425
|
+
Restrict: "restrict",
|
|
426
|
+
Cascade: "cascade",
|
|
427
|
+
SetNull: "setNull",
|
|
428
|
+
SetDefault: "setDefault",
|
|
429
|
+
noAction: "noAction",
|
|
430
|
+
restrict: "restrict",
|
|
431
|
+
cascade: "cascade",
|
|
432
|
+
setNull: "setNull",
|
|
433
|
+
setDefault: "setDefault"
|
|
434
|
+
};
|
|
435
|
+
function fkRelationPairKey(declaringModelName, targetModelName) {
|
|
436
|
+
return `${declaringModelName}::${targetModelName}`;
|
|
437
|
+
}
|
|
438
|
+
function lowerFirst(value) {
|
|
439
|
+
if (value.length === 0) return value;
|
|
440
|
+
return value[0]?.toLowerCase() + value.slice(1);
|
|
441
|
+
}
|
|
442
|
+
function getAttribute(attributes, name) {
|
|
443
|
+
return attributes.find((attribute) => attribute.name === name);
|
|
444
|
+
}
|
|
445
|
+
function getNamedArgument(attribute, name) {
|
|
446
|
+
const entry = attribute.args.find((arg) => arg.kind === "named" && arg.name === name);
|
|
447
|
+
if (!entry || entry.kind !== "named") return;
|
|
448
|
+
return entry.value;
|
|
449
|
+
}
|
|
450
|
+
function getPositionalArgument(attribute, index = 0) {
|
|
451
|
+
const entry = attribute.args.filter((arg) => arg.kind === "positional")[index];
|
|
452
|
+
if (!entry || entry.kind !== "positional") return;
|
|
453
|
+
return entry.value;
|
|
454
|
+
}
|
|
455
|
+
function getPositionalArgumentEntry(attribute, index = 0) {
|
|
456
|
+
const entry = attribute.args.filter((arg) => arg.kind === "positional")[index];
|
|
457
|
+
if (!entry || entry.kind !== "positional") return;
|
|
458
|
+
return {
|
|
459
|
+
value: entry.value,
|
|
460
|
+
span: entry.span
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
function unquoteStringLiteral(value) {
|
|
464
|
+
const trimmed = value.trim();
|
|
465
|
+
const match = trimmed.match(/^(['"])(.*)\1$/);
|
|
466
|
+
if (!match) return trimmed;
|
|
467
|
+
return match[2] ?? "";
|
|
468
|
+
}
|
|
469
|
+
function parseQuotedStringLiteral(value) {
|
|
470
|
+
const match = value.trim().match(/^(['"])(.*)\1$/);
|
|
471
|
+
if (!match) return;
|
|
472
|
+
return match[2] ?? "";
|
|
473
|
+
}
|
|
474
|
+
function parseFieldList(value) {
|
|
475
|
+
const trimmed = value.trim();
|
|
476
|
+
if (!trimmed.startsWith("[") || !trimmed.endsWith("]")) return;
|
|
477
|
+
return trimmed.slice(1, -1).split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0);
|
|
478
|
+
}
|
|
479
|
+
function parseDefaultLiteralValue(expression) {
|
|
480
|
+
const trimmed = expression.trim();
|
|
481
|
+
if (trimmed === "true" || trimmed === "false") return {
|
|
482
|
+
kind: "literal",
|
|
483
|
+
value: trimmed === "true"
|
|
484
|
+
};
|
|
485
|
+
const numericValue = Number(trimmed);
|
|
486
|
+
if (!Number.isNaN(numericValue) && trimmed.length > 0 && !/^(['"]).*\1$/.test(trimmed)) return {
|
|
487
|
+
kind: "literal",
|
|
488
|
+
value: numericValue
|
|
489
|
+
};
|
|
490
|
+
if (/^(['"]).*\1$/.test(trimmed)) return {
|
|
491
|
+
kind: "literal",
|
|
492
|
+
value: unquoteStringLiteral(trimmed)
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
function lowerDefaultForField(input) {
|
|
496
|
+
const positionalEntries = input.defaultAttribute.args.filter((arg) => arg.kind === "positional");
|
|
497
|
+
if (input.defaultAttribute.args.filter((arg) => arg.kind === "named").length > 0 || positionalEntries.length !== 1) {
|
|
498
|
+
input.diagnostics.push({
|
|
499
|
+
code: "PSL_INVALID_DEFAULT_FUNCTION_ARGUMENT",
|
|
500
|
+
message: `Field "${input.modelName}.${input.fieldName}" requires exactly one positional @default(...) expression.`,
|
|
501
|
+
sourceId: input.sourceId,
|
|
502
|
+
span: input.defaultAttribute.span
|
|
503
|
+
});
|
|
504
|
+
return {};
|
|
505
|
+
}
|
|
506
|
+
const expressionEntry = getPositionalArgumentEntry(input.defaultAttribute);
|
|
507
|
+
if (!expressionEntry) {
|
|
508
|
+
input.diagnostics.push({
|
|
509
|
+
code: "PSL_INVALID_DEFAULT_FUNCTION_ARGUMENT",
|
|
510
|
+
message: `Field "${input.modelName}.${input.fieldName}" requires a positional @default(...) expression.`,
|
|
511
|
+
sourceId: input.sourceId,
|
|
512
|
+
span: input.defaultAttribute.span
|
|
513
|
+
});
|
|
514
|
+
return {};
|
|
515
|
+
}
|
|
516
|
+
const literalDefault = parseDefaultLiteralValue(expressionEntry.value);
|
|
517
|
+
if (literalDefault) return { defaultValue: literalDefault };
|
|
518
|
+
const defaultFunctionCall = parseDefaultFunctionCall(expressionEntry.value, expressionEntry.span);
|
|
519
|
+
if (!defaultFunctionCall) {
|
|
520
|
+
input.diagnostics.push({
|
|
521
|
+
code: "PSL_INVALID_DEFAULT_VALUE",
|
|
522
|
+
message: `Unsupported default value "${expressionEntry.value}"`,
|
|
523
|
+
sourceId: input.sourceId,
|
|
524
|
+
span: input.defaultAttribute.span
|
|
525
|
+
});
|
|
526
|
+
return {};
|
|
527
|
+
}
|
|
528
|
+
const lowered = lowerDefaultFunctionWithRegistry({
|
|
529
|
+
call: defaultFunctionCall,
|
|
530
|
+
registry: input.defaultFunctionRegistry,
|
|
531
|
+
context: {
|
|
532
|
+
sourceId: input.sourceId,
|
|
533
|
+
modelName: input.modelName,
|
|
534
|
+
fieldName: input.fieldName
|
|
535
|
+
}
|
|
536
|
+
});
|
|
537
|
+
if (!lowered.ok) {
|
|
538
|
+
input.diagnostics.push(lowered.diagnostic);
|
|
539
|
+
return {};
|
|
540
|
+
}
|
|
541
|
+
if (lowered.value.kind === "storage") return { defaultValue: lowered.value.defaultValue };
|
|
542
|
+
return { executionDefault: lowered.value.generated };
|
|
543
|
+
}
|
|
544
|
+
function parseMapName(input) {
|
|
545
|
+
if (!input.attribute) return input.defaultValue;
|
|
546
|
+
const value = getPositionalArgument(input.attribute);
|
|
547
|
+
if (!value) {
|
|
548
|
+
input.diagnostics.push({
|
|
549
|
+
code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
|
|
550
|
+
message: `${input.entityLabel} @map requires a positional quoted string literal argument`,
|
|
551
|
+
sourceId: input.sourceId,
|
|
552
|
+
span: input.attribute.span
|
|
553
|
+
});
|
|
554
|
+
return input.defaultValue;
|
|
555
|
+
}
|
|
556
|
+
const parsed = parseQuotedStringLiteral(value);
|
|
557
|
+
if (parsed === void 0) {
|
|
558
|
+
input.diagnostics.push({
|
|
559
|
+
code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
|
|
560
|
+
message: `${input.entityLabel} @map requires a positional quoted string literal argument`,
|
|
561
|
+
sourceId: input.sourceId,
|
|
562
|
+
span: input.attribute.span
|
|
563
|
+
});
|
|
564
|
+
return input.defaultValue;
|
|
565
|
+
}
|
|
566
|
+
return parsed;
|
|
567
|
+
}
|
|
568
|
+
function parsePgvectorLength(input) {
|
|
569
|
+
const namedLength = getNamedArgument(input.attribute, "length");
|
|
570
|
+
const namedDim = getNamedArgument(input.attribute, "dim");
|
|
571
|
+
const positional = getPositionalArgument(input.attribute);
|
|
572
|
+
const raw = namedLength ?? namedDim ?? positional;
|
|
573
|
+
if (!raw) {
|
|
574
|
+
input.diagnostics.push({
|
|
575
|
+
code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
|
|
576
|
+
message: "@pgvector.column requires length/dim argument",
|
|
577
|
+
sourceId: input.sourceId,
|
|
578
|
+
span: input.attribute.span
|
|
579
|
+
});
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
582
|
+
const parsed = Number(unquoteStringLiteral(raw));
|
|
583
|
+
if (!Number.isInteger(parsed) || parsed < 1) {
|
|
584
|
+
input.diagnostics.push({
|
|
585
|
+
code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
|
|
586
|
+
message: "@pgvector.column length/dim must be a positive integer",
|
|
587
|
+
sourceId: input.sourceId,
|
|
588
|
+
span: input.attribute.span
|
|
589
|
+
});
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
return parsed;
|
|
593
|
+
}
|
|
594
|
+
function resolveColumnDescriptor(field, enumTypeDescriptors, namedTypeDescriptors) {
|
|
595
|
+
if (field.typeRef && namedTypeDescriptors.has(field.typeRef)) return namedTypeDescriptors.get(field.typeRef);
|
|
596
|
+
if (namedTypeDescriptors.has(field.typeName)) return namedTypeDescriptors.get(field.typeName);
|
|
597
|
+
if (enumTypeDescriptors.has(field.typeName)) return enumTypeDescriptors.get(field.typeName);
|
|
598
|
+
return SCALAR_COLUMN_MAP[field.typeName];
|
|
599
|
+
}
|
|
600
|
+
function collectResolvedFields(model, mapping, enumTypeDescriptors, namedTypeDescriptors, namedTypeBaseTypes, modelNames, composedExtensions, defaultFunctionRegistry, diagnostics, sourceId) {
|
|
601
|
+
const resolvedFields = [];
|
|
602
|
+
for (const field of model.fields) {
|
|
603
|
+
if (field.list) {
|
|
604
|
+
if (modelNames.has(field.typeName)) continue;
|
|
605
|
+
diagnostics.push({
|
|
606
|
+
code: "PSL_UNSUPPORTED_FIELD_LIST",
|
|
607
|
+
message: `Field "${model.name}.${field.name}" uses a scalar/storage list type, which is not supported in SQL PSL provider v1. Model-typed lists are only supported as backrelation navigation fields when they match an FK-side relation.`,
|
|
608
|
+
sourceId,
|
|
609
|
+
span: field.span
|
|
610
|
+
});
|
|
611
|
+
continue;
|
|
612
|
+
}
|
|
613
|
+
for (const attribute of field.attributes) {
|
|
614
|
+
if (attribute.name === "id" || attribute.name === "unique" || attribute.name === "default" || attribute.name === "relation" || attribute.name === "map" || attribute.name === "pgvector.column") continue;
|
|
615
|
+
if (attribute.name.startsWith("pgvector.") && !composedExtensions.has("pgvector")) {
|
|
616
|
+
diagnostics.push({
|
|
617
|
+
code: "PSL_EXTENSION_NAMESPACE_NOT_COMPOSED",
|
|
618
|
+
message: `Attribute "@${attribute.name}" uses unrecognized namespace "pgvector". Add extension pack "pgvector" to extensionPacks in prisma-next.config.ts.`,
|
|
619
|
+
sourceId,
|
|
620
|
+
span: attribute.span
|
|
621
|
+
});
|
|
622
|
+
continue;
|
|
623
|
+
}
|
|
624
|
+
diagnostics.push({
|
|
625
|
+
code: "PSL_UNSUPPORTED_FIELD_ATTRIBUTE",
|
|
626
|
+
message: `Field "${model.name}.${field.name}" uses unsupported attribute "@${attribute.name}"`,
|
|
627
|
+
sourceId,
|
|
628
|
+
span: attribute.span
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
if (getAttribute(field.attributes, "relation") && modelNames.has(field.typeName)) continue;
|
|
632
|
+
let descriptor = resolveColumnDescriptor(field, enumTypeDescriptors, namedTypeDescriptors);
|
|
633
|
+
const pgvectorColumnAttribute = getAttribute(field.attributes, "pgvector.column");
|
|
634
|
+
if (pgvectorColumnAttribute) if (!composedExtensions.has("pgvector")) diagnostics.push({
|
|
635
|
+
code: "PSL_EXTENSION_NAMESPACE_NOT_COMPOSED",
|
|
636
|
+
message: "Attribute \"@pgvector.column\" uses unrecognized namespace \"pgvector\". Add extension pack \"pgvector\" to extensionPacks in prisma-next.config.ts.",
|
|
637
|
+
sourceId,
|
|
638
|
+
span: pgvectorColumnAttribute.span
|
|
639
|
+
});
|
|
640
|
+
else if (!(field.typeName === "Bytes" || namedTypeBaseTypes.get(field.typeRef ?? field.typeName) === "Bytes")) diagnostics.push({
|
|
641
|
+
code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
|
|
642
|
+
message: `Field "${model.name}.${field.name}" uses @pgvector.column on unsupported base type "${field.typeName}"`,
|
|
643
|
+
sourceId,
|
|
644
|
+
span: pgvectorColumnAttribute.span
|
|
645
|
+
});
|
|
646
|
+
else {
|
|
647
|
+
const length = parsePgvectorLength({
|
|
648
|
+
attribute: pgvectorColumnAttribute,
|
|
649
|
+
diagnostics,
|
|
650
|
+
sourceId
|
|
651
|
+
});
|
|
652
|
+
if (length !== void 0) descriptor = {
|
|
653
|
+
codecId: "pg/vector@1",
|
|
654
|
+
nativeType: `vector(${length})`,
|
|
655
|
+
typeParams: { length }
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
if (!descriptor) {
|
|
659
|
+
diagnostics.push({
|
|
660
|
+
code: "PSL_UNSUPPORTED_FIELD_TYPE",
|
|
661
|
+
message: `Field "${model.name}.${field.name}" type "${field.typeName}" is not supported in SQL PSL provider v1`,
|
|
662
|
+
sourceId,
|
|
663
|
+
span: field.span
|
|
664
|
+
});
|
|
665
|
+
continue;
|
|
666
|
+
}
|
|
667
|
+
const defaultAttribute = getAttribute(field.attributes, "default");
|
|
668
|
+
const loweredDefault = defaultAttribute ? lowerDefaultForField({
|
|
669
|
+
modelName: model.name,
|
|
670
|
+
fieldName: field.name,
|
|
671
|
+
defaultAttribute,
|
|
672
|
+
sourceId,
|
|
673
|
+
defaultFunctionRegistry,
|
|
674
|
+
diagnostics
|
|
675
|
+
}) : {};
|
|
676
|
+
if (field.optional && loweredDefault.executionDefault) {
|
|
677
|
+
const generatorDescription = loweredDefault.executionDefault.kind === "generator" ? `"${loweredDefault.executionDefault.id}"` : "for this field";
|
|
678
|
+
diagnostics.push({
|
|
679
|
+
code: "PSL_INVALID_DEFAULT_FUNCTION_ARGUMENT",
|
|
680
|
+
message: `Field "${model.name}.${field.name}" cannot be optional when using execution default ${generatorDescription}. Remove "?" or use a storage default.`,
|
|
681
|
+
sourceId,
|
|
682
|
+
span: defaultAttribute?.span ?? field.span
|
|
683
|
+
});
|
|
684
|
+
continue;
|
|
685
|
+
}
|
|
686
|
+
if (loweredDefault.executionDefault) {
|
|
687
|
+
const generatedDescriptor = resolveGeneratedColumnDescriptor(loweredDefault.executionDefault);
|
|
688
|
+
if (generatedDescriptor) descriptor = generatedDescriptor;
|
|
689
|
+
}
|
|
690
|
+
const mappedColumnName = mapping.fieldColumns.get(field.name) ?? field.name;
|
|
691
|
+
resolvedFields.push({
|
|
692
|
+
field,
|
|
693
|
+
columnName: mappedColumnName,
|
|
694
|
+
descriptor,
|
|
695
|
+
...ifDefined("defaultValue", loweredDefault.defaultValue),
|
|
696
|
+
...ifDefined("executionDefault", loweredDefault.executionDefault),
|
|
697
|
+
isId: Boolean(getAttribute(field.attributes, "id")),
|
|
698
|
+
isUnique: Boolean(getAttribute(field.attributes, "unique"))
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
return resolvedFields;
|
|
702
|
+
}
|
|
703
|
+
function hasSameSpan(a, b) {
|
|
704
|
+
return a.start.offset === b.start.offset && a.end.offset === b.end.offset && a.start.line === b.start.line && a.end.line === b.end.line;
|
|
705
|
+
}
|
|
706
|
+
function compareStrings(left, right) {
|
|
707
|
+
if (left < right) return -1;
|
|
708
|
+
if (left > right) return 1;
|
|
709
|
+
return 0;
|
|
710
|
+
}
|
|
711
|
+
function indexFkRelations(input) {
|
|
712
|
+
const modelRelations = /* @__PURE__ */ new Map();
|
|
713
|
+
const fkRelationsByPair = /* @__PURE__ */ new Map();
|
|
714
|
+
for (const relation of input.fkRelationMetadata) {
|
|
715
|
+
const existing = modelRelations.get(relation.declaringModelName);
|
|
716
|
+
const current = existing ?? [];
|
|
717
|
+
if (!existing) modelRelations.set(relation.declaringModelName, current);
|
|
718
|
+
current.push({
|
|
719
|
+
fieldName: relation.declaringFieldName,
|
|
720
|
+
toModel: relation.targetModelName,
|
|
721
|
+
toTable: relation.targetTableName,
|
|
722
|
+
cardinality: "N:1",
|
|
723
|
+
parentTable: relation.declaringTableName,
|
|
724
|
+
parentColumns: relation.localColumns,
|
|
725
|
+
childTable: relation.targetTableName,
|
|
726
|
+
childColumns: relation.referencedColumns
|
|
727
|
+
});
|
|
728
|
+
const pairKey = fkRelationPairKey(relation.declaringModelName, relation.targetModelName);
|
|
729
|
+
const pairRelations = fkRelationsByPair.get(pairKey);
|
|
730
|
+
if (!pairRelations) {
|
|
731
|
+
fkRelationsByPair.set(pairKey, [relation]);
|
|
732
|
+
continue;
|
|
733
|
+
}
|
|
734
|
+
pairRelations.push(relation);
|
|
735
|
+
}
|
|
736
|
+
return {
|
|
737
|
+
modelRelations,
|
|
738
|
+
fkRelationsByPair
|
|
739
|
+
};
|
|
740
|
+
}
|
|
741
|
+
function applyBackrelationCandidates(input) {
|
|
742
|
+
for (const candidate of input.backrelationCandidates) {
|
|
743
|
+
const pairKey = fkRelationPairKey(candidate.targetModelName, candidate.modelName);
|
|
744
|
+
const pairMatches = input.fkRelationsByPair.get(pairKey) ?? [];
|
|
745
|
+
const matches = candidate.relationName ? pairMatches.filter((relation) => relation.relationName === candidate.relationName) : [...pairMatches];
|
|
746
|
+
if (matches.length === 0) {
|
|
747
|
+
input.diagnostics.push({
|
|
748
|
+
code: "PSL_ORPHANED_BACKRELATION_LIST",
|
|
749
|
+
message: `Backrelation list field "${candidate.modelName}.${candidate.field.name}" has no matching FK-side relation on model "${candidate.targetModelName}". Add @relation(fields: [...], references: [...]) on the FK-side relation or use an explicit join model for many-to-many.`,
|
|
750
|
+
sourceId: input.sourceId,
|
|
751
|
+
span: candidate.field.span
|
|
752
|
+
});
|
|
753
|
+
continue;
|
|
754
|
+
}
|
|
755
|
+
if (matches.length > 1) {
|
|
756
|
+
input.diagnostics.push({
|
|
757
|
+
code: "PSL_AMBIGUOUS_BACKRELATION_LIST",
|
|
758
|
+
message: `Backrelation list field "${candidate.modelName}.${candidate.field.name}" matches multiple FK-side relations on model "${candidate.targetModelName}". Add @relation(name: "...") (or @relation("...")) to both sides to disambiguate.`,
|
|
759
|
+
sourceId: input.sourceId,
|
|
760
|
+
span: candidate.field.span
|
|
761
|
+
});
|
|
762
|
+
continue;
|
|
763
|
+
}
|
|
764
|
+
invariant(matches.length === 1, "Backrelation matching requires exactly one match");
|
|
765
|
+
const matched = matches[0];
|
|
766
|
+
assertDefined(matched, "Backrelation matching requires a defined relation match");
|
|
767
|
+
const existing = input.modelRelations.get(candidate.modelName);
|
|
768
|
+
const current = existing ?? [];
|
|
769
|
+
if (!existing) input.modelRelations.set(candidate.modelName, current);
|
|
770
|
+
current.push({
|
|
771
|
+
fieldName: candidate.field.name,
|
|
772
|
+
toModel: matched.declaringModelName,
|
|
773
|
+
toTable: matched.declaringTableName,
|
|
774
|
+
cardinality: "1:N",
|
|
775
|
+
parentTable: candidate.tableName,
|
|
776
|
+
parentColumns: matched.referencedColumns,
|
|
777
|
+
childTable: matched.declaringTableName,
|
|
778
|
+
childColumns: matched.localColumns
|
|
779
|
+
});
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
function emitModelsWithRelations(input) {
|
|
783
|
+
let nextBuilder = input.builder;
|
|
784
|
+
const sortedModels = input.resolvedModels.sort((left, right) => {
|
|
785
|
+
const tableComparison = compareStrings(left.mapping.tableName, right.mapping.tableName);
|
|
786
|
+
if (tableComparison === 0) return compareStrings(left.model.name, right.model.name);
|
|
787
|
+
return tableComparison;
|
|
788
|
+
});
|
|
789
|
+
for (const entry of sortedModels) {
|
|
790
|
+
const relationEntries = [...input.modelRelations.get(entry.model.name) ?? []].sort((left, right) => compareStrings(left.fieldName, right.fieldName));
|
|
791
|
+
nextBuilder = nextBuilder.model(entry.model.name, entry.mapping.tableName, (modelBuilder) => {
|
|
792
|
+
let next = modelBuilder;
|
|
793
|
+
for (const resolvedField of entry.resolvedFields) next = next.field(resolvedField.field.name, resolvedField.columnName);
|
|
794
|
+
for (const relation of relationEntries) next = next.relation(relation.fieldName, {
|
|
795
|
+
toModel: relation.toModel,
|
|
796
|
+
toTable: relation.toTable,
|
|
797
|
+
cardinality: relation.cardinality,
|
|
798
|
+
on: {
|
|
799
|
+
parentTable: relation.parentTable,
|
|
800
|
+
parentColumns: relation.parentColumns,
|
|
801
|
+
childTable: relation.childTable,
|
|
802
|
+
childColumns: relation.childColumns
|
|
803
|
+
}
|
|
804
|
+
});
|
|
805
|
+
return next;
|
|
806
|
+
});
|
|
807
|
+
}
|
|
808
|
+
return nextBuilder;
|
|
809
|
+
}
|
|
810
|
+
function mapParserDiagnostics(document) {
|
|
811
|
+
return document.diagnostics.map((diagnostic) => ({
|
|
812
|
+
code: diagnostic.code,
|
|
813
|
+
message: diagnostic.message,
|
|
814
|
+
sourceId: diagnostic.sourceId,
|
|
815
|
+
span: diagnostic.span
|
|
816
|
+
}));
|
|
817
|
+
}
|
|
818
|
+
function normalizeReferentialAction(input) {
|
|
819
|
+
const normalized = REFERENTIAL_ACTION_MAP[input.actionToken];
|
|
820
|
+
if (normalized) return normalized;
|
|
821
|
+
input.diagnostics.push({
|
|
822
|
+
code: "PSL_UNSUPPORTED_REFERENTIAL_ACTION",
|
|
823
|
+
message: `Relation field "${input.modelName}.${input.fieldName}" has unsupported ${input.actionName} action "${input.actionToken}"`,
|
|
824
|
+
sourceId: input.sourceId,
|
|
825
|
+
span: input.span
|
|
826
|
+
});
|
|
827
|
+
}
|
|
828
|
+
function parseAttributeFieldList(input) {
|
|
829
|
+
const raw = getNamedArgument(input.attribute, "fields") ?? getPositionalArgument(input.attribute);
|
|
830
|
+
if (!raw) {
|
|
831
|
+
input.diagnostics.push({
|
|
832
|
+
code: input.code,
|
|
833
|
+
message: `${input.messagePrefix} requires fields list argument`,
|
|
834
|
+
sourceId: input.sourceId,
|
|
835
|
+
span: input.attribute.span
|
|
836
|
+
});
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
839
|
+
const fields = parseFieldList(raw);
|
|
840
|
+
if (!fields || fields.length === 0) {
|
|
841
|
+
input.diagnostics.push({
|
|
842
|
+
code: input.code,
|
|
843
|
+
message: `${input.messagePrefix} requires bracketed field list argument`,
|
|
844
|
+
sourceId: input.sourceId,
|
|
845
|
+
span: input.attribute.span
|
|
846
|
+
});
|
|
847
|
+
return;
|
|
848
|
+
}
|
|
849
|
+
return fields;
|
|
850
|
+
}
|
|
851
|
+
function mapFieldNamesToColumns(input) {
|
|
852
|
+
const columns = [];
|
|
853
|
+
for (const fieldName of input.fieldNames) {
|
|
854
|
+
const columnName = input.mapping.fieldColumns.get(fieldName);
|
|
855
|
+
if (!columnName) {
|
|
856
|
+
input.diagnostics.push({
|
|
857
|
+
code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
|
|
858
|
+
message: `${input.contextLabel} references unknown field "${input.modelName}.${fieldName}"`,
|
|
859
|
+
sourceId: input.sourceId,
|
|
860
|
+
span: input.span
|
|
861
|
+
});
|
|
862
|
+
return;
|
|
863
|
+
}
|
|
864
|
+
columns.push(columnName);
|
|
865
|
+
}
|
|
866
|
+
return columns;
|
|
867
|
+
}
|
|
868
|
+
function buildModelMappings(models, diagnostics, sourceId) {
|
|
869
|
+
const result = /* @__PURE__ */ new Map();
|
|
870
|
+
for (const model of models) {
|
|
871
|
+
const tableName = parseMapName({
|
|
872
|
+
attribute: getAttribute(model.attributes, "map"),
|
|
873
|
+
defaultValue: lowerFirst(model.name),
|
|
874
|
+
sourceId,
|
|
875
|
+
diagnostics,
|
|
876
|
+
entityLabel: `Model "${model.name}"`,
|
|
877
|
+
span: model.span
|
|
878
|
+
});
|
|
879
|
+
const fieldColumns = /* @__PURE__ */ new Map();
|
|
880
|
+
for (const field of model.fields) {
|
|
881
|
+
const columnName = parseMapName({
|
|
882
|
+
attribute: getAttribute(field.attributes, "map"),
|
|
883
|
+
defaultValue: field.name,
|
|
884
|
+
sourceId,
|
|
885
|
+
diagnostics,
|
|
886
|
+
entityLabel: `Field "${model.name}.${field.name}"`,
|
|
887
|
+
span: field.span
|
|
888
|
+
});
|
|
889
|
+
fieldColumns.set(field.name, columnName);
|
|
890
|
+
}
|
|
891
|
+
result.set(model.name, {
|
|
892
|
+
model,
|
|
893
|
+
tableName,
|
|
894
|
+
fieldColumns
|
|
895
|
+
});
|
|
896
|
+
}
|
|
897
|
+
return result;
|
|
898
|
+
}
|
|
899
|
+
function validateNavigationListFieldAttributes(input) {
|
|
900
|
+
let valid = true;
|
|
901
|
+
for (const attribute of input.field.attributes) {
|
|
902
|
+
if (attribute.name === "relation") continue;
|
|
903
|
+
if (attribute.name.startsWith("pgvector.") && !input.composedExtensions.has("pgvector")) {
|
|
904
|
+
input.diagnostics.push({
|
|
905
|
+
code: "PSL_EXTENSION_NAMESPACE_NOT_COMPOSED",
|
|
906
|
+
message: `Attribute "@${attribute.name}" uses unrecognized namespace "pgvector". Add extension pack "pgvector" to extensionPacks in prisma-next.config.ts.`,
|
|
907
|
+
sourceId: input.sourceId,
|
|
908
|
+
span: attribute.span
|
|
909
|
+
});
|
|
910
|
+
valid = false;
|
|
911
|
+
continue;
|
|
912
|
+
}
|
|
913
|
+
input.diagnostics.push({
|
|
914
|
+
code: "PSL_UNSUPPORTED_FIELD_ATTRIBUTE",
|
|
915
|
+
message: `Field "${input.modelName}.${input.field.name}" uses unsupported attribute "@${attribute.name}"`,
|
|
916
|
+
sourceId: input.sourceId,
|
|
917
|
+
span: attribute.span
|
|
918
|
+
});
|
|
919
|
+
valid = false;
|
|
920
|
+
}
|
|
921
|
+
return valid;
|
|
922
|
+
}
|
|
923
|
+
function parseRelationAttribute(input) {
|
|
924
|
+
if (input.attribute.args.filter((arg) => arg.kind === "positional").length > 1) {
|
|
925
|
+
input.diagnostics.push({
|
|
926
|
+
code: "PSL_INVALID_RELATION_ATTRIBUTE",
|
|
927
|
+
message: `Relation field "${input.modelName}.${input.fieldName}" has too many positional arguments`,
|
|
928
|
+
sourceId: input.sourceId,
|
|
929
|
+
span: input.attribute.span
|
|
930
|
+
});
|
|
931
|
+
return;
|
|
932
|
+
}
|
|
933
|
+
let relationNameFromPositional;
|
|
934
|
+
const positionalNameEntry = getPositionalArgumentEntry(input.attribute);
|
|
935
|
+
if (positionalNameEntry) {
|
|
936
|
+
const parsedName = parseQuotedStringLiteral(positionalNameEntry.value);
|
|
937
|
+
if (!parsedName) {
|
|
938
|
+
input.diagnostics.push({
|
|
939
|
+
code: "PSL_INVALID_RELATION_ATTRIBUTE",
|
|
940
|
+
message: `Relation field "${input.modelName}.${input.fieldName}" positional relation name must be a quoted string literal`,
|
|
941
|
+
sourceId: input.sourceId,
|
|
942
|
+
span: positionalNameEntry.span
|
|
943
|
+
});
|
|
944
|
+
return;
|
|
945
|
+
}
|
|
946
|
+
relationNameFromPositional = parsedName;
|
|
947
|
+
}
|
|
948
|
+
for (const arg of input.attribute.args) {
|
|
949
|
+
if (arg.kind === "positional") continue;
|
|
950
|
+
if (arg.name !== "name" && arg.name !== "fields" && arg.name !== "references" && arg.name !== "onDelete" && arg.name !== "onUpdate") {
|
|
951
|
+
input.diagnostics.push({
|
|
952
|
+
code: "PSL_INVALID_RELATION_ATTRIBUTE",
|
|
953
|
+
message: `Relation field "${input.modelName}.${input.fieldName}" has unsupported argument "${arg.name}"`,
|
|
954
|
+
sourceId: input.sourceId,
|
|
955
|
+
span: arg.span
|
|
956
|
+
});
|
|
957
|
+
return;
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
const namedRelationNameRaw = getNamedArgument(input.attribute, "name");
|
|
961
|
+
const namedRelationName = namedRelationNameRaw ? parseQuotedStringLiteral(namedRelationNameRaw) : void 0;
|
|
962
|
+
if (namedRelationNameRaw && !namedRelationName) {
|
|
963
|
+
input.diagnostics.push({
|
|
964
|
+
code: "PSL_INVALID_RELATION_ATTRIBUTE",
|
|
965
|
+
message: `Relation field "${input.modelName}.${input.fieldName}" named relation name must be a quoted string literal`,
|
|
966
|
+
sourceId: input.sourceId,
|
|
967
|
+
span: input.attribute.span
|
|
968
|
+
});
|
|
969
|
+
return;
|
|
970
|
+
}
|
|
971
|
+
if (relationNameFromPositional && namedRelationName && relationNameFromPositional !== namedRelationName) {
|
|
972
|
+
input.diagnostics.push({
|
|
973
|
+
code: "PSL_INVALID_RELATION_ATTRIBUTE",
|
|
974
|
+
message: `Relation field "${input.modelName}.${input.fieldName}" has conflicting positional and named relation names`,
|
|
975
|
+
sourceId: input.sourceId,
|
|
976
|
+
span: input.attribute.span
|
|
977
|
+
});
|
|
978
|
+
return;
|
|
979
|
+
}
|
|
980
|
+
const relationName = namedRelationName ?? relationNameFromPositional;
|
|
981
|
+
const fieldsRaw = getNamedArgument(input.attribute, "fields");
|
|
982
|
+
const referencesRaw = getNamedArgument(input.attribute, "references");
|
|
983
|
+
if (fieldsRaw && !referencesRaw || !fieldsRaw && referencesRaw) {
|
|
984
|
+
input.diagnostics.push({
|
|
985
|
+
code: "PSL_INVALID_RELATION_ATTRIBUTE",
|
|
986
|
+
message: `Relation field "${input.modelName}.${input.fieldName}" requires fields and references arguments`,
|
|
987
|
+
sourceId: input.sourceId,
|
|
988
|
+
span: input.attribute.span
|
|
989
|
+
});
|
|
990
|
+
return;
|
|
991
|
+
}
|
|
992
|
+
let fields;
|
|
993
|
+
let references;
|
|
994
|
+
if (fieldsRaw && referencesRaw) {
|
|
995
|
+
const parsedFields = parseFieldList(fieldsRaw);
|
|
996
|
+
const parsedReferences = parseFieldList(referencesRaw);
|
|
997
|
+
if (!parsedFields || !parsedReferences || parsedFields.length === 0 || parsedReferences.length === 0) {
|
|
998
|
+
input.diagnostics.push({
|
|
999
|
+
code: "PSL_INVALID_RELATION_ATTRIBUTE",
|
|
1000
|
+
message: `Relation field "${input.modelName}.${input.fieldName}" requires bracketed fields and references lists`,
|
|
1001
|
+
sourceId: input.sourceId,
|
|
1002
|
+
span: input.attribute.span
|
|
1003
|
+
});
|
|
1004
|
+
return;
|
|
1005
|
+
}
|
|
1006
|
+
fields = parsedFields;
|
|
1007
|
+
references = parsedReferences;
|
|
1008
|
+
}
|
|
1009
|
+
const onDeleteArgument = getNamedArgument(input.attribute, "onDelete");
|
|
1010
|
+
const onUpdateArgument = getNamedArgument(input.attribute, "onUpdate");
|
|
1011
|
+
return {
|
|
1012
|
+
...ifDefined("relationName", relationName),
|
|
1013
|
+
...ifDefined("fields", fields),
|
|
1014
|
+
...ifDefined("references", references),
|
|
1015
|
+
...ifDefined("onDelete", onDeleteArgument ? unquoteStringLiteral(onDeleteArgument) : void 0),
|
|
1016
|
+
...ifDefined("onUpdate", onUpdateArgument ? unquoteStringLiteral(onUpdateArgument) : void 0)
|
|
1017
|
+
};
|
|
1018
|
+
}
|
|
1019
|
+
function interpretPslDocumentToSqlContractIR(input) {
|
|
1020
|
+
const diagnostics = mapParserDiagnostics(input.document);
|
|
1021
|
+
const modelNames = new Set(input.document.ast.models.map((model) => model.name));
|
|
1022
|
+
const sourceId = input.document.ast.sourceId;
|
|
1023
|
+
const composedExtensions = new Set(input.composedExtensionPacks ?? []);
|
|
1024
|
+
const defaultFunctionRegistry = input.defaultFunctionRegistry ?? createBuiltinDefaultFunctionRegistry();
|
|
1025
|
+
let builder = defineContract().target(input.target ?? DEFAULT_POSTGRES_TARGET);
|
|
1026
|
+
const enumTypeDescriptors = /* @__PURE__ */ new Map();
|
|
1027
|
+
const namedTypeDescriptors = /* @__PURE__ */ new Map();
|
|
1028
|
+
const namedTypeBaseTypes = /* @__PURE__ */ new Map();
|
|
1029
|
+
for (const enumDeclaration of input.document.ast.enums) {
|
|
1030
|
+
const nativeType = enumDeclaration.name.toLowerCase();
|
|
1031
|
+
const descriptor = {
|
|
1032
|
+
codecId: "pg/enum@1",
|
|
1033
|
+
nativeType,
|
|
1034
|
+
typeRef: enumDeclaration.name
|
|
1035
|
+
};
|
|
1036
|
+
enumTypeDescriptors.set(enumDeclaration.name, descriptor);
|
|
1037
|
+
builder = builder.storageType(enumDeclaration.name, {
|
|
1038
|
+
codecId: "pg/enum@1",
|
|
1039
|
+
nativeType,
|
|
1040
|
+
typeParams: { values: enumDeclaration.values.map((value) => value.name) }
|
|
1041
|
+
});
|
|
1042
|
+
}
|
|
1043
|
+
for (const declaration of input.document.ast.types?.declarations ?? []) {
|
|
1044
|
+
const baseDescriptor = enumTypeDescriptors.get(declaration.baseType) ?? SCALAR_COLUMN_MAP[declaration.baseType];
|
|
1045
|
+
if (!baseDescriptor) {
|
|
1046
|
+
diagnostics.push({
|
|
1047
|
+
code: "PSL_UNSUPPORTED_NAMED_TYPE_BASE",
|
|
1048
|
+
message: `Named type "${declaration.name}" references unsupported base type "${declaration.baseType}"`,
|
|
1049
|
+
sourceId,
|
|
1050
|
+
span: declaration.span
|
|
1051
|
+
});
|
|
1052
|
+
continue;
|
|
1053
|
+
}
|
|
1054
|
+
namedTypeBaseTypes.set(declaration.name, declaration.baseType);
|
|
1055
|
+
const pgvectorAttribute = getAttribute(declaration.attributes, "pgvector.column");
|
|
1056
|
+
const unsupportedNamedTypeAttribute = declaration.attributes.find((attribute) => attribute.name !== "pgvector.column");
|
|
1057
|
+
if (unsupportedNamedTypeAttribute) {
|
|
1058
|
+
diagnostics.push({
|
|
1059
|
+
code: "PSL_UNSUPPORTED_NAMED_TYPE_ATTRIBUTE",
|
|
1060
|
+
message: `Named type "${declaration.name}" uses unsupported attribute "${unsupportedNamedTypeAttribute.name}"`,
|
|
1061
|
+
sourceId,
|
|
1062
|
+
span: unsupportedNamedTypeAttribute.span
|
|
1063
|
+
});
|
|
1064
|
+
continue;
|
|
1065
|
+
}
|
|
1066
|
+
if (pgvectorAttribute) {
|
|
1067
|
+
if (!composedExtensions.has("pgvector")) {
|
|
1068
|
+
diagnostics.push({
|
|
1069
|
+
code: "PSL_EXTENSION_NAMESPACE_NOT_COMPOSED",
|
|
1070
|
+
message: "Attribute \"@pgvector.column\" uses unrecognized namespace \"pgvector\". Add extension pack \"pgvector\" to extensionPacks in prisma-next.config.ts.",
|
|
1071
|
+
sourceId,
|
|
1072
|
+
span: pgvectorAttribute.span
|
|
1073
|
+
});
|
|
1074
|
+
continue;
|
|
1075
|
+
}
|
|
1076
|
+
if (declaration.baseType !== "Bytes") {
|
|
1077
|
+
diagnostics.push({
|
|
1078
|
+
code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
|
|
1079
|
+
message: `Named type "${declaration.name}" uses @pgvector.column on unsupported base type "${declaration.baseType}"`,
|
|
1080
|
+
sourceId,
|
|
1081
|
+
span: pgvectorAttribute.span
|
|
1082
|
+
});
|
|
1083
|
+
continue;
|
|
1084
|
+
}
|
|
1085
|
+
const length = parsePgvectorLength({
|
|
1086
|
+
attribute: pgvectorAttribute,
|
|
1087
|
+
diagnostics,
|
|
1088
|
+
sourceId
|
|
1089
|
+
});
|
|
1090
|
+
if (length === void 0) continue;
|
|
1091
|
+
namedTypeDescriptors.set(declaration.name, {
|
|
1092
|
+
codecId: "pg/vector@1",
|
|
1093
|
+
nativeType: `vector(${length})`,
|
|
1094
|
+
typeRef: declaration.name
|
|
1095
|
+
});
|
|
1096
|
+
builder = builder.storageType(declaration.name, {
|
|
1097
|
+
codecId: "pg/vector@1",
|
|
1098
|
+
nativeType: `vector(${length})`,
|
|
1099
|
+
typeParams: { length }
|
|
1100
|
+
});
|
|
1101
|
+
continue;
|
|
1102
|
+
}
|
|
1103
|
+
const descriptor = {
|
|
1104
|
+
codecId: baseDescriptor.codecId,
|
|
1105
|
+
nativeType: baseDescriptor.nativeType,
|
|
1106
|
+
typeRef: declaration.name
|
|
1107
|
+
};
|
|
1108
|
+
namedTypeDescriptors.set(declaration.name, descriptor);
|
|
1109
|
+
builder = builder.storageType(declaration.name, {
|
|
1110
|
+
codecId: baseDescriptor.codecId,
|
|
1111
|
+
nativeType: baseDescriptor.nativeType,
|
|
1112
|
+
typeParams: {}
|
|
1113
|
+
});
|
|
1114
|
+
}
|
|
1115
|
+
const modelMappings = buildModelMappings(input.document.ast.models, diagnostics, sourceId);
|
|
1116
|
+
const resolvedModels = [];
|
|
1117
|
+
const fkRelationMetadata = [];
|
|
1118
|
+
const backrelationCandidates = [];
|
|
1119
|
+
for (const model of input.document.ast.models) {
|
|
1120
|
+
const mapping = modelMappings.get(model.name);
|
|
1121
|
+
if (!mapping) continue;
|
|
1122
|
+
const tableName = mapping.tableName;
|
|
1123
|
+
const resolvedFields = collectResolvedFields(model, mapping, enumTypeDescriptors, namedTypeDescriptors, namedTypeBaseTypes, modelNames, composedExtensions, defaultFunctionRegistry, diagnostics, sourceId);
|
|
1124
|
+
resolvedModels.push({
|
|
1125
|
+
model,
|
|
1126
|
+
mapping,
|
|
1127
|
+
resolvedFields
|
|
1128
|
+
});
|
|
1129
|
+
const primaryKeyColumns = resolvedFields.filter((field) => field.isId).map((field) => field.columnName);
|
|
1130
|
+
if (primaryKeyColumns.length === 0) diagnostics.push({
|
|
1131
|
+
code: "PSL_MISSING_PRIMARY_KEY",
|
|
1132
|
+
message: `Model "${model.name}" must declare at least one @id field for SQL provider`,
|
|
1133
|
+
sourceId,
|
|
1134
|
+
span: model.span
|
|
1135
|
+
});
|
|
1136
|
+
for (const field of model.fields) {
|
|
1137
|
+
if (!field.list || !modelNames.has(field.typeName)) continue;
|
|
1138
|
+
const attributesValid = validateNavigationListFieldAttributes({
|
|
1139
|
+
modelName: model.name,
|
|
1140
|
+
field,
|
|
1141
|
+
sourceId,
|
|
1142
|
+
composedExtensions,
|
|
1143
|
+
diagnostics
|
|
1144
|
+
});
|
|
1145
|
+
const relationAttribute = getAttribute(field.attributes, "relation");
|
|
1146
|
+
let relationName;
|
|
1147
|
+
if (relationAttribute) {
|
|
1148
|
+
const parsedRelation = parseRelationAttribute({
|
|
1149
|
+
attribute: relationAttribute,
|
|
1150
|
+
modelName: model.name,
|
|
1151
|
+
fieldName: field.name,
|
|
1152
|
+
sourceId,
|
|
1153
|
+
diagnostics
|
|
1154
|
+
});
|
|
1155
|
+
if (!parsedRelation) continue;
|
|
1156
|
+
if (parsedRelation.fields || parsedRelation.references) {
|
|
1157
|
+
diagnostics.push({
|
|
1158
|
+
code: "PSL_INVALID_RELATION_ATTRIBUTE",
|
|
1159
|
+
message: `Backrelation list field "${model.name}.${field.name}" cannot declare fields/references; define them on the FK-side relation field`,
|
|
1160
|
+
sourceId,
|
|
1161
|
+
span: relationAttribute.span
|
|
1162
|
+
});
|
|
1163
|
+
continue;
|
|
1164
|
+
}
|
|
1165
|
+
if (parsedRelation.onDelete || parsedRelation.onUpdate) {
|
|
1166
|
+
diagnostics.push({
|
|
1167
|
+
code: "PSL_INVALID_RELATION_ATTRIBUTE",
|
|
1168
|
+
message: `Backrelation list field "${model.name}.${field.name}" cannot declare onDelete/onUpdate; define referential actions on the FK-side relation field`,
|
|
1169
|
+
sourceId,
|
|
1170
|
+
span: relationAttribute.span
|
|
1171
|
+
});
|
|
1172
|
+
continue;
|
|
1173
|
+
}
|
|
1174
|
+
relationName = parsedRelation.relationName;
|
|
1175
|
+
}
|
|
1176
|
+
if (!attributesValid) continue;
|
|
1177
|
+
backrelationCandidates.push({
|
|
1178
|
+
modelName: model.name,
|
|
1179
|
+
tableName,
|
|
1180
|
+
field,
|
|
1181
|
+
targetModelName: field.typeName,
|
|
1182
|
+
...ifDefined("relationName", relationName)
|
|
1183
|
+
});
|
|
1184
|
+
}
|
|
1185
|
+
const relationAttributes = model.fields.map((field) => ({
|
|
1186
|
+
field,
|
|
1187
|
+
relation: getAttribute(field.attributes, "relation")
|
|
1188
|
+
})).filter((entry) => Boolean(entry.relation));
|
|
1189
|
+
builder = builder.table(tableName, (tableBuilder) => {
|
|
1190
|
+
let table = tableBuilder;
|
|
1191
|
+
for (const resolvedField of resolvedFields) {
|
|
1192
|
+
if (resolvedField.executionDefault) table = table.generated(resolvedField.columnName, {
|
|
1193
|
+
type: resolvedField.descriptor,
|
|
1194
|
+
generated: resolvedField.executionDefault
|
|
1195
|
+
});
|
|
1196
|
+
else {
|
|
1197
|
+
const options = {
|
|
1198
|
+
type: resolvedField.descriptor,
|
|
1199
|
+
...ifDefined("nullable", resolvedField.field.optional ? true : void 0),
|
|
1200
|
+
...ifDefined("default", resolvedField.defaultValue)
|
|
1201
|
+
};
|
|
1202
|
+
table = table.column(resolvedField.columnName, options);
|
|
1203
|
+
}
|
|
1204
|
+
if (resolvedField.isUnique) table = table.unique([resolvedField.columnName]);
|
|
1205
|
+
}
|
|
1206
|
+
if (primaryKeyColumns.length > 0) table = table.primaryKey(primaryKeyColumns);
|
|
1207
|
+
for (const modelAttribute of model.attributes) {
|
|
1208
|
+
if (modelAttribute.name === "map") continue;
|
|
1209
|
+
if (modelAttribute.name === "unique" || modelAttribute.name === "index") {
|
|
1210
|
+
const fieldNames = parseAttributeFieldList({
|
|
1211
|
+
attribute: modelAttribute,
|
|
1212
|
+
sourceId,
|
|
1213
|
+
diagnostics,
|
|
1214
|
+
code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
|
|
1215
|
+
messagePrefix: `Model "${model.name}" @@${modelAttribute.name}`
|
|
1216
|
+
});
|
|
1217
|
+
if (!fieldNames) continue;
|
|
1218
|
+
const columnNames = mapFieldNamesToColumns({
|
|
1219
|
+
modelName: model.name,
|
|
1220
|
+
fieldNames,
|
|
1221
|
+
mapping,
|
|
1222
|
+
sourceId,
|
|
1223
|
+
diagnostics,
|
|
1224
|
+
span: modelAttribute.span,
|
|
1225
|
+
contextLabel: `Model "${model.name}" @@${modelAttribute.name}`
|
|
1226
|
+
});
|
|
1227
|
+
if (!columnNames) continue;
|
|
1228
|
+
if (modelAttribute.name === "unique") table = table.unique(columnNames);
|
|
1229
|
+
else table = table.index(columnNames);
|
|
1230
|
+
continue;
|
|
1231
|
+
}
|
|
1232
|
+
if (modelAttribute.name.startsWith("pgvector.") && !composedExtensions.has("pgvector")) {
|
|
1233
|
+
diagnostics.push({
|
|
1234
|
+
code: "PSL_EXTENSION_NAMESPACE_NOT_COMPOSED",
|
|
1235
|
+
message: `Attribute "@@${modelAttribute.name}" uses unrecognized namespace "pgvector". Add extension pack "pgvector" to extensionPacks in prisma-next.config.ts.`,
|
|
1236
|
+
sourceId,
|
|
1237
|
+
span: modelAttribute.span
|
|
1238
|
+
});
|
|
1239
|
+
continue;
|
|
1240
|
+
}
|
|
1241
|
+
diagnostics.push({
|
|
1242
|
+
code: "PSL_UNSUPPORTED_MODEL_ATTRIBUTE",
|
|
1243
|
+
message: `Model "${model.name}" uses unsupported attribute "@@${modelAttribute.name}"`,
|
|
1244
|
+
sourceId,
|
|
1245
|
+
span: modelAttribute.span
|
|
1246
|
+
});
|
|
1247
|
+
}
|
|
1248
|
+
for (const relationAttribute of relationAttributes) {
|
|
1249
|
+
if (relationAttribute.field.list) continue;
|
|
1250
|
+
if (!modelNames.has(relationAttribute.field.typeName)) {
|
|
1251
|
+
diagnostics.push({
|
|
1252
|
+
code: "PSL_INVALID_RELATION_TARGET",
|
|
1253
|
+
message: `Relation field "${model.name}.${relationAttribute.field.name}" references unknown model "${relationAttribute.field.typeName}"`,
|
|
1254
|
+
sourceId,
|
|
1255
|
+
span: relationAttribute.field.span
|
|
1256
|
+
});
|
|
1257
|
+
continue;
|
|
1258
|
+
}
|
|
1259
|
+
const parsedRelation = parseRelationAttribute({
|
|
1260
|
+
attribute: relationAttribute.relation,
|
|
1261
|
+
modelName: model.name,
|
|
1262
|
+
fieldName: relationAttribute.field.name,
|
|
1263
|
+
sourceId,
|
|
1264
|
+
diagnostics
|
|
1265
|
+
});
|
|
1266
|
+
if (!parsedRelation) continue;
|
|
1267
|
+
if (!parsedRelation.fields || !parsedRelation.references) {
|
|
1268
|
+
diagnostics.push({
|
|
1269
|
+
code: "PSL_INVALID_RELATION_ATTRIBUTE",
|
|
1270
|
+
message: `Relation field "${model.name}.${relationAttribute.field.name}" requires fields and references arguments`,
|
|
1271
|
+
sourceId,
|
|
1272
|
+
span: relationAttribute.relation.span
|
|
1273
|
+
});
|
|
1274
|
+
continue;
|
|
1275
|
+
}
|
|
1276
|
+
const targetMapping = modelMappings.get(relationAttribute.field.typeName);
|
|
1277
|
+
if (!targetMapping) {
|
|
1278
|
+
diagnostics.push({
|
|
1279
|
+
code: "PSL_INVALID_RELATION_TARGET",
|
|
1280
|
+
message: `Relation field "${model.name}.${relationAttribute.field.name}" references unknown model "${relationAttribute.field.typeName}"`,
|
|
1281
|
+
sourceId,
|
|
1282
|
+
span: relationAttribute.field.span
|
|
1283
|
+
});
|
|
1284
|
+
continue;
|
|
1285
|
+
}
|
|
1286
|
+
const localColumns = mapFieldNamesToColumns({
|
|
1287
|
+
modelName: model.name,
|
|
1288
|
+
fieldNames: parsedRelation.fields,
|
|
1289
|
+
mapping,
|
|
1290
|
+
sourceId,
|
|
1291
|
+
diagnostics,
|
|
1292
|
+
span: relationAttribute.relation.span,
|
|
1293
|
+
contextLabel: `Relation field "${model.name}.${relationAttribute.field.name}"`
|
|
1294
|
+
});
|
|
1295
|
+
if (!localColumns) continue;
|
|
1296
|
+
const referencedColumns = mapFieldNamesToColumns({
|
|
1297
|
+
modelName: targetMapping.model.name,
|
|
1298
|
+
fieldNames: parsedRelation.references,
|
|
1299
|
+
mapping: targetMapping,
|
|
1300
|
+
sourceId,
|
|
1301
|
+
diagnostics,
|
|
1302
|
+
span: relationAttribute.relation.span,
|
|
1303
|
+
contextLabel: `Relation field "${model.name}.${relationAttribute.field.name}"`
|
|
1304
|
+
});
|
|
1305
|
+
if (!referencedColumns) continue;
|
|
1306
|
+
if (localColumns.length !== referencedColumns.length) {
|
|
1307
|
+
diagnostics.push({
|
|
1308
|
+
code: "PSL_INVALID_RELATION_ATTRIBUTE",
|
|
1309
|
+
message: `Relation field "${model.name}.${relationAttribute.field.name}" must provide the same number of fields and references`,
|
|
1310
|
+
sourceId,
|
|
1311
|
+
span: relationAttribute.relation.span
|
|
1312
|
+
});
|
|
1313
|
+
continue;
|
|
1314
|
+
}
|
|
1315
|
+
const onDelete = parsedRelation.onDelete ? normalizeReferentialAction({
|
|
1316
|
+
modelName: model.name,
|
|
1317
|
+
fieldName: relationAttribute.field.name,
|
|
1318
|
+
actionName: "onDelete",
|
|
1319
|
+
actionToken: parsedRelation.onDelete,
|
|
1320
|
+
sourceId,
|
|
1321
|
+
span: relationAttribute.field.span,
|
|
1322
|
+
diagnostics
|
|
1323
|
+
}) : void 0;
|
|
1324
|
+
const onUpdate = parsedRelation.onUpdate ? normalizeReferentialAction({
|
|
1325
|
+
modelName: model.name,
|
|
1326
|
+
fieldName: relationAttribute.field.name,
|
|
1327
|
+
actionName: "onUpdate",
|
|
1328
|
+
actionToken: parsedRelation.onUpdate,
|
|
1329
|
+
sourceId,
|
|
1330
|
+
span: relationAttribute.field.span,
|
|
1331
|
+
diagnostics
|
|
1332
|
+
}) : void 0;
|
|
1333
|
+
table = table.foreignKey(localColumns, {
|
|
1334
|
+
table: targetMapping.tableName,
|
|
1335
|
+
columns: referencedColumns
|
|
1336
|
+
}, {
|
|
1337
|
+
...ifDefined("onDelete", onDelete),
|
|
1338
|
+
...ifDefined("onUpdate", onUpdate)
|
|
1339
|
+
});
|
|
1340
|
+
fkRelationMetadata.push({
|
|
1341
|
+
declaringModelName: model.name,
|
|
1342
|
+
declaringFieldName: relationAttribute.field.name,
|
|
1343
|
+
declaringTableName: tableName,
|
|
1344
|
+
targetModelName: targetMapping.model.name,
|
|
1345
|
+
targetTableName: targetMapping.tableName,
|
|
1346
|
+
...ifDefined("relationName", parsedRelation.relationName),
|
|
1347
|
+
localColumns,
|
|
1348
|
+
referencedColumns
|
|
1349
|
+
});
|
|
1350
|
+
}
|
|
1351
|
+
return table;
|
|
1352
|
+
});
|
|
1353
|
+
}
|
|
1354
|
+
const { modelRelations, fkRelationsByPair } = indexFkRelations({ fkRelationMetadata });
|
|
1355
|
+
applyBackrelationCandidates({
|
|
1356
|
+
backrelationCandidates,
|
|
1357
|
+
fkRelationsByPair,
|
|
1358
|
+
modelRelations,
|
|
1359
|
+
diagnostics,
|
|
1360
|
+
sourceId
|
|
1361
|
+
});
|
|
1362
|
+
builder = emitModelsWithRelations({
|
|
1363
|
+
builder,
|
|
1364
|
+
resolvedModels,
|
|
1365
|
+
modelRelations
|
|
1366
|
+
});
|
|
1367
|
+
if (diagnostics.length > 0) return notOk({
|
|
1368
|
+
summary: "PSL to SQL Contract IR normalization failed",
|
|
1369
|
+
diagnostics: diagnostics.filter((diagnostic, index, allDiagnostics) => allDiagnostics.findIndex((candidate) => candidate.code === diagnostic.code && candidate.message === diagnostic.message && candidate.sourceId === diagnostic.sourceId && (candidate.span && diagnostic.span && hasSameSpan(candidate.span, diagnostic.span) || !candidate.span && !diagnostic.span)) === index)
|
|
1370
|
+
});
|
|
1371
|
+
return ok(builder.build());
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
//#endregion
|
|
1375
|
+
export { createBuiltinDefaultFunctionRegistry as n, interpretPslDocumentToSqlContractIR as t };
|
|
1376
|
+
//# sourceMappingURL=interpreter-IXr5c7s7.mjs.map
|