@prisma-next/sql-contract-psl 0.3.0-dev.71 → 0.4.0-dev.1
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 +9 -6
- 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,600 @@
|
|
|
1
|
+
import type { ContractSourceDiagnostic } from '@prisma-next/config/config-types';
|
|
2
|
+
import type { ColumnDefault, ExecutionMutationDefaultValue } from '@prisma-next/contract/types';
|
|
3
|
+
import type {
|
|
4
|
+
AuthoringContributions,
|
|
5
|
+
AuthoringTypeConstructorDescriptor,
|
|
6
|
+
} from '@prisma-next/framework-components/authoring';
|
|
7
|
+
import {
|
|
8
|
+
instantiateAuthoringTypeConstructor,
|
|
9
|
+
isAuthoringTypeConstructorDescriptor,
|
|
10
|
+
validateAuthoringHelperArguments,
|
|
11
|
+
} from '@prisma-next/framework-components/authoring';
|
|
12
|
+
import type {
|
|
13
|
+
PslAttribute,
|
|
14
|
+
PslField,
|
|
15
|
+
PslSpan,
|
|
16
|
+
PslTypeConstructorCall,
|
|
17
|
+
} from '@prisma-next/psl-parser';
|
|
18
|
+
import type {
|
|
19
|
+
ControlMutationDefaultRegistry,
|
|
20
|
+
MutationDefaultGeneratorDescriptor,
|
|
21
|
+
} from './default-function-registry';
|
|
22
|
+
import {
|
|
23
|
+
lowerDefaultFunctionWithRegistry,
|
|
24
|
+
parseDefaultFunctionCall,
|
|
25
|
+
} from './default-function-registry';
|
|
26
|
+
import {
|
|
27
|
+
getPositionalArgumentEntry,
|
|
28
|
+
getPositionalArguments,
|
|
29
|
+
parseOptionalNumericArguments,
|
|
30
|
+
parseOptionalSingleIntegerArgument,
|
|
31
|
+
pushInvalidAttributeArgument,
|
|
32
|
+
unquoteStringLiteral,
|
|
33
|
+
} from './psl-attribute-parsing';
|
|
34
|
+
import { mapPslHelperArgs } from './psl-authoring-arguments';
|
|
35
|
+
|
|
36
|
+
export type ColumnDescriptor = {
|
|
37
|
+
readonly codecId: string;
|
|
38
|
+
readonly nativeType: string;
|
|
39
|
+
readonly typeRef?: string;
|
|
40
|
+
readonly typeParams?: Record<string, unknown>;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export function toNamedTypeFieldDescriptor(
|
|
44
|
+
typeRef: string,
|
|
45
|
+
descriptor: Pick<ColumnDescriptor, 'codecId' | 'nativeType'>,
|
|
46
|
+
): ColumnDescriptor {
|
|
47
|
+
return {
|
|
48
|
+
codecId: descriptor.codecId,
|
|
49
|
+
nativeType: descriptor.nativeType,
|
|
50
|
+
typeRef,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function getAuthoringTypeConstructor(
|
|
55
|
+
contributions: AuthoringContributions | undefined,
|
|
56
|
+
path: readonly string[],
|
|
57
|
+
): AuthoringTypeConstructorDescriptor | undefined {
|
|
58
|
+
let current: unknown = contributions?.type;
|
|
59
|
+
|
|
60
|
+
for (const segment of path) {
|
|
61
|
+
if (typeof current !== 'object' || current === null || Array.isArray(current)) {
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
current = (current as Record<string, unknown>)[segment];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return isAuthoringTypeConstructorDescriptor(current) ? current : undefined;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Returns the namespace prefix of `attributeName` if it references an
|
|
72
|
+
* unrecognized extension namespace, otherwise `undefined`. A namespace is
|
|
73
|
+
* considered recognized when it is:
|
|
74
|
+
*
|
|
75
|
+
* - `db` (native-type spec, always allowed),
|
|
76
|
+
* - the active family id (e.g. `sql`),
|
|
77
|
+
* - the active target id (e.g. `postgres`),
|
|
78
|
+
* - present in `composedExtensions`.
|
|
79
|
+
*
|
|
80
|
+
* Family/target namespaces are exempted so that e.g. `@sql.foo` surfaces as
|
|
81
|
+
* PSL_UNSUPPORTED_*_ATTRIBUTE (the attribute isn't defined) rather than
|
|
82
|
+
* PSL_EXTENSION_NAMESPACE_NOT_COMPOSED (the namespace is already composed).
|
|
83
|
+
*/
|
|
84
|
+
export function checkUncomposedNamespace(
|
|
85
|
+
attributeName: string,
|
|
86
|
+
composedExtensions: ReadonlySet<string>,
|
|
87
|
+
context?: { readonly familyId?: string; readonly targetId?: string },
|
|
88
|
+
): string | undefined {
|
|
89
|
+
const dotIndex = attributeName.indexOf('.');
|
|
90
|
+
if (dotIndex <= 0 || dotIndex === attributeName.length - 1) {
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
93
|
+
const namespace = attributeName.slice(0, dotIndex);
|
|
94
|
+
if (
|
|
95
|
+
namespace === 'db' ||
|
|
96
|
+
namespace === context?.familyId ||
|
|
97
|
+
namespace === context?.targetId ||
|
|
98
|
+
composedExtensions.has(namespace)
|
|
99
|
+
) {
|
|
100
|
+
return undefined;
|
|
101
|
+
}
|
|
102
|
+
return namespace;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Pushes the canonical `PSL_EXTENSION_NAMESPACE_NOT_COMPOSED` diagnostic for a
|
|
107
|
+
* subject (attribute, model attribute, or type constructor) that references an
|
|
108
|
+
* extension namespace which is not composed in the current contract.
|
|
109
|
+
*
|
|
110
|
+
* The `data` payload carries the missing namespace so machine consumers
|
|
111
|
+
* (agents, IDE extensions, CLI auto-fix) don't have to parse the prose.
|
|
112
|
+
*/
|
|
113
|
+
export function reportUncomposedNamespace(input: {
|
|
114
|
+
readonly subjectLabel: string;
|
|
115
|
+
readonly namespace: string;
|
|
116
|
+
readonly sourceId: string;
|
|
117
|
+
readonly span: PslSpan;
|
|
118
|
+
readonly diagnostics: ContractSourceDiagnostic[];
|
|
119
|
+
}): void {
|
|
120
|
+
input.diagnostics.push({
|
|
121
|
+
code: 'PSL_EXTENSION_NAMESPACE_NOT_COMPOSED',
|
|
122
|
+
message: `${input.subjectLabel} uses unrecognized namespace "${input.namespace}". Add extension pack "${input.namespace}" to extensionPacks in prisma-next.config.ts.`,
|
|
123
|
+
sourceId: input.sourceId,
|
|
124
|
+
span: input.span,
|
|
125
|
+
data: { namespace: input.namespace, suggestedPack: input.namespace },
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function instantiatePslTypeConstructor(input: {
|
|
130
|
+
readonly call: PslTypeConstructorCall;
|
|
131
|
+
readonly descriptor: AuthoringTypeConstructorDescriptor;
|
|
132
|
+
readonly diagnostics: ContractSourceDiagnostic[];
|
|
133
|
+
readonly sourceId: string;
|
|
134
|
+
readonly entityLabel: string;
|
|
135
|
+
}):
|
|
136
|
+
| {
|
|
137
|
+
readonly codecId: string;
|
|
138
|
+
readonly nativeType: string;
|
|
139
|
+
readonly typeParams?: Record<string, unknown>;
|
|
140
|
+
}
|
|
141
|
+
| undefined {
|
|
142
|
+
const helperPath = input.call.path.join('.');
|
|
143
|
+
const args = mapPslHelperArgs({
|
|
144
|
+
args: input.call.args,
|
|
145
|
+
descriptors: input.descriptor.args ?? [],
|
|
146
|
+
helperLabel: `constructor "${helperPath}"`,
|
|
147
|
+
span: input.call.span,
|
|
148
|
+
diagnostics: input.diagnostics,
|
|
149
|
+
sourceId: input.sourceId,
|
|
150
|
+
entityLabel: input.entityLabel,
|
|
151
|
+
});
|
|
152
|
+
if (!args) {
|
|
153
|
+
return undefined;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
try {
|
|
157
|
+
validateAuthoringHelperArguments(helperPath, input.descriptor.args, args);
|
|
158
|
+
return instantiateAuthoringTypeConstructor(input.descriptor, args);
|
|
159
|
+
} catch (error) {
|
|
160
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
161
|
+
input.diagnostics.push({
|
|
162
|
+
code: 'PSL_INVALID_ATTRIBUTE_ARGUMENT',
|
|
163
|
+
message: `${input.entityLabel} constructor "${helperPath}" ${message}`,
|
|
164
|
+
sourceId: input.sourceId,
|
|
165
|
+
span: input.call.span,
|
|
166
|
+
});
|
|
167
|
+
return undefined;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function pushUnsupportedTypeConstructorDiagnostic(input: {
|
|
172
|
+
readonly diagnostics: ContractSourceDiagnostic[];
|
|
173
|
+
readonly sourceId: string;
|
|
174
|
+
readonly span: PslSpan;
|
|
175
|
+
readonly code: 'PSL_UNSUPPORTED_FIELD_TYPE' | 'PSL_UNSUPPORTED_NAMED_TYPE_CONSTRUCTOR';
|
|
176
|
+
readonly message: string;
|
|
177
|
+
}): undefined {
|
|
178
|
+
input.diagnostics.push({
|
|
179
|
+
code: input.code,
|
|
180
|
+
message: input.message,
|
|
181
|
+
sourceId: input.sourceId,
|
|
182
|
+
span: input.span,
|
|
183
|
+
});
|
|
184
|
+
return undefined;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export function resolvePslTypeConstructorDescriptor(input: {
|
|
188
|
+
readonly call: PslTypeConstructorCall;
|
|
189
|
+
readonly authoringContributions: AuthoringContributions | undefined;
|
|
190
|
+
readonly composedExtensions: ReadonlySet<string>;
|
|
191
|
+
readonly familyId: string;
|
|
192
|
+
readonly targetId: string;
|
|
193
|
+
readonly diagnostics: ContractSourceDiagnostic[];
|
|
194
|
+
readonly sourceId: string;
|
|
195
|
+
readonly unsupportedCode: 'PSL_UNSUPPORTED_FIELD_TYPE' | 'PSL_UNSUPPORTED_NAMED_TYPE_CONSTRUCTOR';
|
|
196
|
+
readonly unsupportedMessage: string;
|
|
197
|
+
}): AuthoringTypeConstructorDescriptor | undefined {
|
|
198
|
+
const descriptor = getAuthoringTypeConstructor(input.authoringContributions, input.call.path);
|
|
199
|
+
if (descriptor) {
|
|
200
|
+
return descriptor;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const namespace = input.call.path.length > 1 ? input.call.path[0] : undefined;
|
|
204
|
+
if (
|
|
205
|
+
namespace &&
|
|
206
|
+
namespace !== 'db' &&
|
|
207
|
+
namespace !== input.familyId &&
|
|
208
|
+
namespace !== input.targetId &&
|
|
209
|
+
!input.composedExtensions.has(namespace)
|
|
210
|
+
) {
|
|
211
|
+
reportUncomposedNamespace({
|
|
212
|
+
subjectLabel: `Type constructor "${input.call.path.join('.')}"`,
|
|
213
|
+
namespace,
|
|
214
|
+
sourceId: input.sourceId,
|
|
215
|
+
span: input.call.span,
|
|
216
|
+
diagnostics: input.diagnostics,
|
|
217
|
+
});
|
|
218
|
+
return undefined;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return pushUnsupportedTypeConstructorDiagnostic({
|
|
222
|
+
diagnostics: input.diagnostics,
|
|
223
|
+
sourceId: input.sourceId,
|
|
224
|
+
span: input.call.span,
|
|
225
|
+
code: input.unsupportedCode,
|
|
226
|
+
message: input.unsupportedMessage,
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export type ResolveFieldTypeResult =
|
|
231
|
+
| { readonly ok: true; readonly descriptor: ColumnDescriptor }
|
|
232
|
+
| { readonly ok: false; readonly alreadyReported: boolean };
|
|
233
|
+
|
|
234
|
+
export function resolveFieldTypeDescriptor(input: {
|
|
235
|
+
readonly field: PslField;
|
|
236
|
+
readonly enumTypeDescriptors: ReadonlyMap<string, ColumnDescriptor>;
|
|
237
|
+
readonly namedTypeDescriptors: ReadonlyMap<string, ColumnDescriptor>;
|
|
238
|
+
readonly scalarTypeDescriptors: ReadonlyMap<string, ColumnDescriptor>;
|
|
239
|
+
readonly authoringContributions: AuthoringContributions | undefined;
|
|
240
|
+
readonly composedExtensions: ReadonlySet<string>;
|
|
241
|
+
readonly familyId: string;
|
|
242
|
+
readonly targetId: string;
|
|
243
|
+
readonly diagnostics: ContractSourceDiagnostic[];
|
|
244
|
+
readonly sourceId: string;
|
|
245
|
+
readonly entityLabel: string;
|
|
246
|
+
}): ResolveFieldTypeResult {
|
|
247
|
+
if (input.field.typeConstructor) {
|
|
248
|
+
const helperPath = input.field.typeConstructor.path.join('.');
|
|
249
|
+
const descriptor = resolvePslTypeConstructorDescriptor({
|
|
250
|
+
call: input.field.typeConstructor,
|
|
251
|
+
authoringContributions: input.authoringContributions,
|
|
252
|
+
composedExtensions: input.composedExtensions,
|
|
253
|
+
familyId: input.familyId,
|
|
254
|
+
targetId: input.targetId,
|
|
255
|
+
diagnostics: input.diagnostics,
|
|
256
|
+
sourceId: input.sourceId,
|
|
257
|
+
unsupportedCode: 'PSL_UNSUPPORTED_FIELD_TYPE',
|
|
258
|
+
unsupportedMessage: `${input.entityLabel} type constructor "${helperPath}" is not supported in SQL PSL provider v1`,
|
|
259
|
+
});
|
|
260
|
+
if (!descriptor) {
|
|
261
|
+
return { ok: false, alreadyReported: true };
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const instantiated = instantiatePslTypeConstructor({
|
|
265
|
+
call: input.field.typeConstructor,
|
|
266
|
+
descriptor,
|
|
267
|
+
diagnostics: input.diagnostics,
|
|
268
|
+
sourceId: input.sourceId,
|
|
269
|
+
entityLabel: input.entityLabel,
|
|
270
|
+
});
|
|
271
|
+
if (!instantiated) {
|
|
272
|
+
return { ok: false, alreadyReported: true };
|
|
273
|
+
}
|
|
274
|
+
return { ok: true, descriptor: instantiated };
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const descriptor = resolveColumnDescriptor(
|
|
278
|
+
input.field,
|
|
279
|
+
input.enumTypeDescriptors,
|
|
280
|
+
input.namedTypeDescriptors,
|
|
281
|
+
input.scalarTypeDescriptors,
|
|
282
|
+
);
|
|
283
|
+
if (!descriptor) {
|
|
284
|
+
return { ok: false, alreadyReported: false };
|
|
285
|
+
}
|
|
286
|
+
return { ok: true, descriptor };
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Declarative specification for @db.* native type attributes.
|
|
291
|
+
*
|
|
292
|
+
* Argument kinds:
|
|
293
|
+
* - `noArgs`: No arguments accepted; `codecId: null` means inherit from baseDescriptor.
|
|
294
|
+
* - `optionalLength`: Zero or one positional integer (minimum 1), stored as `{ length }`.
|
|
295
|
+
* - `optionalPrecision`: Zero or one positional integer (minimum 0), stored as `{ precision }`.
|
|
296
|
+
* - `optionalNumeric`: Zero, one, or two positional integers (precision + scale).
|
|
297
|
+
*/
|
|
298
|
+
export type NativeTypeSpec =
|
|
299
|
+
| {
|
|
300
|
+
readonly args: 'noArgs';
|
|
301
|
+
readonly baseType: string;
|
|
302
|
+
readonly codecId: string | null;
|
|
303
|
+
readonly nativeType: string;
|
|
304
|
+
}
|
|
305
|
+
| {
|
|
306
|
+
readonly args: 'optionalLength';
|
|
307
|
+
readonly baseType: string;
|
|
308
|
+
readonly codecId: string;
|
|
309
|
+
readonly nativeType: string;
|
|
310
|
+
}
|
|
311
|
+
| {
|
|
312
|
+
readonly args: 'optionalPrecision';
|
|
313
|
+
readonly baseType: string;
|
|
314
|
+
readonly codecId: string;
|
|
315
|
+
readonly nativeType: string;
|
|
316
|
+
}
|
|
317
|
+
| {
|
|
318
|
+
readonly args: 'optionalNumeric';
|
|
319
|
+
readonly baseType: string;
|
|
320
|
+
readonly codecId: string;
|
|
321
|
+
readonly nativeType: string;
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
export const NATIVE_TYPE_SPECS: Readonly<Record<string, NativeTypeSpec>> = {
|
|
325
|
+
'db.VarChar': {
|
|
326
|
+
args: 'optionalLength',
|
|
327
|
+
baseType: 'String',
|
|
328
|
+
codecId: 'sql/varchar@1',
|
|
329
|
+
nativeType: 'character varying',
|
|
330
|
+
},
|
|
331
|
+
'db.Char': {
|
|
332
|
+
args: 'optionalLength',
|
|
333
|
+
baseType: 'String',
|
|
334
|
+
codecId: 'sql/char@1',
|
|
335
|
+
nativeType: 'character',
|
|
336
|
+
},
|
|
337
|
+
'db.Uuid': { args: 'noArgs', baseType: 'String', codecId: null, nativeType: 'uuid' },
|
|
338
|
+
'db.SmallInt': { args: 'noArgs', baseType: 'Int', codecId: 'pg/int2@1', nativeType: 'int2' },
|
|
339
|
+
'db.Real': { args: 'noArgs', baseType: 'Float', codecId: 'pg/float4@1', nativeType: 'float4' },
|
|
340
|
+
'db.Numeric': {
|
|
341
|
+
args: 'optionalNumeric',
|
|
342
|
+
baseType: 'Decimal',
|
|
343
|
+
codecId: 'pg/numeric@1',
|
|
344
|
+
nativeType: 'numeric',
|
|
345
|
+
},
|
|
346
|
+
'db.Timestamp': {
|
|
347
|
+
args: 'optionalPrecision',
|
|
348
|
+
baseType: 'DateTime',
|
|
349
|
+
codecId: 'pg/timestamp@1',
|
|
350
|
+
nativeType: 'timestamp',
|
|
351
|
+
},
|
|
352
|
+
'db.Timestamptz': {
|
|
353
|
+
args: 'optionalPrecision',
|
|
354
|
+
baseType: 'DateTime',
|
|
355
|
+
codecId: 'pg/timestamptz@1',
|
|
356
|
+
nativeType: 'timestamptz',
|
|
357
|
+
},
|
|
358
|
+
'db.Date': { args: 'noArgs', baseType: 'DateTime', codecId: null, nativeType: 'date' },
|
|
359
|
+
'db.Time': {
|
|
360
|
+
args: 'optionalPrecision',
|
|
361
|
+
baseType: 'DateTime',
|
|
362
|
+
codecId: 'pg/time@1',
|
|
363
|
+
nativeType: 'time',
|
|
364
|
+
},
|
|
365
|
+
'db.Timetz': {
|
|
366
|
+
args: 'optionalPrecision',
|
|
367
|
+
baseType: 'DateTime',
|
|
368
|
+
codecId: 'pg/timetz@1',
|
|
369
|
+
nativeType: 'timetz',
|
|
370
|
+
},
|
|
371
|
+
'db.Json': { args: 'noArgs', baseType: 'Json', codecId: 'pg/json@1', nativeType: 'json' },
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
export function resolveDbNativeTypeAttribute(input: {
|
|
375
|
+
readonly attribute: PslAttribute;
|
|
376
|
+
readonly baseType: string;
|
|
377
|
+
readonly baseDescriptor: ColumnDescriptor;
|
|
378
|
+
readonly diagnostics: ContractSourceDiagnostic[];
|
|
379
|
+
readonly sourceId: string;
|
|
380
|
+
readonly entityLabel: string;
|
|
381
|
+
}): ColumnDescriptor | undefined {
|
|
382
|
+
const spec = NATIVE_TYPE_SPECS[input.attribute.name];
|
|
383
|
+
if (!spec) {
|
|
384
|
+
input.diagnostics.push({
|
|
385
|
+
code: 'PSL_UNSUPPORTED_NAMED_TYPE_ATTRIBUTE',
|
|
386
|
+
message: `${input.entityLabel} uses unsupported attribute "@${input.attribute.name}"`,
|
|
387
|
+
sourceId: input.sourceId,
|
|
388
|
+
span: input.attribute.span,
|
|
389
|
+
});
|
|
390
|
+
return undefined;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
if (input.baseType !== spec.baseType) {
|
|
394
|
+
return pushInvalidAttributeArgument({
|
|
395
|
+
diagnostics: input.diagnostics,
|
|
396
|
+
sourceId: input.sourceId,
|
|
397
|
+
span: input.attribute.span,
|
|
398
|
+
message: `${input.entityLabel} uses @${input.attribute.name} on unsupported base type "${input.baseType}". Expected "${spec.baseType}".`,
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
switch (spec.args) {
|
|
403
|
+
case 'noArgs': {
|
|
404
|
+
if (getPositionalArguments(input.attribute).length > 0 || input.attribute.args.length > 0) {
|
|
405
|
+
return pushInvalidAttributeArgument({
|
|
406
|
+
diagnostics: input.diagnostics,
|
|
407
|
+
sourceId: input.sourceId,
|
|
408
|
+
span: input.attribute.span,
|
|
409
|
+
message: `${input.entityLabel} @${input.attribute.name} does not accept arguments.`,
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
return {
|
|
413
|
+
codecId: spec.codecId ?? input.baseDescriptor.codecId,
|
|
414
|
+
nativeType: spec.nativeType,
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
case 'optionalLength': {
|
|
418
|
+
const length = parseOptionalSingleIntegerArgument({
|
|
419
|
+
attribute: input.attribute,
|
|
420
|
+
diagnostics: input.diagnostics,
|
|
421
|
+
sourceId: input.sourceId,
|
|
422
|
+
entityLabel: input.entityLabel,
|
|
423
|
+
minimum: 1,
|
|
424
|
+
valueLabel: 'positive integer length',
|
|
425
|
+
});
|
|
426
|
+
if (length === undefined) {
|
|
427
|
+
return undefined;
|
|
428
|
+
}
|
|
429
|
+
return {
|
|
430
|
+
codecId: spec.codecId,
|
|
431
|
+
nativeType: spec.nativeType,
|
|
432
|
+
...(length === null ? {} : { typeParams: { length } }),
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
case 'optionalPrecision': {
|
|
436
|
+
const precision = parseOptionalSingleIntegerArgument({
|
|
437
|
+
attribute: input.attribute,
|
|
438
|
+
diagnostics: input.diagnostics,
|
|
439
|
+
sourceId: input.sourceId,
|
|
440
|
+
entityLabel: input.entityLabel,
|
|
441
|
+
minimum: 0,
|
|
442
|
+
valueLabel: 'non-negative integer precision',
|
|
443
|
+
});
|
|
444
|
+
if (precision === undefined) {
|
|
445
|
+
return undefined;
|
|
446
|
+
}
|
|
447
|
+
return {
|
|
448
|
+
codecId: spec.codecId,
|
|
449
|
+
nativeType: spec.nativeType,
|
|
450
|
+
...(precision === null ? {} : { typeParams: { precision } }),
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
case 'optionalNumeric': {
|
|
454
|
+
const numeric = parseOptionalNumericArguments({
|
|
455
|
+
attribute: input.attribute,
|
|
456
|
+
diagnostics: input.diagnostics,
|
|
457
|
+
sourceId: input.sourceId,
|
|
458
|
+
entityLabel: input.entityLabel,
|
|
459
|
+
});
|
|
460
|
+
if (numeric === undefined) {
|
|
461
|
+
return undefined;
|
|
462
|
+
}
|
|
463
|
+
return {
|
|
464
|
+
codecId: spec.codecId,
|
|
465
|
+
nativeType: spec.nativeType,
|
|
466
|
+
...(numeric === null ? {} : { typeParams: numeric }),
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
export function parseDefaultLiteralValue(expression: string): ColumnDefault | undefined {
|
|
473
|
+
const trimmed = expression.trim();
|
|
474
|
+
if (trimmed === 'true' || trimmed === 'false') {
|
|
475
|
+
return { kind: 'literal', value: trimmed === 'true' };
|
|
476
|
+
}
|
|
477
|
+
const numericValue = Number(trimmed);
|
|
478
|
+
if (!Number.isNaN(numericValue) && trimmed.length > 0 && !/^(['"]).*\1$/.test(trimmed)) {
|
|
479
|
+
return { kind: 'literal', value: numericValue };
|
|
480
|
+
}
|
|
481
|
+
if (/^(['"]).*\1$/.test(trimmed)) {
|
|
482
|
+
return { kind: 'literal', value: unquoteStringLiteral(trimmed) };
|
|
483
|
+
}
|
|
484
|
+
return undefined;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
export function lowerDefaultForField(input: {
|
|
488
|
+
readonly modelName: string;
|
|
489
|
+
readonly fieldName: string;
|
|
490
|
+
readonly defaultAttribute: PslAttribute;
|
|
491
|
+
readonly columnDescriptor: ColumnDescriptor;
|
|
492
|
+
readonly generatorDescriptorById: ReadonlyMap<string, MutationDefaultGeneratorDescriptor>;
|
|
493
|
+
readonly sourceId: string;
|
|
494
|
+
readonly defaultFunctionRegistry: ControlMutationDefaultRegistry;
|
|
495
|
+
readonly diagnostics: ContractSourceDiagnostic[];
|
|
496
|
+
}): {
|
|
497
|
+
readonly defaultValue?: ColumnDefault;
|
|
498
|
+
readonly executionDefault?: ExecutionMutationDefaultValue;
|
|
499
|
+
} {
|
|
500
|
+
const positionalEntries = input.defaultAttribute.args.filter((arg) => arg.kind === 'positional');
|
|
501
|
+
const namedEntries = input.defaultAttribute.args.filter((arg) => arg.kind === 'named');
|
|
502
|
+
|
|
503
|
+
if (namedEntries.length > 0 || positionalEntries.length !== 1) {
|
|
504
|
+
input.diagnostics.push({
|
|
505
|
+
code: 'PSL_INVALID_DEFAULT_FUNCTION_ARGUMENT',
|
|
506
|
+
message: `Field "${input.modelName}.${input.fieldName}" requires exactly one positional @default(...) expression.`,
|
|
507
|
+
sourceId: input.sourceId,
|
|
508
|
+
span: input.defaultAttribute.span,
|
|
509
|
+
});
|
|
510
|
+
return {};
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
const expressionEntry = getPositionalArgumentEntry(input.defaultAttribute);
|
|
514
|
+
if (!expressionEntry) {
|
|
515
|
+
input.diagnostics.push({
|
|
516
|
+
code: 'PSL_INVALID_DEFAULT_FUNCTION_ARGUMENT',
|
|
517
|
+
message: `Field "${input.modelName}.${input.fieldName}" requires a positional @default(...) expression.`,
|
|
518
|
+
sourceId: input.sourceId,
|
|
519
|
+
span: input.defaultAttribute.span,
|
|
520
|
+
});
|
|
521
|
+
return {};
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
const literalDefault = parseDefaultLiteralValue(expressionEntry.value);
|
|
525
|
+
if (literalDefault) {
|
|
526
|
+
return { defaultValue: literalDefault };
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
const defaultFunctionCall = parseDefaultFunctionCall(expressionEntry.value, expressionEntry.span);
|
|
530
|
+
if (!defaultFunctionCall) {
|
|
531
|
+
input.diagnostics.push({
|
|
532
|
+
code: 'PSL_INVALID_DEFAULT_VALUE',
|
|
533
|
+
message: `Unsupported default value "${expressionEntry.value}"`,
|
|
534
|
+
sourceId: input.sourceId,
|
|
535
|
+
span: input.defaultAttribute.span,
|
|
536
|
+
});
|
|
537
|
+
return {};
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
const lowered = lowerDefaultFunctionWithRegistry({
|
|
541
|
+
call: defaultFunctionCall,
|
|
542
|
+
registry: input.defaultFunctionRegistry,
|
|
543
|
+
context: {
|
|
544
|
+
sourceId: input.sourceId,
|
|
545
|
+
modelName: input.modelName,
|
|
546
|
+
fieldName: input.fieldName,
|
|
547
|
+
columnCodecId: input.columnDescriptor.codecId,
|
|
548
|
+
},
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
if (!lowered.ok) {
|
|
552
|
+
input.diagnostics.push(lowered.diagnostic);
|
|
553
|
+
return {};
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
if (lowered.value.kind === 'storage') {
|
|
557
|
+
return { defaultValue: lowered.value.defaultValue };
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
const generatorDescriptor = input.generatorDescriptorById.get(lowered.value.generated.id);
|
|
561
|
+
if (!generatorDescriptor) {
|
|
562
|
+
input.diagnostics.push({
|
|
563
|
+
code: 'PSL_INVALID_DEFAULT_APPLICABILITY',
|
|
564
|
+
message: `Default generator "${lowered.value.generated.id}" is not available in the composed mutation default registry.`,
|
|
565
|
+
sourceId: input.sourceId,
|
|
566
|
+
span: expressionEntry.span,
|
|
567
|
+
});
|
|
568
|
+
return {};
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
if (!generatorDescriptor.applicableCodecIds.includes(input.columnDescriptor.codecId)) {
|
|
572
|
+
input.diagnostics.push({
|
|
573
|
+
code: 'PSL_INVALID_DEFAULT_APPLICABILITY',
|
|
574
|
+
message: `Default generator "${generatorDescriptor.id}" is not applicable to "${input.modelName}.${input.fieldName}" with codecId "${input.columnDescriptor.codecId}".`,
|
|
575
|
+
sourceId: input.sourceId,
|
|
576
|
+
span: expressionEntry.span,
|
|
577
|
+
});
|
|
578
|
+
return {};
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
return { executionDefault: lowered.value.generated };
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
export function resolveColumnDescriptor(
|
|
585
|
+
field: PslField,
|
|
586
|
+
enumTypeDescriptors: ReadonlyMap<string, ColumnDescriptor>,
|
|
587
|
+
namedTypeDescriptors: ReadonlyMap<string, ColumnDescriptor>,
|
|
588
|
+
scalarTypeDescriptors: ReadonlyMap<string, ColumnDescriptor>,
|
|
589
|
+
): ColumnDescriptor | undefined {
|
|
590
|
+
if (field.typeRef && namedTypeDescriptors.has(field.typeRef)) {
|
|
591
|
+
return namedTypeDescriptors.get(field.typeRef);
|
|
592
|
+
}
|
|
593
|
+
if (namedTypeDescriptors.has(field.typeName)) {
|
|
594
|
+
return namedTypeDescriptors.get(field.typeName);
|
|
595
|
+
}
|
|
596
|
+
if (enumTypeDescriptors.has(field.typeName)) {
|
|
597
|
+
return enumTypeDescriptors.get(field.typeName);
|
|
598
|
+
}
|
|
599
|
+
return scalarTypeDescriptors.get(field.typeName);
|
|
600
|
+
}
|