@prisma-next/sql-contract-psl 0.3.0-dev.70 → 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/README.md +18 -17
- package/dist/default-function-registry-DUMRIhJH.d.mts +71 -0
- package/dist/default-function-registry-DUMRIhJH.d.mts.map +1 -0
- package/dist/index.d.mts +18 -42
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +2 -2
- package/dist/interpreter-iFCRN9nb.mjs +2343 -0
- package/dist/interpreter-iFCRN9nb.mjs.map +1 -0
- package/dist/provider.d.mts +14 -12
- package/dist/provider.d.mts.map +1 -1
- package/dist/provider.mjs +20 -13
- package/dist/provider.mjs.map +1 -1
- package/package.json +11 -8
- package/src/default-function-registry.ts +52 -300
- package/src/exports/index.ts +10 -2
- package/src/interpreter.ts +1074 -1326
- package/src/provider.ts +44 -25
- package/src/psl-attribute-parsing.ts +303 -0
- package/src/psl-authoring-arguments.ts +454 -0
- package/src/psl-column-resolution.ts +600 -0
- package/src/psl-field-resolution.ts +335 -0
- package/src/psl-relation-resolution.ts +371 -0
- package/dist/interpreter-IXr5c7s7.mjs +0 -1376
- package/dist/interpreter-IXr5c7s7.mjs.map +0 -1
|
@@ -0,0 +1,2343 @@
|
|
|
1
|
+
import { instantiateAuthoringTypeConstructor, isAuthoringTypeConstructorDescriptor, validateAuthoringHelperArguments } from "@prisma-next/framework-components/authoring";
|
|
2
|
+
import { buildSqlContractFromDefinition } from "@prisma-next/sql-contract-ts/contract-builder";
|
|
3
|
+
import { ifDefined } from "@prisma-next/utils/defined";
|
|
4
|
+
import { notOk, ok } from "@prisma-next/utils/result";
|
|
5
|
+
import { getPositionalArgument, parseQuotedStringLiteral } from "@prisma-next/psl-parser";
|
|
6
|
+
import { assertDefined, invariant } from "@prisma-next/utils/assertions";
|
|
7
|
+
|
|
8
|
+
//#region src/psl-attribute-parsing.ts
|
|
9
|
+
function lowerFirst(value) {
|
|
10
|
+
if (value.length === 0) return value;
|
|
11
|
+
return value[0]?.toLowerCase() + value.slice(1);
|
|
12
|
+
}
|
|
13
|
+
function getAttribute(attributes, name) {
|
|
14
|
+
return attributes?.find((attribute) => attribute.name === name);
|
|
15
|
+
}
|
|
16
|
+
function getNamedArgument(attribute, name) {
|
|
17
|
+
const entry = attribute.args.find((arg) => arg.kind === "named" && arg.name === name);
|
|
18
|
+
if (!entry || entry.kind !== "named") return;
|
|
19
|
+
return entry.value;
|
|
20
|
+
}
|
|
21
|
+
function getPositionalArgumentEntry(attribute, index = 0) {
|
|
22
|
+
const entry = attribute.args.filter((arg) => arg.kind === "positional")[index];
|
|
23
|
+
if (!entry || entry.kind !== "positional") return;
|
|
24
|
+
return {
|
|
25
|
+
value: entry.value,
|
|
26
|
+
span: entry.span
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function unquoteStringLiteral(value) {
|
|
30
|
+
const trimmed = value.trim();
|
|
31
|
+
const match = trimmed.match(/^(['"])(.*)\1$/);
|
|
32
|
+
if (!match) return trimmed;
|
|
33
|
+
return match[2] ?? "";
|
|
34
|
+
}
|
|
35
|
+
function parseFieldList(value) {
|
|
36
|
+
const trimmed = value.trim();
|
|
37
|
+
if (!trimmed.startsWith("[") || !trimmed.endsWith("]")) return;
|
|
38
|
+
return trimmed.slice(1, -1).split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0);
|
|
39
|
+
}
|
|
40
|
+
function parseMapName(input) {
|
|
41
|
+
if (!input.attribute) return input.defaultValue;
|
|
42
|
+
const value = getPositionalArgument(input.attribute);
|
|
43
|
+
if (!value) {
|
|
44
|
+
input.diagnostics.push({
|
|
45
|
+
code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
|
|
46
|
+
message: `${input.entityLabel} @map requires a positional quoted string literal argument`,
|
|
47
|
+
sourceId: input.sourceId,
|
|
48
|
+
span: input.attribute.span
|
|
49
|
+
});
|
|
50
|
+
return input.defaultValue;
|
|
51
|
+
}
|
|
52
|
+
const parsed = parseQuotedStringLiteral(value);
|
|
53
|
+
if (parsed === void 0) {
|
|
54
|
+
input.diagnostics.push({
|
|
55
|
+
code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
|
|
56
|
+
message: `${input.entityLabel} @map requires a positional quoted string literal argument`,
|
|
57
|
+
sourceId: input.sourceId,
|
|
58
|
+
span: input.attribute.span
|
|
59
|
+
});
|
|
60
|
+
return input.defaultValue;
|
|
61
|
+
}
|
|
62
|
+
return parsed;
|
|
63
|
+
}
|
|
64
|
+
function parseConstraintMapArgument(input) {
|
|
65
|
+
if (!input.attribute) return;
|
|
66
|
+
const raw = getNamedArgument(input.attribute, "map");
|
|
67
|
+
if (!raw) return;
|
|
68
|
+
const parsed = parseQuotedStringLiteral(raw);
|
|
69
|
+
if (parsed !== void 0) return parsed;
|
|
70
|
+
input.diagnostics.push({
|
|
71
|
+
code: input.code,
|
|
72
|
+
message: `${input.entityLabel} map argument must be a quoted string literal`,
|
|
73
|
+
sourceId: input.sourceId,
|
|
74
|
+
span: input.span
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
function getPositionalArguments(attribute) {
|
|
78
|
+
return attribute.args.filter((arg) => arg.kind === "positional").map((arg) => arg.kind === "positional" ? arg.value : "");
|
|
79
|
+
}
|
|
80
|
+
function pushInvalidAttributeArgument(input) {
|
|
81
|
+
input.diagnostics.push({
|
|
82
|
+
code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
|
|
83
|
+
message: input.message,
|
|
84
|
+
sourceId: input.sourceId,
|
|
85
|
+
span: input.span
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
function parseOptionalSingleIntegerArgument(input) {
|
|
89
|
+
if (input.attribute.args.some((arg) => arg.kind === "named")) return pushInvalidAttributeArgument({
|
|
90
|
+
diagnostics: input.diagnostics,
|
|
91
|
+
sourceId: input.sourceId,
|
|
92
|
+
span: input.attribute.span,
|
|
93
|
+
message: `${input.entityLabel} @${input.attribute.name} accepts zero or one positional integer argument.`
|
|
94
|
+
});
|
|
95
|
+
const positionalArguments = getPositionalArguments(input.attribute);
|
|
96
|
+
if (positionalArguments.length > 1) return pushInvalidAttributeArgument({
|
|
97
|
+
diagnostics: input.diagnostics,
|
|
98
|
+
sourceId: input.sourceId,
|
|
99
|
+
span: input.attribute.span,
|
|
100
|
+
message: `${input.entityLabel} @${input.attribute.name} accepts zero or one positional integer argument.`
|
|
101
|
+
});
|
|
102
|
+
if (positionalArguments.length === 0) return null;
|
|
103
|
+
const parsed = Number(unquoteStringLiteral(positionalArguments[0] ?? ""));
|
|
104
|
+
if (!Number.isInteger(parsed) || parsed < input.minimum) return pushInvalidAttributeArgument({
|
|
105
|
+
diagnostics: input.diagnostics,
|
|
106
|
+
sourceId: input.sourceId,
|
|
107
|
+
span: input.attribute.span,
|
|
108
|
+
message: `${input.entityLabel} @${input.attribute.name} requires a ${input.valueLabel}.`
|
|
109
|
+
});
|
|
110
|
+
return parsed;
|
|
111
|
+
}
|
|
112
|
+
function parseOptionalNumericArguments(input) {
|
|
113
|
+
if (input.attribute.args.some((arg) => arg.kind === "named")) return pushInvalidAttributeArgument({
|
|
114
|
+
diagnostics: input.diagnostics,
|
|
115
|
+
sourceId: input.sourceId,
|
|
116
|
+
span: input.attribute.span,
|
|
117
|
+
message: `${input.entityLabel} @${input.attribute.name} accepts zero, one, or two positional integer arguments.`
|
|
118
|
+
});
|
|
119
|
+
const positionalArguments = getPositionalArguments(input.attribute);
|
|
120
|
+
if (positionalArguments.length > 2) return pushInvalidAttributeArgument({
|
|
121
|
+
diagnostics: input.diagnostics,
|
|
122
|
+
sourceId: input.sourceId,
|
|
123
|
+
span: input.attribute.span,
|
|
124
|
+
message: `${input.entityLabel} @${input.attribute.name} accepts zero, one, or two positional integer arguments.`
|
|
125
|
+
});
|
|
126
|
+
if (positionalArguments.length === 0) return null;
|
|
127
|
+
const precision = Number(unquoteStringLiteral(positionalArguments[0] ?? ""));
|
|
128
|
+
if (!Number.isInteger(precision) || precision < 1) return pushInvalidAttributeArgument({
|
|
129
|
+
diagnostics: input.diagnostics,
|
|
130
|
+
sourceId: input.sourceId,
|
|
131
|
+
span: input.attribute.span,
|
|
132
|
+
message: `${input.entityLabel} @${input.attribute.name} requires a positive integer precision.`
|
|
133
|
+
});
|
|
134
|
+
if (positionalArguments.length === 1) return { precision };
|
|
135
|
+
const scale = Number(unquoteStringLiteral(positionalArguments[1] ?? ""));
|
|
136
|
+
if (!Number.isInteger(scale) || scale < 0) return pushInvalidAttributeArgument({
|
|
137
|
+
diagnostics: input.diagnostics,
|
|
138
|
+
sourceId: input.sourceId,
|
|
139
|
+
span: input.attribute.span,
|
|
140
|
+
message: `${input.entityLabel} @${input.attribute.name} requires a non-negative integer scale.`
|
|
141
|
+
});
|
|
142
|
+
return {
|
|
143
|
+
precision,
|
|
144
|
+
scale
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
function parseAttributeFieldList(input) {
|
|
148
|
+
const raw = getNamedArgument(input.attribute, "fields") ?? getPositionalArgument(input.attribute);
|
|
149
|
+
if (!raw) {
|
|
150
|
+
input.diagnostics.push({
|
|
151
|
+
code: input.code,
|
|
152
|
+
message: `${input.messagePrefix} requires fields list argument`,
|
|
153
|
+
sourceId: input.sourceId,
|
|
154
|
+
span: input.attribute.span
|
|
155
|
+
});
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
const fields = parseFieldList(raw);
|
|
159
|
+
if (!fields || fields.length === 0) {
|
|
160
|
+
input.diagnostics.push({
|
|
161
|
+
code: input.code,
|
|
162
|
+
message: `${input.messagePrefix} requires bracketed field list argument`,
|
|
163
|
+
sourceId: input.sourceId,
|
|
164
|
+
span: input.attribute.span
|
|
165
|
+
});
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
return fields;
|
|
169
|
+
}
|
|
170
|
+
function mapFieldNamesToColumns(input) {
|
|
171
|
+
const columns = [];
|
|
172
|
+
for (const fieldName of input.fieldNames) {
|
|
173
|
+
const columnName = input.mapping.fieldColumns.get(fieldName);
|
|
174
|
+
if (!columnName) {
|
|
175
|
+
input.diagnostics.push({
|
|
176
|
+
code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
|
|
177
|
+
message: `${input.contextLabel} references unknown field "${input.modelName}.${fieldName}"`,
|
|
178
|
+
sourceId: input.sourceId,
|
|
179
|
+
span: input.span
|
|
180
|
+
});
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
columns.push(columnName);
|
|
184
|
+
}
|
|
185
|
+
return columns;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
//#endregion
|
|
189
|
+
//#region src/default-function-registry.ts
|
|
190
|
+
function resolveSpanPositionFromBase(base, text, offset) {
|
|
191
|
+
const safeOffset = Math.min(Math.max(0, offset), text.length);
|
|
192
|
+
let line = base.start.line;
|
|
193
|
+
let column = base.start.column;
|
|
194
|
+
for (let index = 0; index < safeOffset; index += 1) {
|
|
195
|
+
const character = text[index] ?? "";
|
|
196
|
+
if (character === "\r") {
|
|
197
|
+
if (text[index + 1] === "\n" && index + 1 < safeOffset) index += 1;
|
|
198
|
+
line += 1;
|
|
199
|
+
column = 1;
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
if (character === "\n") {
|
|
203
|
+
line += 1;
|
|
204
|
+
column = 1;
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
column += 1;
|
|
208
|
+
}
|
|
209
|
+
return {
|
|
210
|
+
offset: base.start.offset + safeOffset,
|
|
211
|
+
line,
|
|
212
|
+
column
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
function createSpanFromBase(base, startOffset, endOffset, text) {
|
|
216
|
+
const safeStart = Math.max(0, Math.min(startOffset, text.length));
|
|
217
|
+
const safeEnd = Math.max(safeStart, Math.min(endOffset, text.length));
|
|
218
|
+
return {
|
|
219
|
+
start: resolveSpanPositionFromBase(base, text, safeStart),
|
|
220
|
+
end: resolveSpanPositionFromBase(base, text, safeEnd)
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
function splitTopLevelArgs(raw) {
|
|
224
|
+
if (raw.trim().length === 0) return [];
|
|
225
|
+
const parts = [];
|
|
226
|
+
let depthParen = 0;
|
|
227
|
+
let depthBracket = 0;
|
|
228
|
+
let quote = null;
|
|
229
|
+
let start = 0;
|
|
230
|
+
for (let index = 0; index < raw.length; index += 1) {
|
|
231
|
+
const character = raw[index] ?? "";
|
|
232
|
+
if (quote) {
|
|
233
|
+
if (character === quote && raw[index - 1] !== "\\") quote = null;
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
if (character === "\"" || character === "'") {
|
|
237
|
+
quote = character;
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
if (character === "(") {
|
|
241
|
+
depthParen += 1;
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
if (character === ")") {
|
|
245
|
+
depthParen = Math.max(0, depthParen - 1);
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
if (character === "[") {
|
|
249
|
+
depthBracket += 1;
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
if (character === "]") {
|
|
253
|
+
depthBracket = Math.max(0, depthBracket - 1);
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
if (character === "," && depthParen === 0 && depthBracket === 0) {
|
|
257
|
+
parts.push({
|
|
258
|
+
raw: raw.slice(start, index),
|
|
259
|
+
start,
|
|
260
|
+
end: index
|
|
261
|
+
});
|
|
262
|
+
start = index + 1;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
parts.push({
|
|
266
|
+
raw: raw.slice(start),
|
|
267
|
+
start,
|
|
268
|
+
end: raw.length
|
|
269
|
+
});
|
|
270
|
+
return parts;
|
|
271
|
+
}
|
|
272
|
+
function parseDefaultFunctionCall(expression, expressionSpan) {
|
|
273
|
+
const trimmed = expression.trim();
|
|
274
|
+
const leadingWhitespace = expression.length - expression.trimStart().length;
|
|
275
|
+
const trailingWhitespace = expression.length - expression.trimEnd().length;
|
|
276
|
+
const contentEnd = expression.length - trailingWhitespace;
|
|
277
|
+
const openParen = trimmed.indexOf("(");
|
|
278
|
+
const closeParen = trimmed.lastIndexOf(")");
|
|
279
|
+
if (openParen <= 0 || closeParen !== trimmed.length - 1) return;
|
|
280
|
+
const functionName = trimmed.slice(0, openParen).trim();
|
|
281
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(functionName)) return;
|
|
282
|
+
const parts = splitTopLevelArgs(trimmed.slice(openParen + 1, closeParen));
|
|
283
|
+
const args = [];
|
|
284
|
+
for (const part of parts) {
|
|
285
|
+
const raw = part.raw.trim();
|
|
286
|
+
if (raw.length === 0) return;
|
|
287
|
+
const leadingPartWhitespace = part.raw.length - part.raw.trimStart().length;
|
|
288
|
+
const argStart = leadingWhitespace + openParen + 1 + part.start + leadingPartWhitespace;
|
|
289
|
+
const argEnd = argStart + raw.length;
|
|
290
|
+
args.push({
|
|
291
|
+
raw,
|
|
292
|
+
span: createSpanFromBase(expressionSpan, argStart, argEnd, expression)
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
return {
|
|
296
|
+
name: functionName,
|
|
297
|
+
raw: trimmed,
|
|
298
|
+
args,
|
|
299
|
+
span: createSpanFromBase(expressionSpan, leadingWhitespace, contentEnd, expression)
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
function formatSupportedFunctionList(registry) {
|
|
303
|
+
const signatures = Array.from(registry.entries()).sort(([a], [b]) => a.localeCompare(b)).flatMap(([functionName, entry]) => {
|
|
304
|
+
const usageSignatures = entry.usageSignatures?.filter((signature) => signature.length > 0);
|
|
305
|
+
return usageSignatures && usageSignatures.length > 0 ? usageSignatures : [`${functionName}()`];
|
|
306
|
+
});
|
|
307
|
+
return signatures.length > 0 ? signatures.join(", ") : "none";
|
|
308
|
+
}
|
|
309
|
+
function lowerDefaultFunctionWithRegistry(input) {
|
|
310
|
+
const entry = input.registry.get(input.call.name);
|
|
311
|
+
if (entry) return entry.lower({
|
|
312
|
+
call: input.call,
|
|
313
|
+
context: input.context
|
|
314
|
+
});
|
|
315
|
+
const supportedFunctionList = formatSupportedFunctionList(input.registry);
|
|
316
|
+
return {
|
|
317
|
+
ok: false,
|
|
318
|
+
diagnostic: {
|
|
319
|
+
code: "PSL_UNKNOWN_DEFAULT_FUNCTION",
|
|
320
|
+
message: `Default function "${input.call.name}" is not supported in SQL PSL provider v1. Supported functions: ${supportedFunctionList}.`,
|
|
321
|
+
sourceId: input.context.sourceId,
|
|
322
|
+
span: input.call.span
|
|
323
|
+
}
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
//#endregion
|
|
328
|
+
//#region src/psl-authoring-arguments.ts
|
|
329
|
+
const INVALID_AUTHORING_ARGUMENT = Symbol("invalidAuthoringArgument");
|
|
330
|
+
function isIdentifierStartCharacter(character) {
|
|
331
|
+
return character !== void 0 && /[A-Za-z_$]/.test(character);
|
|
332
|
+
}
|
|
333
|
+
function isIdentifierCharacter(character) {
|
|
334
|
+
return character !== void 0 && /[A-Za-z0-9_$]/.test(character);
|
|
335
|
+
}
|
|
336
|
+
function parseJsLikeLiteral(value) {
|
|
337
|
+
let index = 0;
|
|
338
|
+
function skipWhitespace() {
|
|
339
|
+
while (/\s/.test(value[index] ?? "")) index += 1;
|
|
340
|
+
}
|
|
341
|
+
function parseIdentifier() {
|
|
342
|
+
const first = value[index];
|
|
343
|
+
if (!isIdentifierStartCharacter(first)) return INVALID_AUTHORING_ARGUMENT;
|
|
344
|
+
let end = index + 1;
|
|
345
|
+
while (isIdentifierCharacter(value[end])) end += 1;
|
|
346
|
+
const identifier = value.slice(index, end);
|
|
347
|
+
index = end;
|
|
348
|
+
return identifier;
|
|
349
|
+
}
|
|
350
|
+
function parseString() {
|
|
351
|
+
const quote = value[index];
|
|
352
|
+
if (quote !== "\"" && quote !== "'") return INVALID_AUTHORING_ARGUMENT;
|
|
353
|
+
index += 1;
|
|
354
|
+
let result = "";
|
|
355
|
+
while (index < value.length) {
|
|
356
|
+
const character = value[index];
|
|
357
|
+
index += 1;
|
|
358
|
+
if (character === void 0) return INVALID_AUTHORING_ARGUMENT;
|
|
359
|
+
if (character === quote) return result;
|
|
360
|
+
if (character !== "\\") {
|
|
361
|
+
result += character;
|
|
362
|
+
continue;
|
|
363
|
+
}
|
|
364
|
+
const escaped = value[index];
|
|
365
|
+
index += 1;
|
|
366
|
+
if (escaped === void 0) return INVALID_AUTHORING_ARGUMENT;
|
|
367
|
+
switch (escaped) {
|
|
368
|
+
case "'":
|
|
369
|
+
case "\"":
|
|
370
|
+
case "\\":
|
|
371
|
+
case "/":
|
|
372
|
+
result += escaped;
|
|
373
|
+
break;
|
|
374
|
+
case "b":
|
|
375
|
+
result += "\b";
|
|
376
|
+
break;
|
|
377
|
+
case "f":
|
|
378
|
+
result += "\f";
|
|
379
|
+
break;
|
|
380
|
+
case "n":
|
|
381
|
+
result += "\n";
|
|
382
|
+
break;
|
|
383
|
+
case "r":
|
|
384
|
+
result += "\r";
|
|
385
|
+
break;
|
|
386
|
+
case "t":
|
|
387
|
+
result += " ";
|
|
388
|
+
break;
|
|
389
|
+
case "u": {
|
|
390
|
+
const hex = value.slice(index, index + 4);
|
|
391
|
+
if (!/^[0-9A-Fa-f]{4}$/.test(hex)) return INVALID_AUTHORING_ARGUMENT;
|
|
392
|
+
result += String.fromCharCode(Number.parseInt(hex, 16));
|
|
393
|
+
index += 4;
|
|
394
|
+
break;
|
|
395
|
+
}
|
|
396
|
+
default: return INVALID_AUTHORING_ARGUMENT;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
return INVALID_AUTHORING_ARGUMENT;
|
|
400
|
+
}
|
|
401
|
+
function parseNumber() {
|
|
402
|
+
const raw = value.slice(index).match(/^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?/)?.[0];
|
|
403
|
+
if (!raw) return INVALID_AUTHORING_ARGUMENT;
|
|
404
|
+
const parsed$1 = Number(raw);
|
|
405
|
+
if (!Number.isFinite(parsed$1)) return INVALID_AUTHORING_ARGUMENT;
|
|
406
|
+
index += raw.length;
|
|
407
|
+
return parsed$1;
|
|
408
|
+
}
|
|
409
|
+
function parseArray() {
|
|
410
|
+
if (value[index] !== "[") return INVALID_AUTHORING_ARGUMENT;
|
|
411
|
+
index += 1;
|
|
412
|
+
const result = [];
|
|
413
|
+
skipWhitespace();
|
|
414
|
+
if (value[index] === "]") {
|
|
415
|
+
index += 1;
|
|
416
|
+
return result;
|
|
417
|
+
}
|
|
418
|
+
while (index < value.length) {
|
|
419
|
+
const entry = parseValue();
|
|
420
|
+
if (entry === INVALID_AUTHORING_ARGUMENT) return INVALID_AUTHORING_ARGUMENT;
|
|
421
|
+
result.push(entry);
|
|
422
|
+
skipWhitespace();
|
|
423
|
+
if (value[index] === ",") {
|
|
424
|
+
index += 1;
|
|
425
|
+
skipWhitespace();
|
|
426
|
+
continue;
|
|
427
|
+
}
|
|
428
|
+
if (value[index] === "]") {
|
|
429
|
+
index += 1;
|
|
430
|
+
return result;
|
|
431
|
+
}
|
|
432
|
+
return INVALID_AUTHORING_ARGUMENT;
|
|
433
|
+
}
|
|
434
|
+
return INVALID_AUTHORING_ARGUMENT;
|
|
435
|
+
}
|
|
436
|
+
function parseObject() {
|
|
437
|
+
if (value[index] !== "{") return INVALID_AUTHORING_ARGUMENT;
|
|
438
|
+
index += 1;
|
|
439
|
+
const result = {};
|
|
440
|
+
skipWhitespace();
|
|
441
|
+
if (value[index] === "}") {
|
|
442
|
+
index += 1;
|
|
443
|
+
return result;
|
|
444
|
+
}
|
|
445
|
+
while (index < value.length) {
|
|
446
|
+
skipWhitespace();
|
|
447
|
+
const key = value[index] === "\"" || value[index] === "'" ? parseString() : parseIdentifier();
|
|
448
|
+
if (key === INVALID_AUTHORING_ARGUMENT) return INVALID_AUTHORING_ARGUMENT;
|
|
449
|
+
skipWhitespace();
|
|
450
|
+
if (value[index] !== ":") return INVALID_AUTHORING_ARGUMENT;
|
|
451
|
+
index += 1;
|
|
452
|
+
const entry = parseValue();
|
|
453
|
+
if (entry === INVALID_AUTHORING_ARGUMENT) return INVALID_AUTHORING_ARGUMENT;
|
|
454
|
+
result[key] = entry;
|
|
455
|
+
skipWhitespace();
|
|
456
|
+
if (value[index] === ",") {
|
|
457
|
+
index += 1;
|
|
458
|
+
skipWhitespace();
|
|
459
|
+
continue;
|
|
460
|
+
}
|
|
461
|
+
if (value[index] === "}") {
|
|
462
|
+
index += 1;
|
|
463
|
+
return result;
|
|
464
|
+
}
|
|
465
|
+
return INVALID_AUTHORING_ARGUMENT;
|
|
466
|
+
}
|
|
467
|
+
return INVALID_AUTHORING_ARGUMENT;
|
|
468
|
+
}
|
|
469
|
+
function parseValue() {
|
|
470
|
+
skipWhitespace();
|
|
471
|
+
const character = value[index];
|
|
472
|
+
if (character === "{") return parseObject();
|
|
473
|
+
if (character === "[") return parseArray();
|
|
474
|
+
if (character === "\"" || character === "'") return parseString();
|
|
475
|
+
if (character === "-" || /\d/.test(character ?? "")) return parseNumber();
|
|
476
|
+
const identifier = parseIdentifier();
|
|
477
|
+
if (identifier === INVALID_AUTHORING_ARGUMENT) return INVALID_AUTHORING_ARGUMENT;
|
|
478
|
+
if (identifier === "true") return true;
|
|
479
|
+
if (identifier === "false") return false;
|
|
480
|
+
if (identifier === "null") return null;
|
|
481
|
+
return INVALID_AUTHORING_ARGUMENT;
|
|
482
|
+
}
|
|
483
|
+
skipWhitespace();
|
|
484
|
+
const parsed = parseValue();
|
|
485
|
+
if (parsed === INVALID_AUTHORING_ARGUMENT) return parsed;
|
|
486
|
+
skipWhitespace();
|
|
487
|
+
return index === value.length ? parsed : INVALID_AUTHORING_ARGUMENT;
|
|
488
|
+
}
|
|
489
|
+
function parseStringArrayLiteral(value) {
|
|
490
|
+
const parsed = parseJsLikeLiteral(value);
|
|
491
|
+
if (parsed === INVALID_AUTHORING_ARGUMENT || !Array.isArray(parsed)) return INVALID_AUTHORING_ARGUMENT;
|
|
492
|
+
if (!parsed.every((item) => typeof item === "string")) return INVALID_AUTHORING_ARGUMENT;
|
|
493
|
+
return parsed;
|
|
494
|
+
}
|
|
495
|
+
function isPlainObject(value) {
|
|
496
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
497
|
+
}
|
|
498
|
+
function parsePslObjectLiteral(value) {
|
|
499
|
+
const trimmed = value.trim();
|
|
500
|
+
if (!trimmed.startsWith("{") || !trimmed.endsWith("}")) return INVALID_AUTHORING_ARGUMENT;
|
|
501
|
+
let parsed;
|
|
502
|
+
try {
|
|
503
|
+
parsed = JSON.parse(trimmed);
|
|
504
|
+
} catch {
|
|
505
|
+
parsed = parseJsLikeLiteral(trimmed);
|
|
506
|
+
if (parsed === INVALID_AUTHORING_ARGUMENT) return INVALID_AUTHORING_ARGUMENT;
|
|
507
|
+
}
|
|
508
|
+
if (!isPlainObject(parsed)) return INVALID_AUTHORING_ARGUMENT;
|
|
509
|
+
return parsed;
|
|
510
|
+
}
|
|
511
|
+
function parsePslAuthoringArgumentValue(descriptor, rawValue) {
|
|
512
|
+
switch (descriptor.kind) {
|
|
513
|
+
case "string": return unquoteStringLiteral(rawValue);
|
|
514
|
+
case "number": {
|
|
515
|
+
const parsed = Number(unquoteStringLiteral(rawValue));
|
|
516
|
+
return Number.isNaN(parsed) ? INVALID_AUTHORING_ARGUMENT : parsed;
|
|
517
|
+
}
|
|
518
|
+
case "stringArray": return parseStringArrayLiteral(rawValue);
|
|
519
|
+
case "object": return parsePslObjectLiteral(rawValue);
|
|
520
|
+
default: return INVALID_AUTHORING_ARGUMENT;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
function pushInvalidPslHelperArgument(input) {
|
|
524
|
+
input.diagnostics.push({
|
|
525
|
+
code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
|
|
526
|
+
message: `${input.entityLabel} ${input.helperLabel} ${input.message}`,
|
|
527
|
+
sourceId: input.sourceId,
|
|
528
|
+
span: input.span
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
function mapPslHelperArgs(input) {
|
|
532
|
+
const mappedArgs = input.descriptors.map(() => void 0);
|
|
533
|
+
const positionalArgs = input.args.filter((arg) => arg.kind === "positional");
|
|
534
|
+
const namedArgs = input.args.filter((arg) => arg.kind === "named");
|
|
535
|
+
if (positionalArgs.length > input.descriptors.length) return pushInvalidPslHelperArgument({
|
|
536
|
+
diagnostics: input.diagnostics,
|
|
537
|
+
sourceId: input.sourceId,
|
|
538
|
+
span: input.span,
|
|
539
|
+
entityLabel: input.entityLabel,
|
|
540
|
+
helperLabel: input.helperLabel,
|
|
541
|
+
message: `accepts at most ${input.descriptors.length} argument(s), received ${positionalArgs.length}.`
|
|
542
|
+
});
|
|
543
|
+
for (const [index, argument] of positionalArgs.entries()) {
|
|
544
|
+
const descriptor = input.descriptors[index];
|
|
545
|
+
if (!descriptor) return pushInvalidPslHelperArgument({
|
|
546
|
+
diagnostics: input.diagnostics,
|
|
547
|
+
sourceId: input.sourceId,
|
|
548
|
+
span: argument.span,
|
|
549
|
+
entityLabel: input.entityLabel,
|
|
550
|
+
helperLabel: input.helperLabel,
|
|
551
|
+
message: `does not define positional argument #${index + 1}.`
|
|
552
|
+
});
|
|
553
|
+
const value = parsePslAuthoringArgumentValue(descriptor, argument.value);
|
|
554
|
+
if (value === INVALID_AUTHORING_ARGUMENT) return pushInvalidPslHelperArgument({
|
|
555
|
+
diagnostics: input.diagnostics,
|
|
556
|
+
sourceId: input.sourceId,
|
|
557
|
+
span: argument.span,
|
|
558
|
+
entityLabel: input.entityLabel,
|
|
559
|
+
helperLabel: input.helperLabel,
|
|
560
|
+
message: `cannot parse argument #${index + 1} for descriptor kind "${descriptor.kind}".`
|
|
561
|
+
});
|
|
562
|
+
mappedArgs[index] = value;
|
|
563
|
+
}
|
|
564
|
+
for (const argument of namedArgs) {
|
|
565
|
+
const descriptorIndex = input.descriptors.findIndex((descriptor$1) => descriptor$1.name === argument.name);
|
|
566
|
+
if (descriptorIndex < 0) return pushInvalidPslHelperArgument({
|
|
567
|
+
diagnostics: input.diagnostics,
|
|
568
|
+
sourceId: input.sourceId,
|
|
569
|
+
span: argument.span,
|
|
570
|
+
entityLabel: input.entityLabel,
|
|
571
|
+
helperLabel: input.helperLabel,
|
|
572
|
+
message: `received unknown named argument "${argument.name}".`
|
|
573
|
+
});
|
|
574
|
+
if (mappedArgs[descriptorIndex] !== void 0) return pushInvalidPslHelperArgument({
|
|
575
|
+
diagnostics: input.diagnostics,
|
|
576
|
+
sourceId: input.sourceId,
|
|
577
|
+
span: argument.span,
|
|
578
|
+
entityLabel: input.entityLabel,
|
|
579
|
+
helperLabel: input.helperLabel,
|
|
580
|
+
message: `received duplicate value for argument "${argument.name}".`
|
|
581
|
+
});
|
|
582
|
+
const descriptor = input.descriptors[descriptorIndex];
|
|
583
|
+
if (!descriptor) return pushInvalidPslHelperArgument({
|
|
584
|
+
diagnostics: input.diagnostics,
|
|
585
|
+
sourceId: input.sourceId,
|
|
586
|
+
span: argument.span,
|
|
587
|
+
entityLabel: input.entityLabel,
|
|
588
|
+
helperLabel: input.helperLabel,
|
|
589
|
+
message: `does not define named argument "${argument.name}".`
|
|
590
|
+
});
|
|
591
|
+
const value = parsePslAuthoringArgumentValue(descriptor, argument.value);
|
|
592
|
+
if (value === INVALID_AUTHORING_ARGUMENT) return pushInvalidPslHelperArgument({
|
|
593
|
+
diagnostics: input.diagnostics,
|
|
594
|
+
sourceId: input.sourceId,
|
|
595
|
+
span: argument.span,
|
|
596
|
+
entityLabel: input.entityLabel,
|
|
597
|
+
helperLabel: input.helperLabel,
|
|
598
|
+
message: `cannot parse named argument "${argument.name}" for descriptor kind "${descriptor.kind}".`
|
|
599
|
+
});
|
|
600
|
+
mappedArgs[descriptorIndex] = value;
|
|
601
|
+
}
|
|
602
|
+
return mappedArgs;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
//#endregion
|
|
606
|
+
//#region src/psl-column-resolution.ts
|
|
607
|
+
function toNamedTypeFieldDescriptor(typeRef, descriptor) {
|
|
608
|
+
return {
|
|
609
|
+
codecId: descriptor.codecId,
|
|
610
|
+
nativeType: descriptor.nativeType,
|
|
611
|
+
typeRef
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
function getAuthoringTypeConstructor(contributions, path) {
|
|
615
|
+
let current = contributions?.type;
|
|
616
|
+
for (const segment of path) {
|
|
617
|
+
if (typeof current !== "object" || current === null || Array.isArray(current)) return;
|
|
618
|
+
current = current[segment];
|
|
619
|
+
}
|
|
620
|
+
return isAuthoringTypeConstructorDescriptor(current) ? current : void 0;
|
|
621
|
+
}
|
|
622
|
+
/**
|
|
623
|
+
* Returns the namespace prefix of `attributeName` if it references an
|
|
624
|
+
* unrecognized extension namespace, otherwise `undefined`. A namespace is
|
|
625
|
+
* considered recognized when it is:
|
|
626
|
+
*
|
|
627
|
+
* - `db` (native-type spec, always allowed),
|
|
628
|
+
* - the active family id (e.g. `sql`),
|
|
629
|
+
* - the active target id (e.g. `postgres`),
|
|
630
|
+
* - present in `composedExtensions`.
|
|
631
|
+
*
|
|
632
|
+
* Family/target namespaces are exempted so that e.g. `@sql.foo` surfaces as
|
|
633
|
+
* PSL_UNSUPPORTED_*_ATTRIBUTE (the attribute isn't defined) rather than
|
|
634
|
+
* PSL_EXTENSION_NAMESPACE_NOT_COMPOSED (the namespace is already composed).
|
|
635
|
+
*/
|
|
636
|
+
function checkUncomposedNamespace(attributeName, composedExtensions, context) {
|
|
637
|
+
const dotIndex = attributeName.indexOf(".");
|
|
638
|
+
if (dotIndex <= 0 || dotIndex === attributeName.length - 1) return;
|
|
639
|
+
const namespace = attributeName.slice(0, dotIndex);
|
|
640
|
+
if (namespace === "db" || namespace === context?.familyId || namespace === context?.targetId || composedExtensions.has(namespace)) return;
|
|
641
|
+
return namespace;
|
|
642
|
+
}
|
|
643
|
+
/**
|
|
644
|
+
* Pushes the canonical `PSL_EXTENSION_NAMESPACE_NOT_COMPOSED` diagnostic for a
|
|
645
|
+
* subject (attribute, model attribute, or type constructor) that references an
|
|
646
|
+
* extension namespace which is not composed in the current contract.
|
|
647
|
+
*
|
|
648
|
+
* The `data` payload carries the missing namespace so machine consumers
|
|
649
|
+
* (agents, IDE extensions, CLI auto-fix) don't have to parse the prose.
|
|
650
|
+
*/
|
|
651
|
+
function reportUncomposedNamespace(input) {
|
|
652
|
+
input.diagnostics.push({
|
|
653
|
+
code: "PSL_EXTENSION_NAMESPACE_NOT_COMPOSED",
|
|
654
|
+
message: `${input.subjectLabel} uses unrecognized namespace "${input.namespace}". Add extension pack "${input.namespace}" to extensionPacks in prisma-next.config.ts.`,
|
|
655
|
+
sourceId: input.sourceId,
|
|
656
|
+
span: input.span,
|
|
657
|
+
data: {
|
|
658
|
+
namespace: input.namespace,
|
|
659
|
+
suggestedPack: input.namespace
|
|
660
|
+
}
|
|
661
|
+
});
|
|
662
|
+
}
|
|
663
|
+
function instantiatePslTypeConstructor(input) {
|
|
664
|
+
const helperPath = input.call.path.join(".");
|
|
665
|
+
const args = mapPslHelperArgs({
|
|
666
|
+
args: input.call.args,
|
|
667
|
+
descriptors: input.descriptor.args ?? [],
|
|
668
|
+
helperLabel: `constructor "${helperPath}"`,
|
|
669
|
+
span: input.call.span,
|
|
670
|
+
diagnostics: input.diagnostics,
|
|
671
|
+
sourceId: input.sourceId,
|
|
672
|
+
entityLabel: input.entityLabel
|
|
673
|
+
});
|
|
674
|
+
if (!args) return;
|
|
675
|
+
try {
|
|
676
|
+
validateAuthoringHelperArguments(helperPath, input.descriptor.args, args);
|
|
677
|
+
return instantiateAuthoringTypeConstructor(input.descriptor, args);
|
|
678
|
+
} catch (error) {
|
|
679
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
680
|
+
input.diagnostics.push({
|
|
681
|
+
code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
|
|
682
|
+
message: `${input.entityLabel} constructor "${helperPath}" ${message}`,
|
|
683
|
+
sourceId: input.sourceId,
|
|
684
|
+
span: input.call.span
|
|
685
|
+
});
|
|
686
|
+
return;
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
function pushUnsupportedTypeConstructorDiagnostic(input) {
|
|
690
|
+
input.diagnostics.push({
|
|
691
|
+
code: input.code,
|
|
692
|
+
message: input.message,
|
|
693
|
+
sourceId: input.sourceId,
|
|
694
|
+
span: input.span
|
|
695
|
+
});
|
|
696
|
+
}
|
|
697
|
+
function resolvePslTypeConstructorDescriptor(input) {
|
|
698
|
+
const descriptor = getAuthoringTypeConstructor(input.authoringContributions, input.call.path);
|
|
699
|
+
if (descriptor) return descriptor;
|
|
700
|
+
const namespace = input.call.path.length > 1 ? input.call.path[0] : void 0;
|
|
701
|
+
if (namespace && namespace !== "db" && namespace !== input.familyId && namespace !== input.targetId && !input.composedExtensions.has(namespace)) {
|
|
702
|
+
reportUncomposedNamespace({
|
|
703
|
+
subjectLabel: `Type constructor "${input.call.path.join(".")}"`,
|
|
704
|
+
namespace,
|
|
705
|
+
sourceId: input.sourceId,
|
|
706
|
+
span: input.call.span,
|
|
707
|
+
diagnostics: input.diagnostics
|
|
708
|
+
});
|
|
709
|
+
return;
|
|
710
|
+
}
|
|
711
|
+
return pushUnsupportedTypeConstructorDiagnostic({
|
|
712
|
+
diagnostics: input.diagnostics,
|
|
713
|
+
sourceId: input.sourceId,
|
|
714
|
+
span: input.call.span,
|
|
715
|
+
code: input.unsupportedCode,
|
|
716
|
+
message: input.unsupportedMessage
|
|
717
|
+
});
|
|
718
|
+
}
|
|
719
|
+
function resolveFieldTypeDescriptor(input) {
|
|
720
|
+
if (input.field.typeConstructor) {
|
|
721
|
+
const helperPath = input.field.typeConstructor.path.join(".");
|
|
722
|
+
const descriptor$1 = resolvePslTypeConstructorDescriptor({
|
|
723
|
+
call: input.field.typeConstructor,
|
|
724
|
+
authoringContributions: input.authoringContributions,
|
|
725
|
+
composedExtensions: input.composedExtensions,
|
|
726
|
+
familyId: input.familyId,
|
|
727
|
+
targetId: input.targetId,
|
|
728
|
+
diagnostics: input.diagnostics,
|
|
729
|
+
sourceId: input.sourceId,
|
|
730
|
+
unsupportedCode: "PSL_UNSUPPORTED_FIELD_TYPE",
|
|
731
|
+
unsupportedMessage: `${input.entityLabel} type constructor "${helperPath}" is not supported in SQL PSL provider v1`
|
|
732
|
+
});
|
|
733
|
+
if (!descriptor$1) return {
|
|
734
|
+
ok: false,
|
|
735
|
+
alreadyReported: true
|
|
736
|
+
};
|
|
737
|
+
const instantiated = instantiatePslTypeConstructor({
|
|
738
|
+
call: input.field.typeConstructor,
|
|
739
|
+
descriptor: descriptor$1,
|
|
740
|
+
diagnostics: input.diagnostics,
|
|
741
|
+
sourceId: input.sourceId,
|
|
742
|
+
entityLabel: input.entityLabel
|
|
743
|
+
});
|
|
744
|
+
if (!instantiated) return {
|
|
745
|
+
ok: false,
|
|
746
|
+
alreadyReported: true
|
|
747
|
+
};
|
|
748
|
+
return {
|
|
749
|
+
ok: true,
|
|
750
|
+
descriptor: instantiated
|
|
751
|
+
};
|
|
752
|
+
}
|
|
753
|
+
const descriptor = resolveColumnDescriptor(input.field, input.enumTypeDescriptors, input.namedTypeDescriptors, input.scalarTypeDescriptors);
|
|
754
|
+
if (!descriptor) return {
|
|
755
|
+
ok: false,
|
|
756
|
+
alreadyReported: false
|
|
757
|
+
};
|
|
758
|
+
return {
|
|
759
|
+
ok: true,
|
|
760
|
+
descriptor
|
|
761
|
+
};
|
|
762
|
+
}
|
|
763
|
+
const NATIVE_TYPE_SPECS = {
|
|
764
|
+
"db.VarChar": {
|
|
765
|
+
args: "optionalLength",
|
|
766
|
+
baseType: "String",
|
|
767
|
+
codecId: "sql/varchar@1",
|
|
768
|
+
nativeType: "character varying"
|
|
769
|
+
},
|
|
770
|
+
"db.Char": {
|
|
771
|
+
args: "optionalLength",
|
|
772
|
+
baseType: "String",
|
|
773
|
+
codecId: "sql/char@1",
|
|
774
|
+
nativeType: "character"
|
|
775
|
+
},
|
|
776
|
+
"db.Uuid": {
|
|
777
|
+
args: "noArgs",
|
|
778
|
+
baseType: "String",
|
|
779
|
+
codecId: null,
|
|
780
|
+
nativeType: "uuid"
|
|
781
|
+
},
|
|
782
|
+
"db.SmallInt": {
|
|
783
|
+
args: "noArgs",
|
|
784
|
+
baseType: "Int",
|
|
785
|
+
codecId: "pg/int2@1",
|
|
786
|
+
nativeType: "int2"
|
|
787
|
+
},
|
|
788
|
+
"db.Real": {
|
|
789
|
+
args: "noArgs",
|
|
790
|
+
baseType: "Float",
|
|
791
|
+
codecId: "pg/float4@1",
|
|
792
|
+
nativeType: "float4"
|
|
793
|
+
},
|
|
794
|
+
"db.Numeric": {
|
|
795
|
+
args: "optionalNumeric",
|
|
796
|
+
baseType: "Decimal",
|
|
797
|
+
codecId: "pg/numeric@1",
|
|
798
|
+
nativeType: "numeric"
|
|
799
|
+
},
|
|
800
|
+
"db.Timestamp": {
|
|
801
|
+
args: "optionalPrecision",
|
|
802
|
+
baseType: "DateTime",
|
|
803
|
+
codecId: "pg/timestamp@1",
|
|
804
|
+
nativeType: "timestamp"
|
|
805
|
+
},
|
|
806
|
+
"db.Timestamptz": {
|
|
807
|
+
args: "optionalPrecision",
|
|
808
|
+
baseType: "DateTime",
|
|
809
|
+
codecId: "pg/timestamptz@1",
|
|
810
|
+
nativeType: "timestamptz"
|
|
811
|
+
},
|
|
812
|
+
"db.Date": {
|
|
813
|
+
args: "noArgs",
|
|
814
|
+
baseType: "DateTime",
|
|
815
|
+
codecId: null,
|
|
816
|
+
nativeType: "date"
|
|
817
|
+
},
|
|
818
|
+
"db.Time": {
|
|
819
|
+
args: "optionalPrecision",
|
|
820
|
+
baseType: "DateTime",
|
|
821
|
+
codecId: "pg/time@1",
|
|
822
|
+
nativeType: "time"
|
|
823
|
+
},
|
|
824
|
+
"db.Timetz": {
|
|
825
|
+
args: "optionalPrecision",
|
|
826
|
+
baseType: "DateTime",
|
|
827
|
+
codecId: "pg/timetz@1",
|
|
828
|
+
nativeType: "timetz"
|
|
829
|
+
},
|
|
830
|
+
"db.Json": {
|
|
831
|
+
args: "noArgs",
|
|
832
|
+
baseType: "Json",
|
|
833
|
+
codecId: "pg/json@1",
|
|
834
|
+
nativeType: "json"
|
|
835
|
+
}
|
|
836
|
+
};
|
|
837
|
+
function resolveDbNativeTypeAttribute(input) {
|
|
838
|
+
const spec = NATIVE_TYPE_SPECS[input.attribute.name];
|
|
839
|
+
if (!spec) {
|
|
840
|
+
input.diagnostics.push({
|
|
841
|
+
code: "PSL_UNSUPPORTED_NAMED_TYPE_ATTRIBUTE",
|
|
842
|
+
message: `${input.entityLabel} uses unsupported attribute "@${input.attribute.name}"`,
|
|
843
|
+
sourceId: input.sourceId,
|
|
844
|
+
span: input.attribute.span
|
|
845
|
+
});
|
|
846
|
+
return;
|
|
847
|
+
}
|
|
848
|
+
if (input.baseType !== spec.baseType) return pushInvalidAttributeArgument({
|
|
849
|
+
diagnostics: input.diagnostics,
|
|
850
|
+
sourceId: input.sourceId,
|
|
851
|
+
span: input.attribute.span,
|
|
852
|
+
message: `${input.entityLabel} uses @${input.attribute.name} on unsupported base type "${input.baseType}". Expected "${spec.baseType}".`
|
|
853
|
+
});
|
|
854
|
+
switch (spec.args) {
|
|
855
|
+
case "noArgs":
|
|
856
|
+
if (getPositionalArguments(input.attribute).length > 0 || input.attribute.args.length > 0) return pushInvalidAttributeArgument({
|
|
857
|
+
diagnostics: input.diagnostics,
|
|
858
|
+
sourceId: input.sourceId,
|
|
859
|
+
span: input.attribute.span,
|
|
860
|
+
message: `${input.entityLabel} @${input.attribute.name} does not accept arguments.`
|
|
861
|
+
});
|
|
862
|
+
return {
|
|
863
|
+
codecId: spec.codecId ?? input.baseDescriptor.codecId,
|
|
864
|
+
nativeType: spec.nativeType
|
|
865
|
+
};
|
|
866
|
+
case "optionalLength": {
|
|
867
|
+
const length = parseOptionalSingleIntegerArgument({
|
|
868
|
+
attribute: input.attribute,
|
|
869
|
+
diagnostics: input.diagnostics,
|
|
870
|
+
sourceId: input.sourceId,
|
|
871
|
+
entityLabel: input.entityLabel,
|
|
872
|
+
minimum: 1,
|
|
873
|
+
valueLabel: "positive integer length"
|
|
874
|
+
});
|
|
875
|
+
if (length === void 0) return;
|
|
876
|
+
return {
|
|
877
|
+
codecId: spec.codecId,
|
|
878
|
+
nativeType: spec.nativeType,
|
|
879
|
+
...length === null ? {} : { typeParams: { length } }
|
|
880
|
+
};
|
|
881
|
+
}
|
|
882
|
+
case "optionalPrecision": {
|
|
883
|
+
const precision = parseOptionalSingleIntegerArgument({
|
|
884
|
+
attribute: input.attribute,
|
|
885
|
+
diagnostics: input.diagnostics,
|
|
886
|
+
sourceId: input.sourceId,
|
|
887
|
+
entityLabel: input.entityLabel,
|
|
888
|
+
minimum: 0,
|
|
889
|
+
valueLabel: "non-negative integer precision"
|
|
890
|
+
});
|
|
891
|
+
if (precision === void 0) return;
|
|
892
|
+
return {
|
|
893
|
+
codecId: spec.codecId,
|
|
894
|
+
nativeType: spec.nativeType,
|
|
895
|
+
...precision === null ? {} : { typeParams: { precision } }
|
|
896
|
+
};
|
|
897
|
+
}
|
|
898
|
+
case "optionalNumeric": {
|
|
899
|
+
const numeric = parseOptionalNumericArguments({
|
|
900
|
+
attribute: input.attribute,
|
|
901
|
+
diagnostics: input.diagnostics,
|
|
902
|
+
sourceId: input.sourceId,
|
|
903
|
+
entityLabel: input.entityLabel
|
|
904
|
+
});
|
|
905
|
+
if (numeric === void 0) return;
|
|
906
|
+
return {
|
|
907
|
+
codecId: spec.codecId,
|
|
908
|
+
nativeType: spec.nativeType,
|
|
909
|
+
...numeric === null ? {} : { typeParams: numeric }
|
|
910
|
+
};
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
function parseDefaultLiteralValue(expression) {
|
|
915
|
+
const trimmed = expression.trim();
|
|
916
|
+
if (trimmed === "true" || trimmed === "false") return {
|
|
917
|
+
kind: "literal",
|
|
918
|
+
value: trimmed === "true"
|
|
919
|
+
};
|
|
920
|
+
const numericValue = Number(trimmed);
|
|
921
|
+
if (!Number.isNaN(numericValue) && trimmed.length > 0 && !/^(['"]).*\1$/.test(trimmed)) return {
|
|
922
|
+
kind: "literal",
|
|
923
|
+
value: numericValue
|
|
924
|
+
};
|
|
925
|
+
if (/^(['"]).*\1$/.test(trimmed)) return {
|
|
926
|
+
kind: "literal",
|
|
927
|
+
value: unquoteStringLiteral(trimmed)
|
|
928
|
+
};
|
|
929
|
+
}
|
|
930
|
+
function lowerDefaultForField(input) {
|
|
931
|
+
const positionalEntries = input.defaultAttribute.args.filter((arg) => arg.kind === "positional");
|
|
932
|
+
if (input.defaultAttribute.args.filter((arg) => arg.kind === "named").length > 0 || positionalEntries.length !== 1) {
|
|
933
|
+
input.diagnostics.push({
|
|
934
|
+
code: "PSL_INVALID_DEFAULT_FUNCTION_ARGUMENT",
|
|
935
|
+
message: `Field "${input.modelName}.${input.fieldName}" requires exactly one positional @default(...) expression.`,
|
|
936
|
+
sourceId: input.sourceId,
|
|
937
|
+
span: input.defaultAttribute.span
|
|
938
|
+
});
|
|
939
|
+
return {};
|
|
940
|
+
}
|
|
941
|
+
const expressionEntry = getPositionalArgumentEntry(input.defaultAttribute);
|
|
942
|
+
if (!expressionEntry) {
|
|
943
|
+
input.diagnostics.push({
|
|
944
|
+
code: "PSL_INVALID_DEFAULT_FUNCTION_ARGUMENT",
|
|
945
|
+
message: `Field "${input.modelName}.${input.fieldName}" requires a positional @default(...) expression.`,
|
|
946
|
+
sourceId: input.sourceId,
|
|
947
|
+
span: input.defaultAttribute.span
|
|
948
|
+
});
|
|
949
|
+
return {};
|
|
950
|
+
}
|
|
951
|
+
const literalDefault = parseDefaultLiteralValue(expressionEntry.value);
|
|
952
|
+
if (literalDefault) return { defaultValue: literalDefault };
|
|
953
|
+
const defaultFunctionCall = parseDefaultFunctionCall(expressionEntry.value, expressionEntry.span);
|
|
954
|
+
if (!defaultFunctionCall) {
|
|
955
|
+
input.diagnostics.push({
|
|
956
|
+
code: "PSL_INVALID_DEFAULT_VALUE",
|
|
957
|
+
message: `Unsupported default value "${expressionEntry.value}"`,
|
|
958
|
+
sourceId: input.sourceId,
|
|
959
|
+
span: input.defaultAttribute.span
|
|
960
|
+
});
|
|
961
|
+
return {};
|
|
962
|
+
}
|
|
963
|
+
const lowered = lowerDefaultFunctionWithRegistry({
|
|
964
|
+
call: defaultFunctionCall,
|
|
965
|
+
registry: input.defaultFunctionRegistry,
|
|
966
|
+
context: {
|
|
967
|
+
sourceId: input.sourceId,
|
|
968
|
+
modelName: input.modelName,
|
|
969
|
+
fieldName: input.fieldName,
|
|
970
|
+
columnCodecId: input.columnDescriptor.codecId
|
|
971
|
+
}
|
|
972
|
+
});
|
|
973
|
+
if (!lowered.ok) {
|
|
974
|
+
input.diagnostics.push(lowered.diagnostic);
|
|
975
|
+
return {};
|
|
976
|
+
}
|
|
977
|
+
if (lowered.value.kind === "storage") return { defaultValue: lowered.value.defaultValue };
|
|
978
|
+
const generatorDescriptor = input.generatorDescriptorById.get(lowered.value.generated.id);
|
|
979
|
+
if (!generatorDescriptor) {
|
|
980
|
+
input.diagnostics.push({
|
|
981
|
+
code: "PSL_INVALID_DEFAULT_APPLICABILITY",
|
|
982
|
+
message: `Default generator "${lowered.value.generated.id}" is not available in the composed mutation default registry.`,
|
|
983
|
+
sourceId: input.sourceId,
|
|
984
|
+
span: expressionEntry.span
|
|
985
|
+
});
|
|
986
|
+
return {};
|
|
987
|
+
}
|
|
988
|
+
if (!generatorDescriptor.applicableCodecIds.includes(input.columnDescriptor.codecId)) {
|
|
989
|
+
input.diagnostics.push({
|
|
990
|
+
code: "PSL_INVALID_DEFAULT_APPLICABILITY",
|
|
991
|
+
message: `Default generator "${generatorDescriptor.id}" is not applicable to "${input.modelName}.${input.fieldName}" with codecId "${input.columnDescriptor.codecId}".`,
|
|
992
|
+
sourceId: input.sourceId,
|
|
993
|
+
span: expressionEntry.span
|
|
994
|
+
});
|
|
995
|
+
return {};
|
|
996
|
+
}
|
|
997
|
+
return { executionDefault: lowered.value.generated };
|
|
998
|
+
}
|
|
999
|
+
function resolveColumnDescriptor(field, enumTypeDescriptors, namedTypeDescriptors, scalarTypeDescriptors) {
|
|
1000
|
+
if (field.typeRef && namedTypeDescriptors.has(field.typeRef)) return namedTypeDescriptors.get(field.typeRef);
|
|
1001
|
+
if (namedTypeDescriptors.has(field.typeName)) return namedTypeDescriptors.get(field.typeName);
|
|
1002
|
+
if (enumTypeDescriptors.has(field.typeName)) return enumTypeDescriptors.get(field.typeName);
|
|
1003
|
+
return scalarTypeDescriptors.get(field.typeName);
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
//#endregion
|
|
1007
|
+
//#region src/psl-field-resolution.ts
|
|
1008
|
+
const BUILTIN_FIELD_ATTRIBUTE_NAMES = new Set([
|
|
1009
|
+
"id",
|
|
1010
|
+
"unique",
|
|
1011
|
+
"default",
|
|
1012
|
+
"relation",
|
|
1013
|
+
"map"
|
|
1014
|
+
]);
|
|
1015
|
+
function validateFieldAttributes(input) {
|
|
1016
|
+
for (const attribute of input.field.attributes) {
|
|
1017
|
+
if (BUILTIN_FIELD_ATTRIBUTE_NAMES.has(attribute.name)) continue;
|
|
1018
|
+
const uncomposedNamespace = checkUncomposedNamespace(attribute.name, input.composedExtensions, {
|
|
1019
|
+
familyId: input.familyId,
|
|
1020
|
+
targetId: input.targetId
|
|
1021
|
+
});
|
|
1022
|
+
if (uncomposedNamespace) {
|
|
1023
|
+
reportUncomposedNamespace({
|
|
1024
|
+
subjectLabel: `Attribute "@${attribute.name}"`,
|
|
1025
|
+
namespace: uncomposedNamespace,
|
|
1026
|
+
sourceId: input.sourceId,
|
|
1027
|
+
span: attribute.span,
|
|
1028
|
+
diagnostics: input.diagnostics
|
|
1029
|
+
});
|
|
1030
|
+
continue;
|
|
1031
|
+
}
|
|
1032
|
+
input.diagnostics.push({
|
|
1033
|
+
code: "PSL_UNSUPPORTED_FIELD_ATTRIBUTE",
|
|
1034
|
+
message: `Field "${input.model.name}.${input.field.name}" uses unsupported attribute "@${attribute.name}"`,
|
|
1035
|
+
sourceId: input.sourceId,
|
|
1036
|
+
span: attribute.span
|
|
1037
|
+
});
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
function extractFieldConstraintNames(input) {
|
|
1041
|
+
const idAttribute = getAttribute(input.field.attributes, "id");
|
|
1042
|
+
const uniqueAttribute = getAttribute(input.field.attributes, "unique");
|
|
1043
|
+
return {
|
|
1044
|
+
idAttribute,
|
|
1045
|
+
uniqueAttribute,
|
|
1046
|
+
idName: parseConstraintMapArgument({
|
|
1047
|
+
attribute: idAttribute,
|
|
1048
|
+
sourceId: input.sourceId,
|
|
1049
|
+
diagnostics: input.diagnostics,
|
|
1050
|
+
entityLabel: `Field "${input.model.name}.${input.field.name}" @id`,
|
|
1051
|
+
span: input.field.span,
|
|
1052
|
+
code: "PSL_INVALID_ATTRIBUTE_ARGUMENT"
|
|
1053
|
+
}),
|
|
1054
|
+
uniqueName: parseConstraintMapArgument({
|
|
1055
|
+
attribute: uniqueAttribute,
|
|
1056
|
+
sourceId: input.sourceId,
|
|
1057
|
+
diagnostics: input.diagnostics,
|
|
1058
|
+
entityLabel: `Field "${input.model.name}.${input.field.name}" @unique`,
|
|
1059
|
+
span: input.field.span,
|
|
1060
|
+
code: "PSL_INVALID_ATTRIBUTE_ARGUMENT"
|
|
1061
|
+
})
|
|
1062
|
+
};
|
|
1063
|
+
}
|
|
1064
|
+
function collectResolvedFields(input) {
|
|
1065
|
+
const { model, mapping, enumTypeDescriptors, namedTypeDescriptors, modelNames, compositeTypeNames, composedExtensions, authoringContributions, familyId, targetId, defaultFunctionRegistry, generatorDescriptorById, diagnostics, sourceId, scalarTypeDescriptors } = input;
|
|
1066
|
+
const resolvedFields = [];
|
|
1067
|
+
for (const field of model.fields) {
|
|
1068
|
+
if (field.list && modelNames.has(field.typeName)) continue;
|
|
1069
|
+
validateFieldAttributes({
|
|
1070
|
+
model,
|
|
1071
|
+
field,
|
|
1072
|
+
composedExtensions,
|
|
1073
|
+
diagnostics,
|
|
1074
|
+
sourceId,
|
|
1075
|
+
familyId,
|
|
1076
|
+
targetId
|
|
1077
|
+
});
|
|
1078
|
+
if (getAttribute(field.attributes, "relation") && modelNames.has(field.typeName)) continue;
|
|
1079
|
+
const isValueObjectField = compositeTypeNames.has(field.typeName);
|
|
1080
|
+
const isListField = field.list;
|
|
1081
|
+
let descriptor;
|
|
1082
|
+
let scalarCodecId;
|
|
1083
|
+
const resolveInput = {
|
|
1084
|
+
field,
|
|
1085
|
+
enumTypeDescriptors,
|
|
1086
|
+
namedTypeDescriptors,
|
|
1087
|
+
scalarTypeDescriptors,
|
|
1088
|
+
authoringContributions,
|
|
1089
|
+
composedExtensions,
|
|
1090
|
+
familyId,
|
|
1091
|
+
targetId,
|
|
1092
|
+
diagnostics,
|
|
1093
|
+
sourceId,
|
|
1094
|
+
entityLabel: `Field "${model.name}.${field.name}"`
|
|
1095
|
+
};
|
|
1096
|
+
if (isValueObjectField) descriptor = scalarTypeDescriptors.get("Json");
|
|
1097
|
+
else if (isListField) {
|
|
1098
|
+
const resolved = resolveFieldTypeDescriptor(resolveInput);
|
|
1099
|
+
if (!resolved.ok) {
|
|
1100
|
+
if (!resolved.alreadyReported) diagnostics.push({
|
|
1101
|
+
code: "PSL_UNSUPPORTED_FIELD_TYPE",
|
|
1102
|
+
message: `Field "${model.name}.${field.name}" type "${field.typeName}" is not supported in SQL PSL provider v1`,
|
|
1103
|
+
sourceId,
|
|
1104
|
+
span: field.span
|
|
1105
|
+
});
|
|
1106
|
+
continue;
|
|
1107
|
+
}
|
|
1108
|
+
scalarCodecId = resolved.descriptor.codecId;
|
|
1109
|
+
descriptor = scalarTypeDescriptors.get("Json");
|
|
1110
|
+
} else {
|
|
1111
|
+
const resolved = resolveFieldTypeDescriptor(resolveInput);
|
|
1112
|
+
if (!resolved.ok) {
|
|
1113
|
+
if (!resolved.alreadyReported) diagnostics.push({
|
|
1114
|
+
code: "PSL_UNSUPPORTED_FIELD_TYPE",
|
|
1115
|
+
message: `Field "${model.name}.${field.name}" type "${field.typeName}" is not supported in SQL PSL provider v1`,
|
|
1116
|
+
sourceId,
|
|
1117
|
+
span: field.span
|
|
1118
|
+
});
|
|
1119
|
+
continue;
|
|
1120
|
+
}
|
|
1121
|
+
descriptor = resolved.descriptor;
|
|
1122
|
+
}
|
|
1123
|
+
if (!descriptor) continue;
|
|
1124
|
+
const defaultAttribute = getAttribute(field.attributes, "default");
|
|
1125
|
+
const loweredDefault = defaultAttribute ? lowerDefaultForField({
|
|
1126
|
+
modelName: model.name,
|
|
1127
|
+
fieldName: field.name,
|
|
1128
|
+
defaultAttribute,
|
|
1129
|
+
columnDescriptor: descriptor,
|
|
1130
|
+
generatorDescriptorById,
|
|
1131
|
+
sourceId,
|
|
1132
|
+
defaultFunctionRegistry,
|
|
1133
|
+
diagnostics
|
|
1134
|
+
}) : {};
|
|
1135
|
+
if (field.optional && loweredDefault.executionDefault) {
|
|
1136
|
+
const generatorDescription = loweredDefault.executionDefault.kind === "generator" ? `"${loweredDefault.executionDefault.id}"` : "for this field";
|
|
1137
|
+
diagnostics.push({
|
|
1138
|
+
code: "PSL_INVALID_DEFAULT_FUNCTION_ARGUMENT",
|
|
1139
|
+
message: `Field "${model.name}.${field.name}" cannot be optional when using execution default ${generatorDescription}. Remove "?" or use a storage default.`,
|
|
1140
|
+
sourceId,
|
|
1141
|
+
span: defaultAttribute?.span ?? field.span
|
|
1142
|
+
});
|
|
1143
|
+
continue;
|
|
1144
|
+
}
|
|
1145
|
+
if (loweredDefault.executionDefault) {
|
|
1146
|
+
const generatedDescriptor = generatorDescriptorById.get(loweredDefault.executionDefault.id)?.resolveGeneratedColumnDescriptor?.({ generated: loweredDefault.executionDefault });
|
|
1147
|
+
if (generatedDescriptor) descriptor = generatedDescriptor;
|
|
1148
|
+
}
|
|
1149
|
+
const mappedColumnName = mapping.fieldColumns.get(field.name) ?? field.name;
|
|
1150
|
+
const { idAttribute, uniqueAttribute, idName, uniqueName } = extractFieldConstraintNames({
|
|
1151
|
+
model,
|
|
1152
|
+
field,
|
|
1153
|
+
sourceId,
|
|
1154
|
+
diagnostics
|
|
1155
|
+
});
|
|
1156
|
+
resolvedFields.push({
|
|
1157
|
+
field,
|
|
1158
|
+
columnName: mappedColumnName,
|
|
1159
|
+
descriptor,
|
|
1160
|
+
...ifDefined("defaultValue", loweredDefault.defaultValue),
|
|
1161
|
+
...ifDefined("executionDefault", loweredDefault.executionDefault),
|
|
1162
|
+
isId: Boolean(idAttribute),
|
|
1163
|
+
isUnique: Boolean(uniqueAttribute),
|
|
1164
|
+
...ifDefined("idName", idName),
|
|
1165
|
+
...ifDefined("uniqueName", uniqueName),
|
|
1166
|
+
...ifDefined("many", isListField ? true : void 0),
|
|
1167
|
+
...ifDefined("valueObjectTypeName", isValueObjectField ? field.typeName : void 0),
|
|
1168
|
+
...ifDefined("scalarCodecId", scalarCodecId)
|
|
1169
|
+
});
|
|
1170
|
+
}
|
|
1171
|
+
return resolvedFields;
|
|
1172
|
+
}
|
|
1173
|
+
function buildModelMappings(models, diagnostics, sourceId) {
|
|
1174
|
+
const result = /* @__PURE__ */ new Map();
|
|
1175
|
+
for (const model of models) {
|
|
1176
|
+
const tableName = parseMapName({
|
|
1177
|
+
attribute: getAttribute(model.attributes, "map"),
|
|
1178
|
+
defaultValue: lowerFirst(model.name),
|
|
1179
|
+
sourceId,
|
|
1180
|
+
diagnostics,
|
|
1181
|
+
entityLabel: `Model "${model.name}"`,
|
|
1182
|
+
span: model.span
|
|
1183
|
+
});
|
|
1184
|
+
const fieldColumns = /* @__PURE__ */ new Map();
|
|
1185
|
+
for (const field of model.fields) {
|
|
1186
|
+
const columnName = parseMapName({
|
|
1187
|
+
attribute: getAttribute(field.attributes, "map"),
|
|
1188
|
+
defaultValue: field.name,
|
|
1189
|
+
sourceId,
|
|
1190
|
+
diagnostics,
|
|
1191
|
+
entityLabel: `Field "${model.name}.${field.name}"`,
|
|
1192
|
+
span: field.span
|
|
1193
|
+
});
|
|
1194
|
+
fieldColumns.set(field.name, columnName);
|
|
1195
|
+
}
|
|
1196
|
+
result.set(model.name, {
|
|
1197
|
+
model,
|
|
1198
|
+
tableName,
|
|
1199
|
+
fieldColumns
|
|
1200
|
+
});
|
|
1201
|
+
}
|
|
1202
|
+
return result;
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
//#endregion
|
|
1206
|
+
//#region src/psl-relation-resolution.ts
|
|
1207
|
+
const REFERENTIAL_ACTION_MAP = {
|
|
1208
|
+
NoAction: "noAction",
|
|
1209
|
+
Restrict: "restrict",
|
|
1210
|
+
Cascade: "cascade",
|
|
1211
|
+
SetNull: "setNull",
|
|
1212
|
+
SetDefault: "setDefault",
|
|
1213
|
+
noAction: "noAction",
|
|
1214
|
+
restrict: "restrict",
|
|
1215
|
+
cascade: "cascade",
|
|
1216
|
+
setNull: "setNull",
|
|
1217
|
+
setDefault: "setDefault"
|
|
1218
|
+
};
|
|
1219
|
+
function fkRelationPairKey(declaringModelName, targetModelName) {
|
|
1220
|
+
return `${declaringModelName}::${targetModelName}`;
|
|
1221
|
+
}
|
|
1222
|
+
function normalizeReferentialAction(input) {
|
|
1223
|
+
const normalized = REFERENTIAL_ACTION_MAP[input.actionToken];
|
|
1224
|
+
if (normalized) return normalized;
|
|
1225
|
+
input.diagnostics.push({
|
|
1226
|
+
code: "PSL_UNSUPPORTED_REFERENTIAL_ACTION",
|
|
1227
|
+
message: `Relation field "${input.modelName}.${input.fieldName}" has unsupported ${input.actionName} action "${input.actionToken}"`,
|
|
1228
|
+
sourceId: input.sourceId,
|
|
1229
|
+
span: input.span
|
|
1230
|
+
});
|
|
1231
|
+
}
|
|
1232
|
+
function parseRelationAttribute(input) {
|
|
1233
|
+
if (input.attribute.args.filter((arg) => arg.kind === "positional").length > 1) {
|
|
1234
|
+
input.diagnostics.push({
|
|
1235
|
+
code: "PSL_INVALID_RELATION_ATTRIBUTE",
|
|
1236
|
+
message: `Relation field "${input.modelName}.${input.fieldName}" has too many positional arguments`,
|
|
1237
|
+
sourceId: input.sourceId,
|
|
1238
|
+
span: input.attribute.span
|
|
1239
|
+
});
|
|
1240
|
+
return;
|
|
1241
|
+
}
|
|
1242
|
+
let relationNameFromPositional;
|
|
1243
|
+
const positionalNameEntry = getPositionalArgumentEntry(input.attribute);
|
|
1244
|
+
if (positionalNameEntry) {
|
|
1245
|
+
const parsedName = parseQuotedStringLiteral(positionalNameEntry.value);
|
|
1246
|
+
if (!parsedName) {
|
|
1247
|
+
input.diagnostics.push({
|
|
1248
|
+
code: "PSL_INVALID_RELATION_ATTRIBUTE",
|
|
1249
|
+
message: `Relation field "${input.modelName}.${input.fieldName}" positional relation name must be a quoted string literal`,
|
|
1250
|
+
sourceId: input.sourceId,
|
|
1251
|
+
span: positionalNameEntry.span
|
|
1252
|
+
});
|
|
1253
|
+
return;
|
|
1254
|
+
}
|
|
1255
|
+
relationNameFromPositional = parsedName;
|
|
1256
|
+
}
|
|
1257
|
+
for (const arg of input.attribute.args) {
|
|
1258
|
+
if (arg.kind === "positional") continue;
|
|
1259
|
+
if (arg.name !== "name" && arg.name !== "fields" && arg.name !== "references" && arg.name !== "map" && arg.name !== "onDelete" && arg.name !== "onUpdate") {
|
|
1260
|
+
input.diagnostics.push({
|
|
1261
|
+
code: "PSL_INVALID_RELATION_ATTRIBUTE",
|
|
1262
|
+
message: `Relation field "${input.modelName}.${input.fieldName}" has unsupported argument "${arg.name}"`,
|
|
1263
|
+
sourceId: input.sourceId,
|
|
1264
|
+
span: arg.span
|
|
1265
|
+
});
|
|
1266
|
+
return;
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
const namedRelationNameRaw = getNamedArgument(input.attribute, "name");
|
|
1270
|
+
const namedRelationName = namedRelationNameRaw ? parseQuotedStringLiteral(namedRelationNameRaw) : void 0;
|
|
1271
|
+
if (namedRelationNameRaw && !namedRelationName) {
|
|
1272
|
+
input.diagnostics.push({
|
|
1273
|
+
code: "PSL_INVALID_RELATION_ATTRIBUTE",
|
|
1274
|
+
message: `Relation field "${input.modelName}.${input.fieldName}" named relation name must be a quoted string literal`,
|
|
1275
|
+
sourceId: input.sourceId,
|
|
1276
|
+
span: input.attribute.span
|
|
1277
|
+
});
|
|
1278
|
+
return;
|
|
1279
|
+
}
|
|
1280
|
+
if (relationNameFromPositional && namedRelationName && relationNameFromPositional !== namedRelationName) {
|
|
1281
|
+
input.diagnostics.push({
|
|
1282
|
+
code: "PSL_INVALID_RELATION_ATTRIBUTE",
|
|
1283
|
+
message: `Relation field "${input.modelName}.${input.fieldName}" has conflicting positional and named relation names`,
|
|
1284
|
+
sourceId: input.sourceId,
|
|
1285
|
+
span: input.attribute.span
|
|
1286
|
+
});
|
|
1287
|
+
return;
|
|
1288
|
+
}
|
|
1289
|
+
const relationName = namedRelationName ?? relationNameFromPositional;
|
|
1290
|
+
const constraintNameRaw = getNamedArgument(input.attribute, "map");
|
|
1291
|
+
const constraintName = constraintNameRaw ? parseQuotedStringLiteral(constraintNameRaw) : void 0;
|
|
1292
|
+
if (constraintNameRaw && !constraintName) {
|
|
1293
|
+
input.diagnostics.push({
|
|
1294
|
+
code: "PSL_INVALID_RELATION_ATTRIBUTE",
|
|
1295
|
+
message: `Relation field "${input.modelName}.${input.fieldName}" map argument must be a quoted string literal`,
|
|
1296
|
+
sourceId: input.sourceId,
|
|
1297
|
+
span: input.attribute.span
|
|
1298
|
+
});
|
|
1299
|
+
return;
|
|
1300
|
+
}
|
|
1301
|
+
const fieldsRaw = getNamedArgument(input.attribute, "fields");
|
|
1302
|
+
const referencesRaw = getNamedArgument(input.attribute, "references");
|
|
1303
|
+
if (fieldsRaw && !referencesRaw || !fieldsRaw && referencesRaw) {
|
|
1304
|
+
input.diagnostics.push({
|
|
1305
|
+
code: "PSL_INVALID_RELATION_ATTRIBUTE",
|
|
1306
|
+
message: `Relation field "${input.modelName}.${input.fieldName}" requires fields and references arguments`,
|
|
1307
|
+
sourceId: input.sourceId,
|
|
1308
|
+
span: input.attribute.span
|
|
1309
|
+
});
|
|
1310
|
+
return;
|
|
1311
|
+
}
|
|
1312
|
+
let fields;
|
|
1313
|
+
let references;
|
|
1314
|
+
if (fieldsRaw && referencesRaw) {
|
|
1315
|
+
const parsedFields = parseFieldList(fieldsRaw);
|
|
1316
|
+
const parsedReferences = parseFieldList(referencesRaw);
|
|
1317
|
+
if (!parsedFields || !parsedReferences || parsedFields.length === 0 || parsedReferences.length === 0) {
|
|
1318
|
+
input.diagnostics.push({
|
|
1319
|
+
code: "PSL_INVALID_RELATION_ATTRIBUTE",
|
|
1320
|
+
message: `Relation field "${input.modelName}.${input.fieldName}" requires bracketed fields and references lists`,
|
|
1321
|
+
sourceId: input.sourceId,
|
|
1322
|
+
span: input.attribute.span
|
|
1323
|
+
});
|
|
1324
|
+
return;
|
|
1325
|
+
}
|
|
1326
|
+
fields = parsedFields;
|
|
1327
|
+
references = parsedReferences;
|
|
1328
|
+
}
|
|
1329
|
+
const onDeleteArgument = getNamedArgument(input.attribute, "onDelete");
|
|
1330
|
+
const onUpdateArgument = getNamedArgument(input.attribute, "onUpdate");
|
|
1331
|
+
return {
|
|
1332
|
+
...ifDefined("relationName", relationName),
|
|
1333
|
+
...ifDefined("fields", fields),
|
|
1334
|
+
...ifDefined("references", references),
|
|
1335
|
+
...ifDefined("constraintName", constraintName),
|
|
1336
|
+
...ifDefined("onDelete", onDeleteArgument ? unquoteStringLiteral(onDeleteArgument) : void 0),
|
|
1337
|
+
...ifDefined("onUpdate", onUpdateArgument ? unquoteStringLiteral(onUpdateArgument) : void 0)
|
|
1338
|
+
};
|
|
1339
|
+
}
|
|
1340
|
+
function indexFkRelations(input) {
|
|
1341
|
+
const modelRelations = /* @__PURE__ */ new Map();
|
|
1342
|
+
const fkRelationsByPair = /* @__PURE__ */ new Map();
|
|
1343
|
+
for (const relation of input.fkRelationMetadata) {
|
|
1344
|
+
const existing = modelRelations.get(relation.declaringModelName);
|
|
1345
|
+
const current = existing ?? [];
|
|
1346
|
+
if (!existing) modelRelations.set(relation.declaringModelName, current);
|
|
1347
|
+
current.push({
|
|
1348
|
+
fieldName: relation.declaringFieldName,
|
|
1349
|
+
toModel: relation.targetModelName,
|
|
1350
|
+
toTable: relation.targetTableName,
|
|
1351
|
+
cardinality: "N:1",
|
|
1352
|
+
on: {
|
|
1353
|
+
parentTable: relation.declaringTableName,
|
|
1354
|
+
parentColumns: relation.localColumns,
|
|
1355
|
+
childTable: relation.targetTableName,
|
|
1356
|
+
childColumns: relation.referencedColumns
|
|
1357
|
+
}
|
|
1358
|
+
});
|
|
1359
|
+
const pairKey = fkRelationPairKey(relation.declaringModelName, relation.targetModelName);
|
|
1360
|
+
const pairRelations = fkRelationsByPair.get(pairKey);
|
|
1361
|
+
if (!pairRelations) {
|
|
1362
|
+
fkRelationsByPair.set(pairKey, [relation]);
|
|
1363
|
+
continue;
|
|
1364
|
+
}
|
|
1365
|
+
pairRelations.push(relation);
|
|
1366
|
+
}
|
|
1367
|
+
return {
|
|
1368
|
+
modelRelations,
|
|
1369
|
+
fkRelationsByPair
|
|
1370
|
+
};
|
|
1371
|
+
}
|
|
1372
|
+
function applyBackrelationCandidates(input) {
|
|
1373
|
+
for (const candidate of input.backrelationCandidates) {
|
|
1374
|
+
const pairKey = fkRelationPairKey(candidate.targetModelName, candidate.modelName);
|
|
1375
|
+
const pairMatches = input.fkRelationsByPair.get(pairKey) ?? [];
|
|
1376
|
+
const matches = candidate.relationName ? pairMatches.filter((relation) => relation.relationName === candidate.relationName) : [...pairMatches];
|
|
1377
|
+
if (matches.length === 0) {
|
|
1378
|
+
input.diagnostics.push({
|
|
1379
|
+
code: "PSL_ORPHANED_BACKRELATION_LIST",
|
|
1380
|
+
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.`,
|
|
1381
|
+
sourceId: input.sourceId,
|
|
1382
|
+
span: candidate.field.span
|
|
1383
|
+
});
|
|
1384
|
+
continue;
|
|
1385
|
+
}
|
|
1386
|
+
if (matches.length > 1) {
|
|
1387
|
+
input.diagnostics.push({
|
|
1388
|
+
code: "PSL_AMBIGUOUS_BACKRELATION_LIST",
|
|
1389
|
+
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.`,
|
|
1390
|
+
sourceId: input.sourceId,
|
|
1391
|
+
span: candidate.field.span
|
|
1392
|
+
});
|
|
1393
|
+
continue;
|
|
1394
|
+
}
|
|
1395
|
+
invariant(matches.length === 1, "Backrelation matching requires exactly one match");
|
|
1396
|
+
const matched = matches[0];
|
|
1397
|
+
assertDefined(matched, "Backrelation matching requires a defined relation match");
|
|
1398
|
+
const existing = input.modelRelations.get(candidate.modelName);
|
|
1399
|
+
const current = existing ?? [];
|
|
1400
|
+
if (!existing) input.modelRelations.set(candidate.modelName, current);
|
|
1401
|
+
current.push({
|
|
1402
|
+
fieldName: candidate.field.name,
|
|
1403
|
+
toModel: matched.declaringModelName,
|
|
1404
|
+
toTable: matched.declaringTableName,
|
|
1405
|
+
cardinality: "1:N",
|
|
1406
|
+
on: {
|
|
1407
|
+
parentTable: candidate.tableName,
|
|
1408
|
+
parentColumns: matched.referencedColumns,
|
|
1409
|
+
childTable: matched.declaringTableName,
|
|
1410
|
+
childColumns: matched.localColumns
|
|
1411
|
+
}
|
|
1412
|
+
});
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
function validateNavigationListFieldAttributes(input) {
|
|
1416
|
+
let valid = true;
|
|
1417
|
+
for (const attribute of input.field.attributes) {
|
|
1418
|
+
if (attribute.name === "relation") continue;
|
|
1419
|
+
const uncomposedNamespace = checkUncomposedNamespace(attribute.name, input.composedExtensions, {
|
|
1420
|
+
familyId: input.familyId,
|
|
1421
|
+
targetId: input.targetId
|
|
1422
|
+
});
|
|
1423
|
+
if (uncomposedNamespace) {
|
|
1424
|
+
reportUncomposedNamespace({
|
|
1425
|
+
subjectLabel: `Attribute "@${attribute.name}"`,
|
|
1426
|
+
namespace: uncomposedNamespace,
|
|
1427
|
+
sourceId: input.sourceId,
|
|
1428
|
+
span: attribute.span,
|
|
1429
|
+
diagnostics: input.diagnostics
|
|
1430
|
+
});
|
|
1431
|
+
valid = false;
|
|
1432
|
+
continue;
|
|
1433
|
+
}
|
|
1434
|
+
input.diagnostics.push({
|
|
1435
|
+
code: "PSL_UNSUPPORTED_FIELD_ATTRIBUTE",
|
|
1436
|
+
message: `Field "${input.modelName}.${input.field.name}" uses unsupported attribute "@${attribute.name}"`,
|
|
1437
|
+
sourceId: input.sourceId,
|
|
1438
|
+
span: attribute.span
|
|
1439
|
+
});
|
|
1440
|
+
valid = false;
|
|
1441
|
+
}
|
|
1442
|
+
return valid;
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
//#endregion
|
|
1446
|
+
//#region src/interpreter.ts
|
|
1447
|
+
function buildComposedExtensionPackRefs(target, extensionIds, extensionPackRefs = []) {
|
|
1448
|
+
if (extensionIds.length === 0) return;
|
|
1449
|
+
const extensionPackRefById = new Map(extensionPackRefs.map((packRef) => [packRef.id, packRef]));
|
|
1450
|
+
return Object.fromEntries(extensionIds.map((extensionId) => [extensionId, extensionPackRefById.get(extensionId) ?? {
|
|
1451
|
+
kind: "extension",
|
|
1452
|
+
id: extensionId,
|
|
1453
|
+
familyId: target.familyId,
|
|
1454
|
+
targetId: target.targetId,
|
|
1455
|
+
version: "0.0.1"
|
|
1456
|
+
}]));
|
|
1457
|
+
}
|
|
1458
|
+
function diagnosticDedupKey(diagnostic) {
|
|
1459
|
+
const span = diagnostic.span;
|
|
1460
|
+
const spanKey = span ? `${span.start.offset}:${span.end.offset}:${span.start.line}:${span.end.line}` : "";
|
|
1461
|
+
return `${diagnostic.code}\u0000${diagnostic.sourceId}\u0000${spanKey}\u0000${diagnostic.message}`;
|
|
1462
|
+
}
|
|
1463
|
+
function dedupeDiagnostics(diagnostics) {
|
|
1464
|
+
const seen = /* @__PURE__ */ new Map();
|
|
1465
|
+
for (const diagnostic of diagnostics) {
|
|
1466
|
+
const key = diagnosticDedupKey(diagnostic);
|
|
1467
|
+
if (!seen.has(key)) seen.set(key, diagnostic);
|
|
1468
|
+
}
|
|
1469
|
+
return [...seen.values()];
|
|
1470
|
+
}
|
|
1471
|
+
function compareStrings(left, right) {
|
|
1472
|
+
if (left < right) return -1;
|
|
1473
|
+
if (left > right) return 1;
|
|
1474
|
+
return 0;
|
|
1475
|
+
}
|
|
1476
|
+
function mapParserDiagnostics(document) {
|
|
1477
|
+
return document.diagnostics.map((diagnostic) => ({
|
|
1478
|
+
code: diagnostic.code,
|
|
1479
|
+
message: diagnostic.message,
|
|
1480
|
+
sourceId: diagnostic.sourceId,
|
|
1481
|
+
span: diagnostic.span
|
|
1482
|
+
}));
|
|
1483
|
+
}
|
|
1484
|
+
function processEnumDeclarations(input) {
|
|
1485
|
+
const storageTypes = {};
|
|
1486
|
+
const enumTypeDescriptors = /* @__PURE__ */ new Map();
|
|
1487
|
+
for (const enumDeclaration of input.enums) {
|
|
1488
|
+
const nativeType = parseMapName({
|
|
1489
|
+
attribute: getAttribute(enumDeclaration.attributes, "map"),
|
|
1490
|
+
defaultValue: enumDeclaration.name,
|
|
1491
|
+
sourceId: input.sourceId,
|
|
1492
|
+
diagnostics: input.diagnostics,
|
|
1493
|
+
entityLabel: `Enum "${enumDeclaration.name}"`,
|
|
1494
|
+
span: enumDeclaration.span
|
|
1495
|
+
});
|
|
1496
|
+
const enumStorageType = input.enumTypeConstructor ? instantiateAuthoringTypeConstructor(input.enumTypeConstructor, [nativeType, enumDeclaration.values.map((value) => value.name)]) : {
|
|
1497
|
+
codecId: "pg/enum@1",
|
|
1498
|
+
nativeType,
|
|
1499
|
+
typeParams: { values: enumDeclaration.values.map((value) => value.name) }
|
|
1500
|
+
};
|
|
1501
|
+
const descriptor = {
|
|
1502
|
+
codecId: enumStorageType.codecId,
|
|
1503
|
+
nativeType: enumStorageType.nativeType,
|
|
1504
|
+
typeRef: enumDeclaration.name
|
|
1505
|
+
};
|
|
1506
|
+
enumTypeDescriptors.set(enumDeclaration.name, descriptor);
|
|
1507
|
+
storageTypes[enumDeclaration.name] = {
|
|
1508
|
+
codecId: enumStorageType.codecId,
|
|
1509
|
+
nativeType: enumStorageType.nativeType,
|
|
1510
|
+
typeParams: enumStorageType.typeParams ?? { values: enumDeclaration.values.map((value) => value.name) }
|
|
1511
|
+
};
|
|
1512
|
+
}
|
|
1513
|
+
return {
|
|
1514
|
+
storageTypes,
|
|
1515
|
+
enumTypeDescriptors
|
|
1516
|
+
};
|
|
1517
|
+
}
|
|
1518
|
+
function validateNamedTypeAttributes(input) {
|
|
1519
|
+
const [dbNativeTypeAttribute, ...extraDbNativeTypeAttributes] = input.allowDbNativeType ? input.declaration.attributes.filter((attribute) => attribute.name.startsWith("db.")) : [];
|
|
1520
|
+
let hasUnsupportedNamedTypeAttribute = false;
|
|
1521
|
+
for (const extra of extraDbNativeTypeAttributes) {
|
|
1522
|
+
input.diagnostics.push({
|
|
1523
|
+
code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
|
|
1524
|
+
message: `Named type "${input.declaration.name}" can declare at most one @db.* attribute`,
|
|
1525
|
+
sourceId: input.sourceId,
|
|
1526
|
+
span: extra.span
|
|
1527
|
+
});
|
|
1528
|
+
hasUnsupportedNamedTypeAttribute = true;
|
|
1529
|
+
}
|
|
1530
|
+
for (const attribute of input.declaration.attributes) {
|
|
1531
|
+
if (input.allowDbNativeType && attribute.name.startsWith("db.")) continue;
|
|
1532
|
+
const uncomposedNamespace = checkUncomposedNamespace(attribute.name, input.composedExtensions, {
|
|
1533
|
+
familyId: input.familyId,
|
|
1534
|
+
targetId: input.targetId
|
|
1535
|
+
});
|
|
1536
|
+
if (uncomposedNamespace) {
|
|
1537
|
+
reportUncomposedNamespace({
|
|
1538
|
+
subjectLabel: `Attribute "@${attribute.name}"`,
|
|
1539
|
+
namespace: uncomposedNamespace,
|
|
1540
|
+
sourceId: input.sourceId,
|
|
1541
|
+
span: attribute.span,
|
|
1542
|
+
diagnostics: input.diagnostics
|
|
1543
|
+
});
|
|
1544
|
+
hasUnsupportedNamedTypeAttribute = true;
|
|
1545
|
+
continue;
|
|
1546
|
+
}
|
|
1547
|
+
input.diagnostics.push({
|
|
1548
|
+
code: "PSL_UNSUPPORTED_NAMED_TYPE_ATTRIBUTE",
|
|
1549
|
+
message: `Named type "${input.declaration.name}" uses unsupported attribute "${attribute.name}"`,
|
|
1550
|
+
sourceId: input.sourceId,
|
|
1551
|
+
span: attribute.span
|
|
1552
|
+
});
|
|
1553
|
+
hasUnsupportedNamedTypeAttribute = true;
|
|
1554
|
+
}
|
|
1555
|
+
return {
|
|
1556
|
+
dbNativeTypeAttribute,
|
|
1557
|
+
hasUnsupportedNamedTypeAttribute
|
|
1558
|
+
};
|
|
1559
|
+
}
|
|
1560
|
+
function resolveNamedTypeDeclarations(input) {
|
|
1561
|
+
const storageTypes = {};
|
|
1562
|
+
const namedTypeDescriptors = /* @__PURE__ */ new Map();
|
|
1563
|
+
for (const declaration of input.declarations) {
|
|
1564
|
+
if (declaration.typeConstructor) {
|
|
1565
|
+
const { hasUnsupportedNamedTypeAttribute: hasUnsupportedNamedTypeAttribute$1 } = validateNamedTypeAttributes({
|
|
1566
|
+
declaration,
|
|
1567
|
+
sourceId: input.sourceId,
|
|
1568
|
+
diagnostics: input.diagnostics,
|
|
1569
|
+
composedExtensions: input.composedExtensions,
|
|
1570
|
+
allowDbNativeType: false,
|
|
1571
|
+
familyId: input.familyId,
|
|
1572
|
+
targetId: input.targetId
|
|
1573
|
+
});
|
|
1574
|
+
if (hasUnsupportedNamedTypeAttribute$1) continue;
|
|
1575
|
+
const helperPath = declaration.typeConstructor.path.join(".");
|
|
1576
|
+
const typeConstructor = resolvePslTypeConstructorDescriptor({
|
|
1577
|
+
call: declaration.typeConstructor,
|
|
1578
|
+
authoringContributions: input.authoringContributions,
|
|
1579
|
+
composedExtensions: input.composedExtensions,
|
|
1580
|
+
familyId: input.familyId,
|
|
1581
|
+
targetId: input.targetId,
|
|
1582
|
+
diagnostics: input.diagnostics,
|
|
1583
|
+
sourceId: input.sourceId,
|
|
1584
|
+
unsupportedCode: "PSL_UNSUPPORTED_NAMED_TYPE_CONSTRUCTOR",
|
|
1585
|
+
unsupportedMessage: `Named type "${declaration.name}" references unsupported constructor "${helperPath}"`
|
|
1586
|
+
});
|
|
1587
|
+
if (!typeConstructor) continue;
|
|
1588
|
+
const storageType = instantiatePslTypeConstructor({
|
|
1589
|
+
call: declaration.typeConstructor,
|
|
1590
|
+
descriptor: typeConstructor,
|
|
1591
|
+
diagnostics: input.diagnostics,
|
|
1592
|
+
sourceId: input.sourceId,
|
|
1593
|
+
entityLabel: `Named type "${declaration.name}"`
|
|
1594
|
+
});
|
|
1595
|
+
if (!storageType) continue;
|
|
1596
|
+
namedTypeDescriptors.set(declaration.name, toNamedTypeFieldDescriptor(declaration.name, storageType));
|
|
1597
|
+
storageTypes[declaration.name] = {
|
|
1598
|
+
codecId: storageType.codecId,
|
|
1599
|
+
nativeType: storageType.nativeType,
|
|
1600
|
+
typeParams: storageType.typeParams ?? {}
|
|
1601
|
+
};
|
|
1602
|
+
continue;
|
|
1603
|
+
}
|
|
1604
|
+
if (declaration.baseType === void 0) {
|
|
1605
|
+
input.diagnostics.push({
|
|
1606
|
+
code: "PSL_UNSUPPORTED_NAMED_TYPE_BASE",
|
|
1607
|
+
message: `Named type "${declaration.name}" must declare a base type or constructor`,
|
|
1608
|
+
sourceId: input.sourceId,
|
|
1609
|
+
span: declaration.span
|
|
1610
|
+
});
|
|
1611
|
+
continue;
|
|
1612
|
+
}
|
|
1613
|
+
const { baseType } = declaration;
|
|
1614
|
+
const baseDescriptor = input.enumTypeDescriptors.get(baseType) ?? input.scalarTypeDescriptors.get(baseType);
|
|
1615
|
+
if (!baseDescriptor) {
|
|
1616
|
+
input.diagnostics.push({
|
|
1617
|
+
code: "PSL_UNSUPPORTED_NAMED_TYPE_BASE",
|
|
1618
|
+
message: `Named type "${declaration.name}" references unsupported base type "${baseType}"`,
|
|
1619
|
+
sourceId: input.sourceId,
|
|
1620
|
+
span: declaration.span
|
|
1621
|
+
});
|
|
1622
|
+
continue;
|
|
1623
|
+
}
|
|
1624
|
+
const { dbNativeTypeAttribute, hasUnsupportedNamedTypeAttribute } = validateNamedTypeAttributes({
|
|
1625
|
+
declaration,
|
|
1626
|
+
sourceId: input.sourceId,
|
|
1627
|
+
diagnostics: input.diagnostics,
|
|
1628
|
+
composedExtensions: input.composedExtensions,
|
|
1629
|
+
allowDbNativeType: true,
|
|
1630
|
+
familyId: input.familyId,
|
|
1631
|
+
targetId: input.targetId
|
|
1632
|
+
});
|
|
1633
|
+
if (hasUnsupportedNamedTypeAttribute) continue;
|
|
1634
|
+
if (dbNativeTypeAttribute) {
|
|
1635
|
+
const descriptor$1 = resolveDbNativeTypeAttribute({
|
|
1636
|
+
attribute: dbNativeTypeAttribute,
|
|
1637
|
+
baseType,
|
|
1638
|
+
baseDescriptor,
|
|
1639
|
+
diagnostics: input.diagnostics,
|
|
1640
|
+
sourceId: input.sourceId,
|
|
1641
|
+
entityLabel: `Named type "${declaration.name}"`
|
|
1642
|
+
});
|
|
1643
|
+
if (!descriptor$1) continue;
|
|
1644
|
+
namedTypeDescriptors.set(declaration.name, toNamedTypeFieldDescriptor(declaration.name, descriptor$1));
|
|
1645
|
+
storageTypes[declaration.name] = {
|
|
1646
|
+
codecId: descriptor$1.codecId,
|
|
1647
|
+
nativeType: descriptor$1.nativeType,
|
|
1648
|
+
typeParams: descriptor$1.typeParams ?? {}
|
|
1649
|
+
};
|
|
1650
|
+
continue;
|
|
1651
|
+
}
|
|
1652
|
+
const descriptor = toNamedTypeFieldDescriptor(declaration.name, baseDescriptor);
|
|
1653
|
+
namedTypeDescriptors.set(declaration.name, descriptor);
|
|
1654
|
+
storageTypes[declaration.name] = {
|
|
1655
|
+
codecId: baseDescriptor.codecId,
|
|
1656
|
+
nativeType: baseDescriptor.nativeType,
|
|
1657
|
+
typeParams: {}
|
|
1658
|
+
};
|
|
1659
|
+
}
|
|
1660
|
+
return {
|
|
1661
|
+
storageTypes,
|
|
1662
|
+
namedTypeDescriptors
|
|
1663
|
+
};
|
|
1664
|
+
}
|
|
1665
|
+
function buildModelNodeFromPsl(input) {
|
|
1666
|
+
const { model, mapping, sourceId, diagnostics } = input;
|
|
1667
|
+
const tableName = mapping.tableName;
|
|
1668
|
+
const resolvedFields = collectResolvedFields({
|
|
1669
|
+
model,
|
|
1670
|
+
mapping,
|
|
1671
|
+
enumTypeDescriptors: input.enumTypeDescriptors,
|
|
1672
|
+
namedTypeDescriptors: input.namedTypeDescriptors,
|
|
1673
|
+
modelNames: input.modelNames,
|
|
1674
|
+
compositeTypeNames: input.compositeTypeNames,
|
|
1675
|
+
composedExtensions: input.composedExtensions,
|
|
1676
|
+
authoringContributions: input.authoringContributions,
|
|
1677
|
+
familyId: input.familyId,
|
|
1678
|
+
targetId: input.targetId,
|
|
1679
|
+
defaultFunctionRegistry: input.defaultFunctionRegistry,
|
|
1680
|
+
generatorDescriptorById: input.generatorDescriptorById,
|
|
1681
|
+
diagnostics,
|
|
1682
|
+
sourceId,
|
|
1683
|
+
scalarTypeDescriptors: input.scalarTypeDescriptors
|
|
1684
|
+
});
|
|
1685
|
+
const primaryKeyFields = resolvedFields.filter((field) => field.isId);
|
|
1686
|
+
const primaryKeyColumns = primaryKeyFields.map((field) => field.columnName);
|
|
1687
|
+
const primaryKeyName = primaryKeyFields.length === 1 ? primaryKeyFields[0]?.idName : void 0;
|
|
1688
|
+
const isVariantModel = model.attributes.some((attr) => attr.name === "base");
|
|
1689
|
+
if (primaryKeyColumns.length === 0 && !isVariantModel) diagnostics.push({
|
|
1690
|
+
code: "PSL_MISSING_PRIMARY_KEY",
|
|
1691
|
+
message: `Model "${model.name}" must declare at least one @id field for SQL provider`,
|
|
1692
|
+
sourceId,
|
|
1693
|
+
span: model.span
|
|
1694
|
+
});
|
|
1695
|
+
const resultBackrelationCandidates = [];
|
|
1696
|
+
for (const field of model.fields) {
|
|
1697
|
+
if (!field.list || !input.modelNames.has(field.typeName)) continue;
|
|
1698
|
+
const attributesValid = validateNavigationListFieldAttributes({
|
|
1699
|
+
modelName: model.name,
|
|
1700
|
+
field,
|
|
1701
|
+
sourceId,
|
|
1702
|
+
composedExtensions: input.composedExtensions,
|
|
1703
|
+
diagnostics,
|
|
1704
|
+
familyId: input.familyId,
|
|
1705
|
+
targetId: input.targetId
|
|
1706
|
+
});
|
|
1707
|
+
const relationAttribute = getAttribute(field.attributes, "relation");
|
|
1708
|
+
let relationName;
|
|
1709
|
+
if (relationAttribute) {
|
|
1710
|
+
const parsedRelation = parseRelationAttribute({
|
|
1711
|
+
attribute: relationAttribute,
|
|
1712
|
+
modelName: model.name,
|
|
1713
|
+
fieldName: field.name,
|
|
1714
|
+
sourceId,
|
|
1715
|
+
diagnostics
|
|
1716
|
+
});
|
|
1717
|
+
if (!parsedRelation) continue;
|
|
1718
|
+
if (parsedRelation.fields || parsedRelation.references) {
|
|
1719
|
+
diagnostics.push({
|
|
1720
|
+
code: "PSL_INVALID_RELATION_ATTRIBUTE",
|
|
1721
|
+
message: `Backrelation list field "${model.name}.${field.name}" cannot declare fields/references; define them on the FK-side relation field`,
|
|
1722
|
+
sourceId,
|
|
1723
|
+
span: relationAttribute.span
|
|
1724
|
+
});
|
|
1725
|
+
continue;
|
|
1726
|
+
}
|
|
1727
|
+
if (parsedRelation.onDelete || parsedRelation.onUpdate) {
|
|
1728
|
+
diagnostics.push({
|
|
1729
|
+
code: "PSL_INVALID_RELATION_ATTRIBUTE",
|
|
1730
|
+
message: `Backrelation list field "${model.name}.${field.name}" cannot declare onDelete/onUpdate; define referential actions on the FK-side relation field`,
|
|
1731
|
+
sourceId,
|
|
1732
|
+
span: relationAttribute.span
|
|
1733
|
+
});
|
|
1734
|
+
continue;
|
|
1735
|
+
}
|
|
1736
|
+
relationName = parsedRelation.relationName;
|
|
1737
|
+
}
|
|
1738
|
+
if (!attributesValid) continue;
|
|
1739
|
+
resultBackrelationCandidates.push({
|
|
1740
|
+
modelName: model.name,
|
|
1741
|
+
tableName,
|
|
1742
|
+
field,
|
|
1743
|
+
targetModelName: field.typeName,
|
|
1744
|
+
...ifDefined("relationName", relationName)
|
|
1745
|
+
});
|
|
1746
|
+
}
|
|
1747
|
+
const relationAttributes = model.fields.map((field) => ({
|
|
1748
|
+
field,
|
|
1749
|
+
relation: getAttribute(field.attributes, "relation")
|
|
1750
|
+
})).filter((entry) => Boolean(entry.relation));
|
|
1751
|
+
const uniqueConstraints = resolvedFields.filter((field) => field.isUnique).map((field) => ({
|
|
1752
|
+
columns: [field.columnName],
|
|
1753
|
+
...ifDefined("name", field.uniqueName)
|
|
1754
|
+
}));
|
|
1755
|
+
const indexNodes = [];
|
|
1756
|
+
const foreignKeyNodes = [];
|
|
1757
|
+
for (const modelAttribute of model.attributes) {
|
|
1758
|
+
if (modelAttribute.name === "map") continue;
|
|
1759
|
+
if (modelAttribute.name === "discriminator" || modelAttribute.name === "base") continue;
|
|
1760
|
+
if (modelAttribute.name === "unique" || modelAttribute.name === "index") {
|
|
1761
|
+
const fieldNames = parseAttributeFieldList({
|
|
1762
|
+
attribute: modelAttribute,
|
|
1763
|
+
sourceId,
|
|
1764
|
+
diagnostics,
|
|
1765
|
+
code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
|
|
1766
|
+
messagePrefix: `Model "${model.name}" @@${modelAttribute.name}`
|
|
1767
|
+
});
|
|
1768
|
+
if (!fieldNames) continue;
|
|
1769
|
+
const columnNames = mapFieldNamesToColumns({
|
|
1770
|
+
modelName: model.name,
|
|
1771
|
+
fieldNames,
|
|
1772
|
+
mapping,
|
|
1773
|
+
sourceId,
|
|
1774
|
+
diagnostics,
|
|
1775
|
+
span: modelAttribute.span,
|
|
1776
|
+
contextLabel: `Model "${model.name}" @@${modelAttribute.name}`
|
|
1777
|
+
});
|
|
1778
|
+
if (!columnNames) continue;
|
|
1779
|
+
const constraintName = parseConstraintMapArgument({
|
|
1780
|
+
attribute: modelAttribute,
|
|
1781
|
+
sourceId,
|
|
1782
|
+
diagnostics,
|
|
1783
|
+
entityLabel: `Model "${model.name}" @@${modelAttribute.name}`,
|
|
1784
|
+
span: modelAttribute.span,
|
|
1785
|
+
code: "PSL_INVALID_ATTRIBUTE_ARGUMENT"
|
|
1786
|
+
});
|
|
1787
|
+
if (modelAttribute.name === "unique") uniqueConstraints.push({
|
|
1788
|
+
columns: columnNames,
|
|
1789
|
+
...ifDefined("name", constraintName)
|
|
1790
|
+
});
|
|
1791
|
+
else indexNodes.push({
|
|
1792
|
+
columns: columnNames,
|
|
1793
|
+
...ifDefined("name", constraintName)
|
|
1794
|
+
});
|
|
1795
|
+
continue;
|
|
1796
|
+
}
|
|
1797
|
+
const uncomposedNamespace = checkUncomposedNamespace(modelAttribute.name, input.composedExtensions, {
|
|
1798
|
+
familyId: input.familyId,
|
|
1799
|
+
targetId: input.targetId
|
|
1800
|
+
});
|
|
1801
|
+
if (uncomposedNamespace) {
|
|
1802
|
+
reportUncomposedNamespace({
|
|
1803
|
+
subjectLabel: `Attribute "@@${modelAttribute.name}"`,
|
|
1804
|
+
namespace: uncomposedNamespace,
|
|
1805
|
+
sourceId,
|
|
1806
|
+
span: modelAttribute.span,
|
|
1807
|
+
diagnostics
|
|
1808
|
+
});
|
|
1809
|
+
continue;
|
|
1810
|
+
}
|
|
1811
|
+
diagnostics.push({
|
|
1812
|
+
code: "PSL_UNSUPPORTED_MODEL_ATTRIBUTE",
|
|
1813
|
+
message: `Model "${model.name}" uses unsupported attribute "@@${modelAttribute.name}"`,
|
|
1814
|
+
sourceId,
|
|
1815
|
+
span: modelAttribute.span
|
|
1816
|
+
});
|
|
1817
|
+
}
|
|
1818
|
+
const resultFkRelationMetadata = [];
|
|
1819
|
+
for (const relationAttribute of relationAttributes) {
|
|
1820
|
+
if (relationAttribute.field.list) continue;
|
|
1821
|
+
if (!input.modelNames.has(relationAttribute.field.typeName)) {
|
|
1822
|
+
diagnostics.push({
|
|
1823
|
+
code: "PSL_INVALID_RELATION_TARGET",
|
|
1824
|
+
message: `Relation field "${model.name}.${relationAttribute.field.name}" references unknown model "${relationAttribute.field.typeName}"`,
|
|
1825
|
+
sourceId,
|
|
1826
|
+
span: relationAttribute.field.span
|
|
1827
|
+
});
|
|
1828
|
+
continue;
|
|
1829
|
+
}
|
|
1830
|
+
const parsedRelation = parseRelationAttribute({
|
|
1831
|
+
attribute: relationAttribute.relation,
|
|
1832
|
+
modelName: model.name,
|
|
1833
|
+
fieldName: relationAttribute.field.name,
|
|
1834
|
+
sourceId,
|
|
1835
|
+
diagnostics
|
|
1836
|
+
});
|
|
1837
|
+
if (!parsedRelation) continue;
|
|
1838
|
+
if (!parsedRelation.fields || !parsedRelation.references) {
|
|
1839
|
+
diagnostics.push({
|
|
1840
|
+
code: "PSL_INVALID_RELATION_ATTRIBUTE",
|
|
1841
|
+
message: `Relation field "${model.name}.${relationAttribute.field.name}" requires fields and references arguments`,
|
|
1842
|
+
sourceId,
|
|
1843
|
+
span: relationAttribute.relation.span
|
|
1844
|
+
});
|
|
1845
|
+
continue;
|
|
1846
|
+
}
|
|
1847
|
+
const targetMapping = input.modelMappings.get(relationAttribute.field.typeName);
|
|
1848
|
+
if (!targetMapping) {
|
|
1849
|
+
diagnostics.push({
|
|
1850
|
+
code: "PSL_INVALID_RELATION_TARGET",
|
|
1851
|
+
message: `Relation field "${model.name}.${relationAttribute.field.name}" references unknown model "${relationAttribute.field.typeName}"`,
|
|
1852
|
+
sourceId,
|
|
1853
|
+
span: relationAttribute.field.span
|
|
1854
|
+
});
|
|
1855
|
+
continue;
|
|
1856
|
+
}
|
|
1857
|
+
const localColumns = mapFieldNamesToColumns({
|
|
1858
|
+
modelName: model.name,
|
|
1859
|
+
fieldNames: parsedRelation.fields,
|
|
1860
|
+
mapping,
|
|
1861
|
+
sourceId,
|
|
1862
|
+
diagnostics,
|
|
1863
|
+
span: relationAttribute.relation.span,
|
|
1864
|
+
contextLabel: `Relation field "${model.name}.${relationAttribute.field.name}"`
|
|
1865
|
+
});
|
|
1866
|
+
if (!localColumns) continue;
|
|
1867
|
+
const referencedColumns = mapFieldNamesToColumns({
|
|
1868
|
+
modelName: targetMapping.model.name,
|
|
1869
|
+
fieldNames: parsedRelation.references,
|
|
1870
|
+
mapping: targetMapping,
|
|
1871
|
+
sourceId,
|
|
1872
|
+
diagnostics,
|
|
1873
|
+
span: relationAttribute.relation.span,
|
|
1874
|
+
contextLabel: `Relation field "${model.name}.${relationAttribute.field.name}"`
|
|
1875
|
+
});
|
|
1876
|
+
if (!referencedColumns) continue;
|
|
1877
|
+
if (localColumns.length !== referencedColumns.length) {
|
|
1878
|
+
diagnostics.push({
|
|
1879
|
+
code: "PSL_INVALID_RELATION_ATTRIBUTE",
|
|
1880
|
+
message: `Relation field "${model.name}.${relationAttribute.field.name}" must provide the same number of fields and references`,
|
|
1881
|
+
sourceId,
|
|
1882
|
+
span: relationAttribute.relation.span
|
|
1883
|
+
});
|
|
1884
|
+
continue;
|
|
1885
|
+
}
|
|
1886
|
+
const onDelete = parsedRelation.onDelete ? normalizeReferentialAction({
|
|
1887
|
+
modelName: model.name,
|
|
1888
|
+
fieldName: relationAttribute.field.name,
|
|
1889
|
+
actionName: "onDelete",
|
|
1890
|
+
actionToken: parsedRelation.onDelete,
|
|
1891
|
+
sourceId,
|
|
1892
|
+
span: relationAttribute.field.span,
|
|
1893
|
+
diagnostics
|
|
1894
|
+
}) : void 0;
|
|
1895
|
+
const onUpdate = parsedRelation.onUpdate ? normalizeReferentialAction({
|
|
1896
|
+
modelName: model.name,
|
|
1897
|
+
fieldName: relationAttribute.field.name,
|
|
1898
|
+
actionName: "onUpdate",
|
|
1899
|
+
actionToken: parsedRelation.onUpdate,
|
|
1900
|
+
sourceId,
|
|
1901
|
+
span: relationAttribute.field.span,
|
|
1902
|
+
diagnostics
|
|
1903
|
+
}) : void 0;
|
|
1904
|
+
foreignKeyNodes.push({
|
|
1905
|
+
columns: localColumns,
|
|
1906
|
+
references: {
|
|
1907
|
+
model: targetMapping.model.name,
|
|
1908
|
+
table: targetMapping.tableName,
|
|
1909
|
+
columns: referencedColumns
|
|
1910
|
+
},
|
|
1911
|
+
...ifDefined("name", parsedRelation.constraintName),
|
|
1912
|
+
...ifDefined("onDelete", onDelete),
|
|
1913
|
+
...ifDefined("onUpdate", onUpdate)
|
|
1914
|
+
});
|
|
1915
|
+
resultFkRelationMetadata.push({
|
|
1916
|
+
declaringModelName: model.name,
|
|
1917
|
+
declaringFieldName: relationAttribute.field.name,
|
|
1918
|
+
declaringTableName: tableName,
|
|
1919
|
+
targetModelName: targetMapping.model.name,
|
|
1920
|
+
targetTableName: targetMapping.tableName,
|
|
1921
|
+
...ifDefined("relationName", parsedRelation.relationName),
|
|
1922
|
+
localColumns,
|
|
1923
|
+
referencedColumns
|
|
1924
|
+
});
|
|
1925
|
+
}
|
|
1926
|
+
return {
|
|
1927
|
+
modelNode: {
|
|
1928
|
+
modelName: model.name,
|
|
1929
|
+
tableName,
|
|
1930
|
+
fields: resolvedFields.map((resolvedField) => ({
|
|
1931
|
+
fieldName: resolvedField.field.name,
|
|
1932
|
+
columnName: resolvedField.columnName,
|
|
1933
|
+
descriptor: resolvedField.descriptor,
|
|
1934
|
+
nullable: resolvedField.field.optional,
|
|
1935
|
+
...ifDefined("default", resolvedField.defaultValue),
|
|
1936
|
+
...ifDefined("executionDefault", resolvedField.executionDefault)
|
|
1937
|
+
})),
|
|
1938
|
+
...primaryKeyColumns.length > 0 ? { id: {
|
|
1939
|
+
columns: primaryKeyColumns,
|
|
1940
|
+
...ifDefined("name", primaryKeyName)
|
|
1941
|
+
} } : {},
|
|
1942
|
+
...uniqueConstraints.length > 0 ? { uniques: uniqueConstraints } : {},
|
|
1943
|
+
...indexNodes.length > 0 ? { indexes: indexNodes } : {},
|
|
1944
|
+
...foreignKeyNodes.length > 0 ? { foreignKeys: foreignKeyNodes } : {}
|
|
1945
|
+
},
|
|
1946
|
+
fkRelationMetadata: resultFkRelationMetadata,
|
|
1947
|
+
backrelationCandidates: resultBackrelationCandidates,
|
|
1948
|
+
resolvedFields
|
|
1949
|
+
};
|
|
1950
|
+
}
|
|
1951
|
+
function buildValueObjects(input) {
|
|
1952
|
+
const { compositeTypes, enumTypeDescriptors, namedTypeDescriptors, scalarTypeDescriptors, composedExtensions, familyId, targetId, authoringContributions, diagnostics, sourceId } = input;
|
|
1953
|
+
const valueObjects = {};
|
|
1954
|
+
const compositeTypeNames = new Set(compositeTypes.map((ct) => ct.name));
|
|
1955
|
+
for (const compositeType of compositeTypes) {
|
|
1956
|
+
const fields = {};
|
|
1957
|
+
for (const field of compositeType.fields) {
|
|
1958
|
+
if (compositeTypeNames.has(field.typeName)) {
|
|
1959
|
+
const result = {
|
|
1960
|
+
type: {
|
|
1961
|
+
kind: "valueObject",
|
|
1962
|
+
name: field.typeName
|
|
1963
|
+
},
|
|
1964
|
+
nullable: field.optional
|
|
1965
|
+
};
|
|
1966
|
+
fields[field.name] = field.list ? {
|
|
1967
|
+
...result,
|
|
1968
|
+
many: true
|
|
1969
|
+
} : result;
|
|
1970
|
+
continue;
|
|
1971
|
+
}
|
|
1972
|
+
const resolved = resolveFieldTypeDescriptor({
|
|
1973
|
+
field,
|
|
1974
|
+
enumTypeDescriptors,
|
|
1975
|
+
namedTypeDescriptors,
|
|
1976
|
+
scalarTypeDescriptors,
|
|
1977
|
+
authoringContributions,
|
|
1978
|
+
composedExtensions,
|
|
1979
|
+
familyId,
|
|
1980
|
+
targetId,
|
|
1981
|
+
diagnostics,
|
|
1982
|
+
sourceId,
|
|
1983
|
+
entityLabel: `Field "${compositeType.name}.${field.name}"`
|
|
1984
|
+
});
|
|
1985
|
+
if (!resolved.ok) {
|
|
1986
|
+
if (!resolved.alreadyReported) diagnostics.push({
|
|
1987
|
+
code: "PSL_UNSUPPORTED_FIELD_TYPE",
|
|
1988
|
+
message: `Field "${compositeType.name}.${field.name}" type "${field.typeName}" is not supported`,
|
|
1989
|
+
sourceId,
|
|
1990
|
+
span: field.span
|
|
1991
|
+
});
|
|
1992
|
+
continue;
|
|
1993
|
+
}
|
|
1994
|
+
const scalarField = {
|
|
1995
|
+
nullable: field.optional,
|
|
1996
|
+
type: {
|
|
1997
|
+
kind: "scalar",
|
|
1998
|
+
codecId: resolved.descriptor.codecId
|
|
1999
|
+
}
|
|
2000
|
+
};
|
|
2001
|
+
fields[field.name] = field.list ? {
|
|
2002
|
+
...scalarField,
|
|
2003
|
+
many: true
|
|
2004
|
+
} : scalarField;
|
|
2005
|
+
}
|
|
2006
|
+
valueObjects[compositeType.name] = { fields };
|
|
2007
|
+
}
|
|
2008
|
+
return valueObjects;
|
|
2009
|
+
}
|
|
2010
|
+
function patchModelDomainFields(models, modelResolvedFields) {
|
|
2011
|
+
let patched = models;
|
|
2012
|
+
for (const [modelName, resolvedFields] of modelResolvedFields) {
|
|
2013
|
+
const model = patched[modelName];
|
|
2014
|
+
if (!model) continue;
|
|
2015
|
+
let needsPatch = false;
|
|
2016
|
+
const patchedFields = { ...model.fields };
|
|
2017
|
+
for (const rf of resolvedFields) if (rf.valueObjectTypeName) {
|
|
2018
|
+
needsPatch = true;
|
|
2019
|
+
patchedFields[rf.field.name] = {
|
|
2020
|
+
nullable: rf.field.optional,
|
|
2021
|
+
type: {
|
|
2022
|
+
kind: "valueObject",
|
|
2023
|
+
name: rf.valueObjectTypeName
|
|
2024
|
+
},
|
|
2025
|
+
...rf.many ? { many: true } : {}
|
|
2026
|
+
};
|
|
2027
|
+
} else if (rf.many && rf.scalarCodecId) {
|
|
2028
|
+
needsPatch = true;
|
|
2029
|
+
patchedFields[rf.field.name] = {
|
|
2030
|
+
nullable: rf.field.optional,
|
|
2031
|
+
type: {
|
|
2032
|
+
kind: "scalar",
|
|
2033
|
+
codecId: rf.scalarCodecId
|
|
2034
|
+
},
|
|
2035
|
+
many: true
|
|
2036
|
+
};
|
|
2037
|
+
}
|
|
2038
|
+
if (needsPatch) patched = {
|
|
2039
|
+
...patched,
|
|
2040
|
+
[modelName]: {
|
|
2041
|
+
...model,
|
|
2042
|
+
fields: patchedFields
|
|
2043
|
+
}
|
|
2044
|
+
};
|
|
2045
|
+
}
|
|
2046
|
+
return patched;
|
|
2047
|
+
}
|
|
2048
|
+
function collectPolymorphismDeclarations(models, sourceId, diagnostics) {
|
|
2049
|
+
const discriminatorDeclarations = /* @__PURE__ */ new Map();
|
|
2050
|
+
const baseDeclarations = /* @__PURE__ */ new Map();
|
|
2051
|
+
for (const model of models) for (const attr of model.attributes) {
|
|
2052
|
+
if (attr.name === "discriminator") {
|
|
2053
|
+
const fieldName = getPositionalArgument(attr);
|
|
2054
|
+
if (!fieldName) {
|
|
2055
|
+
diagnostics.push({
|
|
2056
|
+
code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
|
|
2057
|
+
message: `Model "${model.name}" @@discriminator requires a field name argument`,
|
|
2058
|
+
sourceId,
|
|
2059
|
+
span: attr.span
|
|
2060
|
+
});
|
|
2061
|
+
continue;
|
|
2062
|
+
}
|
|
2063
|
+
const discField = model.fields.find((f) => f.name === fieldName);
|
|
2064
|
+
if (discField && discField.typeName !== "String") {
|
|
2065
|
+
diagnostics.push({
|
|
2066
|
+
code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
|
|
2067
|
+
message: `Discriminator field "${fieldName}" on model "${model.name}" must be of type String, but is "${discField.typeName}"`,
|
|
2068
|
+
sourceId,
|
|
2069
|
+
span: attr.span
|
|
2070
|
+
});
|
|
2071
|
+
continue;
|
|
2072
|
+
}
|
|
2073
|
+
discriminatorDeclarations.set(model.name, {
|
|
2074
|
+
fieldName,
|
|
2075
|
+
span: attr.span
|
|
2076
|
+
});
|
|
2077
|
+
}
|
|
2078
|
+
if (attr.name === "base") {
|
|
2079
|
+
const baseName = getPositionalArgument(attr, 0);
|
|
2080
|
+
const rawValue = getPositionalArgument(attr, 1);
|
|
2081
|
+
if (!baseName || !rawValue) {
|
|
2082
|
+
diagnostics.push({
|
|
2083
|
+
code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
|
|
2084
|
+
message: `Model "${model.name}" @@base requires two arguments: base model name and discriminator value`,
|
|
2085
|
+
sourceId,
|
|
2086
|
+
span: attr.span
|
|
2087
|
+
});
|
|
2088
|
+
continue;
|
|
2089
|
+
}
|
|
2090
|
+
const value = parseQuotedStringLiteral(rawValue);
|
|
2091
|
+
if (value === void 0) {
|
|
2092
|
+
diagnostics.push({
|
|
2093
|
+
code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
|
|
2094
|
+
message: `Model "${model.name}" @@base discriminator value must be a quoted string literal`,
|
|
2095
|
+
sourceId,
|
|
2096
|
+
span: attr.span
|
|
2097
|
+
});
|
|
2098
|
+
continue;
|
|
2099
|
+
}
|
|
2100
|
+
baseDeclarations.set(model.name, {
|
|
2101
|
+
baseName,
|
|
2102
|
+
value,
|
|
2103
|
+
span: attr.span
|
|
2104
|
+
});
|
|
2105
|
+
}
|
|
2106
|
+
}
|
|
2107
|
+
return {
|
|
2108
|
+
discriminatorDeclarations,
|
|
2109
|
+
baseDeclarations
|
|
2110
|
+
};
|
|
2111
|
+
}
|
|
2112
|
+
function resolvePolymorphism(models, discriminatorDeclarations, baseDeclarations, modelNames, modelMappings, sourceId, diagnostics) {
|
|
2113
|
+
let patched = models;
|
|
2114
|
+
for (const [modelName, decl] of discriminatorDeclarations) {
|
|
2115
|
+
if (baseDeclarations.has(modelName)) {
|
|
2116
|
+
diagnostics.push({
|
|
2117
|
+
code: "PSL_DISCRIMINATOR_AND_BASE",
|
|
2118
|
+
message: `Model "${modelName}" cannot have both @@discriminator and @@base`,
|
|
2119
|
+
sourceId,
|
|
2120
|
+
span: decl.span
|
|
2121
|
+
});
|
|
2122
|
+
continue;
|
|
2123
|
+
}
|
|
2124
|
+
const model = patched[modelName];
|
|
2125
|
+
if (!model) continue;
|
|
2126
|
+
if (!Object.hasOwn(model.fields, decl.fieldName)) {
|
|
2127
|
+
diagnostics.push({
|
|
2128
|
+
code: "PSL_DISCRIMINATOR_FIELD_NOT_FOUND",
|
|
2129
|
+
message: `Discriminator field "${decl.fieldName}" is not a field on model "${modelName}"`,
|
|
2130
|
+
sourceId,
|
|
2131
|
+
span: decl.span
|
|
2132
|
+
});
|
|
2133
|
+
continue;
|
|
2134
|
+
}
|
|
2135
|
+
const variants = {};
|
|
2136
|
+
const seenValues = /* @__PURE__ */ new Map();
|
|
2137
|
+
for (const [variantName, baseDecl] of baseDeclarations) {
|
|
2138
|
+
if (baseDecl.baseName !== modelName) continue;
|
|
2139
|
+
const existingVariant = seenValues.get(baseDecl.value);
|
|
2140
|
+
if (existingVariant) {
|
|
2141
|
+
diagnostics.push({
|
|
2142
|
+
code: "PSL_DUPLICATE_DISCRIMINATOR_VALUE",
|
|
2143
|
+
message: `Discriminator value "${baseDecl.value}" is used by both "${existingVariant}" and "${variantName}" on base model "${modelName}"`,
|
|
2144
|
+
sourceId,
|
|
2145
|
+
span: baseDecl.span
|
|
2146
|
+
});
|
|
2147
|
+
continue;
|
|
2148
|
+
}
|
|
2149
|
+
seenValues.set(baseDecl.value, variantName);
|
|
2150
|
+
variants[variantName] = { value: baseDecl.value };
|
|
2151
|
+
}
|
|
2152
|
+
if (Object.keys(variants).length === 0) {
|
|
2153
|
+
diagnostics.push({
|
|
2154
|
+
code: "PSL_ORPHANED_DISCRIMINATOR",
|
|
2155
|
+
message: `Model "${modelName}" has @@discriminator but no variant models declare @@base(${modelName}, ...)`,
|
|
2156
|
+
sourceId,
|
|
2157
|
+
span: decl.span
|
|
2158
|
+
});
|
|
2159
|
+
continue;
|
|
2160
|
+
}
|
|
2161
|
+
patched = {
|
|
2162
|
+
...patched,
|
|
2163
|
+
[modelName]: {
|
|
2164
|
+
...model,
|
|
2165
|
+
discriminator: { field: decl.fieldName },
|
|
2166
|
+
variants
|
|
2167
|
+
}
|
|
2168
|
+
};
|
|
2169
|
+
}
|
|
2170
|
+
for (const [variantName, baseDecl] of baseDeclarations) {
|
|
2171
|
+
if (!modelNames.has(baseDecl.baseName)) {
|
|
2172
|
+
diagnostics.push({
|
|
2173
|
+
code: "PSL_BASE_TARGET_NOT_FOUND",
|
|
2174
|
+
message: `Model "${variantName}" @@base references non-existent model "${baseDecl.baseName}"`,
|
|
2175
|
+
sourceId,
|
|
2176
|
+
span: baseDecl.span
|
|
2177
|
+
});
|
|
2178
|
+
continue;
|
|
2179
|
+
}
|
|
2180
|
+
if (!discriminatorDeclarations.has(baseDecl.baseName)) {
|
|
2181
|
+
diagnostics.push({
|
|
2182
|
+
code: "PSL_ORPHANED_BASE",
|
|
2183
|
+
message: `Model "${variantName}" declares @@base(${baseDecl.baseName}, ...) but "${baseDecl.baseName}" has no @@discriminator`,
|
|
2184
|
+
sourceId,
|
|
2185
|
+
span: baseDecl.span
|
|
2186
|
+
});
|
|
2187
|
+
continue;
|
|
2188
|
+
}
|
|
2189
|
+
if (discriminatorDeclarations.has(variantName)) continue;
|
|
2190
|
+
const variantModel = patched[variantName];
|
|
2191
|
+
if (!variantModel) continue;
|
|
2192
|
+
const baseMapping = modelMappings.get(baseDecl.baseName);
|
|
2193
|
+
const variantMapping = modelMappings.get(variantName);
|
|
2194
|
+
const resolvedTable = variantMapping?.model.attributes.some((attr) => attr.name === "map") ?? false ? variantMapping?.tableName : baseMapping?.tableName;
|
|
2195
|
+
patched = {
|
|
2196
|
+
...patched,
|
|
2197
|
+
[variantName]: {
|
|
2198
|
+
...variantModel,
|
|
2199
|
+
base: baseDecl.baseName,
|
|
2200
|
+
...resolvedTable ? { storage: {
|
|
2201
|
+
...variantModel.storage,
|
|
2202
|
+
table: resolvedTable
|
|
2203
|
+
} } : {}
|
|
2204
|
+
}
|
|
2205
|
+
};
|
|
2206
|
+
}
|
|
2207
|
+
return patched;
|
|
2208
|
+
}
|
|
2209
|
+
function interpretPslDocumentToSqlContract(input) {
|
|
2210
|
+
const sourceId = input.document.ast.sourceId;
|
|
2211
|
+
if (!input.target) return notOk({
|
|
2212
|
+
summary: "PSL to SQL contract interpretation failed",
|
|
2213
|
+
diagnostics: [{
|
|
2214
|
+
code: "PSL_TARGET_CONTEXT_REQUIRED",
|
|
2215
|
+
message: "PSL interpretation requires an explicit target context from composition.",
|
|
2216
|
+
sourceId
|
|
2217
|
+
}]
|
|
2218
|
+
});
|
|
2219
|
+
if (!input.scalarTypeDescriptors) return notOk({
|
|
2220
|
+
summary: "PSL to SQL contract interpretation failed",
|
|
2221
|
+
diagnostics: [{
|
|
2222
|
+
code: "PSL_SCALAR_TYPE_CONTEXT_REQUIRED",
|
|
2223
|
+
message: "PSL interpretation requires composed scalar type descriptors.",
|
|
2224
|
+
sourceId
|
|
2225
|
+
}]
|
|
2226
|
+
});
|
|
2227
|
+
const diagnostics = mapParserDiagnostics(input.document);
|
|
2228
|
+
const models = input.document.ast.models ?? [];
|
|
2229
|
+
const enums = input.document.ast.enums ?? [];
|
|
2230
|
+
const compositeTypes = input.document.ast.compositeTypes ?? [];
|
|
2231
|
+
const modelNames = new Set(models.map((model) => model.name));
|
|
2232
|
+
const compositeTypeNames = new Set(compositeTypes.map((ct) => ct.name));
|
|
2233
|
+
const composedExtensions = new Set(input.composedExtensionPacks ?? []);
|
|
2234
|
+
const defaultFunctionRegistry = input.controlMutationDefaults?.defaultFunctionRegistry ?? /* @__PURE__ */ new Map();
|
|
2235
|
+
const generatorDescriptors = input.controlMutationDefaults?.generatorDescriptors ?? [];
|
|
2236
|
+
const generatorDescriptorById = /* @__PURE__ */ new Map();
|
|
2237
|
+
for (const descriptor of generatorDescriptors) generatorDescriptorById.set(descriptor.id, descriptor);
|
|
2238
|
+
const enumResult = processEnumDeclarations({
|
|
2239
|
+
enums,
|
|
2240
|
+
sourceId,
|
|
2241
|
+
enumTypeConstructor: getAuthoringTypeConstructor(input.authoringContributions, ["enum"]),
|
|
2242
|
+
diagnostics
|
|
2243
|
+
});
|
|
2244
|
+
const namedTypeResult = resolveNamedTypeDeclarations({
|
|
2245
|
+
declarations: input.document.ast.types?.declarations ?? [],
|
|
2246
|
+
sourceId,
|
|
2247
|
+
enumTypeDescriptors: enumResult.enumTypeDescriptors,
|
|
2248
|
+
scalarTypeDescriptors: input.scalarTypeDescriptors,
|
|
2249
|
+
composedExtensions,
|
|
2250
|
+
familyId: input.target.familyId,
|
|
2251
|
+
targetId: input.target.targetId,
|
|
2252
|
+
authoringContributions: input.authoringContributions,
|
|
2253
|
+
diagnostics
|
|
2254
|
+
});
|
|
2255
|
+
const storageTypes = {
|
|
2256
|
+
...enumResult.storageTypes,
|
|
2257
|
+
...namedTypeResult.storageTypes
|
|
2258
|
+
};
|
|
2259
|
+
const modelMappings = buildModelMappings(models, diagnostics, sourceId);
|
|
2260
|
+
const modelNodes = [];
|
|
2261
|
+
const fkRelationMetadata = [];
|
|
2262
|
+
const backrelationCandidates = [];
|
|
2263
|
+
const modelResolvedFields = /* @__PURE__ */ new Map();
|
|
2264
|
+
for (const model of models) {
|
|
2265
|
+
const mapping = modelMappings.get(model.name);
|
|
2266
|
+
if (!mapping) continue;
|
|
2267
|
+
const result = buildModelNodeFromPsl({
|
|
2268
|
+
model,
|
|
2269
|
+
mapping,
|
|
2270
|
+
modelMappings,
|
|
2271
|
+
modelNames,
|
|
2272
|
+
compositeTypeNames,
|
|
2273
|
+
enumTypeDescriptors: enumResult.enumTypeDescriptors,
|
|
2274
|
+
namedTypeDescriptors: namedTypeResult.namedTypeDescriptors,
|
|
2275
|
+
composedExtensions,
|
|
2276
|
+
familyId: input.target.familyId,
|
|
2277
|
+
targetId: input.target.targetId,
|
|
2278
|
+
authoringContributions: input.authoringContributions,
|
|
2279
|
+
defaultFunctionRegistry,
|
|
2280
|
+
generatorDescriptorById,
|
|
2281
|
+
scalarTypeDescriptors: input.scalarTypeDescriptors,
|
|
2282
|
+
sourceId,
|
|
2283
|
+
diagnostics
|
|
2284
|
+
});
|
|
2285
|
+
modelNodes.push(result.modelNode);
|
|
2286
|
+
fkRelationMetadata.push(...result.fkRelationMetadata);
|
|
2287
|
+
backrelationCandidates.push(...result.backrelationCandidates);
|
|
2288
|
+
modelResolvedFields.set(model.name, result.resolvedFields);
|
|
2289
|
+
}
|
|
2290
|
+
const { modelRelations, fkRelationsByPair } = indexFkRelations({ fkRelationMetadata });
|
|
2291
|
+
applyBackrelationCandidates({
|
|
2292
|
+
backrelationCandidates,
|
|
2293
|
+
fkRelationsByPair,
|
|
2294
|
+
modelRelations,
|
|
2295
|
+
diagnostics,
|
|
2296
|
+
sourceId
|
|
2297
|
+
});
|
|
2298
|
+
const { discriminatorDeclarations, baseDeclarations } = collectPolymorphismDeclarations(models, sourceId, diagnostics);
|
|
2299
|
+
const valueObjects = buildValueObjects({
|
|
2300
|
+
compositeTypes,
|
|
2301
|
+
enumTypeDescriptors: enumResult.enumTypeDescriptors,
|
|
2302
|
+
namedTypeDescriptors: namedTypeResult.namedTypeDescriptors,
|
|
2303
|
+
scalarTypeDescriptors: input.scalarTypeDescriptors,
|
|
2304
|
+
composedExtensions,
|
|
2305
|
+
familyId: input.target.familyId,
|
|
2306
|
+
targetId: input.target.targetId,
|
|
2307
|
+
authoringContributions: input.authoringContributions,
|
|
2308
|
+
diagnostics,
|
|
2309
|
+
sourceId
|
|
2310
|
+
});
|
|
2311
|
+
if (diagnostics.length > 0) return notOk({
|
|
2312
|
+
summary: "PSL to SQL contract interpretation failed",
|
|
2313
|
+
diagnostics: dedupeDiagnostics(diagnostics)
|
|
2314
|
+
});
|
|
2315
|
+
const contract = buildSqlContractFromDefinition({
|
|
2316
|
+
target: input.target,
|
|
2317
|
+
...ifDefined("extensionPacks", buildComposedExtensionPackRefs(input.target, [...composedExtensions].sort(compareStrings), input.composedExtensionPackRefs)),
|
|
2318
|
+
...Object.keys(storageTypes).length > 0 ? { storageTypes } : {},
|
|
2319
|
+
models: modelNodes.map((model) => ({
|
|
2320
|
+
...model,
|
|
2321
|
+
...modelRelations.has(model.modelName) ? { relations: [...modelRelations.get(model.modelName) ?? []].sort((left, right) => compareStrings(left.fieldName, right.fieldName)) } : {}
|
|
2322
|
+
}))
|
|
2323
|
+
});
|
|
2324
|
+
let patchedModels = patchModelDomainFields(contract.models, modelResolvedFields);
|
|
2325
|
+
const polyDiagnostics = [];
|
|
2326
|
+
patchedModels = resolvePolymorphism(patchedModels, discriminatorDeclarations, baseDeclarations, modelNames, modelMappings, sourceId, polyDiagnostics);
|
|
2327
|
+
if (polyDiagnostics.length > 0) return notOk({
|
|
2328
|
+
summary: "PSL to SQL contract interpretation failed",
|
|
2329
|
+
diagnostics: polyDiagnostics
|
|
2330
|
+
});
|
|
2331
|
+
const variantModelNames = new Set(baseDeclarations.keys());
|
|
2332
|
+
const filteredRoots = Object.fromEntries(Object.entries(contract.roots).filter(([, modelName]) => !variantModelNames.has(modelName)));
|
|
2333
|
+
return ok({
|
|
2334
|
+
...contract,
|
|
2335
|
+
roots: filteredRoots,
|
|
2336
|
+
models: patchedModels,
|
|
2337
|
+
...Object.keys(valueObjects).length > 0 ? { valueObjects } : {}
|
|
2338
|
+
});
|
|
2339
|
+
}
|
|
2340
|
+
|
|
2341
|
+
//#endregion
|
|
2342
|
+
export { interpretPslDocumentToSqlContract as t };
|
|
2343
|
+
//# sourceMappingURL=interpreter-iFCRN9nb.mjs.map
|