@prisma-next/family-sql 0.13.0-dev.3 → 0.13.0-dev.31
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/dist/authoring-type-constructors-CjFfO6LM.mjs +342 -0
- package/dist/authoring-type-constructors-CjFfO6LM.mjs.map +1 -0
- package/dist/{control-adapter-CgIL9Vtx.d.mts → control-adapter-Cmw9LvEP.d.mts} +16 -33
- package/dist/control-adapter-Cmw9LvEP.d.mts.map +1 -0
- package/dist/control-adapter.d.mts +2 -2
- package/dist/control.d.mts +36 -34
- package/dist/control.d.mts.map +1 -1
- package/dist/control.mjs +24 -77
- package/dist/control.mjs.map +1 -1
- package/dist/ir.d.mts +3 -2
- package/dist/ir.d.mts.map +1 -1
- package/dist/ir.mjs +1 -1
- package/dist/migration.d.mts +1 -1
- package/dist/migration.d.mts.map +1 -1
- package/dist/migration.mjs +2 -1
- package/dist/migration.mjs.map +1 -1
- package/dist/pack.d.mts +16 -3
- package/dist/pack.d.mts.map +1 -1
- package/dist/pack.mjs +4 -2
- package/dist/pack.mjs.map +1 -1
- package/dist/schema-verify.d.mts +1 -1
- package/dist/schema-verify.mjs +1 -1
- package/dist/{sql-contract-serializer-CY7qnms7.mjs → sql-contract-serializer-D6-28zKd.mjs} +26 -15
- package/dist/sql-contract-serializer-D6-28zKd.mjs.map +1 -0
- package/dist/{types-CbwQCzXY.d.mts → types-kgstZ_Zd.d.mts} +5 -5
- package/dist/types-kgstZ_Zd.d.mts.map +1 -0
- package/dist/{verify-sql-schema-DcMaT5Zj.d.mts → verify-sql-schema-thU-jKpf.d.mts} +2 -14
- package/dist/verify-sql-schema-thU-jKpf.d.mts.map +1 -0
- package/dist/{verify-sql-schema-DlAgBiT_.mjs → verify-sql-schema-xT4udQLQ.mjs} +25 -118
- package/dist/verify-sql-schema-xT4udQLQ.mjs.map +1 -0
- package/package.json +21 -21
- package/src/core/authoring-entity-types.ts +178 -0
- package/src/core/authoring-field-presets.ts +8 -3
- package/src/core/control-adapter.ts +18 -49
- package/src/core/control-descriptor.ts +3 -0
- package/src/core/control-instance.ts +13 -11
- package/src/core/ir/sql-contract-serializer-base.ts +76 -60
- package/src/core/migrations/contract-to-schema-ir.ts +47 -112
- package/src/core/migrations/types.ts +4 -1
- package/src/core/psl-contract-infer/postgres-type-map.ts +5 -13
- package/src/core/psl-contract-infer/sql-schema-ir-to-psl-ast.ts +17 -70
- package/src/core/schema-verify/verify-sql-schema.ts +10 -146
- package/src/core/sql-migration.ts +5 -1
- package/src/exports/control-adapter.ts +1 -0
- package/src/exports/control.ts +1 -1
- package/src/exports/pack.ts +3 -0
- package/dist/authoring-type-constructors-D4lQ-qpj.mjs +0 -192
- package/dist/authoring-type-constructors-D4lQ-qpj.mjs.map +0 -1
- package/dist/control-adapter-CgIL9Vtx.d.mts.map +0 -1
- package/dist/sql-contract-serializer-CY7qnms7.mjs.map +0 -1
- package/dist/types-CbwQCzXY.d.mts.map +0 -1
- package/dist/verify-sql-schema-DcMaT5Zj.d.mts.map +0 -1
- package/dist/verify-sql-schema-DlAgBiT_.mjs.map +0 -1
|
@@ -1,18 +1,14 @@
|
|
|
1
|
-
import type { ColumnDefault, Contract } from '@prisma-next/contract/types';
|
|
1
|
+
import type { ColumnDefault, Contract, JsonValue } from '@prisma-next/contract/types';
|
|
2
2
|
import type { MigrationPlannerConflict } from '@prisma-next/framework-components/control';
|
|
3
|
-
import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir';
|
|
4
3
|
import {
|
|
5
4
|
type CheckConstraint,
|
|
6
5
|
type ForeignKey,
|
|
7
6
|
type Index,
|
|
8
|
-
isPostgresEnumStorageEntry,
|
|
9
7
|
isStorageTypeInstance,
|
|
10
|
-
type PostgresEnumStorageEntry,
|
|
11
8
|
type SqlStorage,
|
|
12
9
|
type StorageColumn,
|
|
13
10
|
StorageTable,
|
|
14
11
|
type StorageTypeInstance,
|
|
15
|
-
toStorageTypeInstance,
|
|
16
12
|
type UniqueConstraint,
|
|
17
13
|
} from '@prisma-next/sql-contract/types';
|
|
18
14
|
import { defaultIndexName } from '@prisma-next/sql-schema-ir/naming';
|
|
@@ -26,7 +22,6 @@ import type {
|
|
|
26
22
|
SqlTableIR,
|
|
27
23
|
SqlUniqueIR,
|
|
28
24
|
} from '@prisma-next/sql-schema-ir/types';
|
|
29
|
-
import { blindCast } from '@prisma-next/utils/casts';
|
|
30
25
|
import { ifDefined } from '@prisma-next/utils/defined';
|
|
31
26
|
|
|
32
27
|
/**
|
|
@@ -57,24 +52,19 @@ export type NativeTypeExpander = (input: {
|
|
|
57
52
|
export type DefaultRenderer = (def: ColumnDefault, column: StorageColumn) => string;
|
|
58
53
|
|
|
59
54
|
/**
|
|
60
|
-
* Target-supplied callback that
|
|
61
|
-
*
|
|
55
|
+
* Target-supplied callback that resolves a contract namespace to the live
|
|
56
|
+
* database schema its enums are stored under.
|
|
62
57
|
*
|
|
63
|
-
*
|
|
64
|
-
* namespaces holding an enum with
|
|
65
|
-
* native type
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
* target
|
|
71
|
-
* still emits keys that match the target's read side exactly.
|
|
58
|
+
* The projected enum annotations are nested by schema
|
|
59
|
+
* (`storageTypes[schema][nativeType]`) so two namespaces holding an enum with
|
|
60
|
+
* the same native type resolve to distinct live-database types. Mapping a
|
|
61
|
+
* namespace to its DDL schema is target-specific (Postgres schemas;
|
|
62
|
+
* SQLite/MySQL differ), so the target injects it here rather than the family
|
|
63
|
+
* importing a concrete `ddlSchemaName`. This keeps the family layer
|
|
64
|
+
* target-agnostic while the projection nests under the same schema the
|
|
65
|
+
* target's read side (`readExistingEnumValues`) looks up.
|
|
72
66
|
*/
|
|
73
|
-
export type
|
|
74
|
-
storage: SqlStorage,
|
|
75
|
-
namespaceId: string,
|
|
76
|
-
nativeType: string,
|
|
77
|
-
) => string;
|
|
67
|
+
export type EnumNamespaceSchemaResolver = (storage: SqlStorage, namespaceId: string) => string;
|
|
78
68
|
|
|
79
69
|
function convertColumn(
|
|
80
70
|
name: string,
|
|
@@ -110,17 +100,7 @@ function convertColumn(
|
|
|
110
100
|
};
|
|
111
101
|
}
|
|
112
102
|
|
|
113
|
-
|
|
114
|
-
* `storageTypes` is polymorphic per Decision 18 (Option B) — codec-typed
|
|
115
|
-
* entries match `StorageTypeInstance`; enum entries match the structural
|
|
116
|
-
* `PostgresEnumStorageEntry` shape (Postgres-only; cross-domain layering
|
|
117
|
-
* keeps target IR classes out of the family layer). Both shapes resolve
|
|
118
|
-
* into the same `(codecId, nativeType, typeParams)` triplet at the
|
|
119
|
-
* column-resolution boundary so downstream walks stay uniform.
|
|
120
|
-
*/
|
|
121
|
-
type ResolvedStorageTypes = Readonly<
|
|
122
|
-
Record<string, StorageTypeInstance | PostgresEnumStorageEntry>
|
|
123
|
-
>;
|
|
103
|
+
type ResolvedStorageTypes = Readonly<Record<string, StorageTypeInstance>>;
|
|
124
104
|
|
|
125
105
|
function resolveColumnTypeMetadata(
|
|
126
106
|
column: StorageColumn,
|
|
@@ -135,13 +115,6 @@ function resolveColumnTypeMetadata(
|
|
|
135
115
|
`Column references storage type "${column.typeRef}" but it is not defined in storage.types.`,
|
|
136
116
|
);
|
|
137
117
|
}
|
|
138
|
-
if (isPostgresEnumStorageEntry(referenced)) {
|
|
139
|
-
return {
|
|
140
|
-
codecId: referenced.codecId,
|
|
141
|
-
nativeType: referenced.nativeType,
|
|
142
|
-
typeParams: { values: referenced.values } as Record<string, unknown>,
|
|
143
|
-
};
|
|
144
|
-
}
|
|
145
118
|
if (isStorageTypeInstance(referenced)) {
|
|
146
119
|
return {
|
|
147
120
|
codecId: referenced.codecId,
|
|
@@ -150,7 +123,7 @@ function resolveColumnTypeMetadata(
|
|
|
150
123
|
};
|
|
151
124
|
}
|
|
152
125
|
throw new Error(
|
|
153
|
-
`Storage type "${column.typeRef}" has an unknown polymorphic kind; expected codec-
|
|
126
|
+
`Storage type "${column.typeRef}" has an unknown polymorphic kind; expected a codec-typed StorageTypeInstance.`,
|
|
154
127
|
);
|
|
155
128
|
}
|
|
156
129
|
|
|
@@ -164,8 +137,12 @@ function resolveColumnTypeMetadata(
|
|
|
164
137
|
* `checkConstraintPlanCallStrategy` (migration planning) so all three agree on
|
|
165
138
|
* the resolved values and the error behavior on a missing reference.
|
|
166
139
|
*/
|
|
140
|
+
function allStrings(values: readonly JsonValue[]): values is readonly string[] {
|
|
141
|
+
return values.every((value) => typeof value === 'string');
|
|
142
|
+
}
|
|
143
|
+
|
|
167
144
|
export function resolveValueSetValues(
|
|
168
|
-
ref: { readonly namespaceId: string; readonly
|
|
145
|
+
ref: { readonly namespaceId: string; readonly entityName: string },
|
|
169
146
|
storage: SqlStorage,
|
|
170
147
|
contextLabel: string,
|
|
171
148
|
): readonly string[] {
|
|
@@ -175,13 +152,22 @@ export function resolveValueSetValues(
|
|
|
175
152
|
`resolveValueSetValues: namespace "${ref.namespaceId}" not found in storage (${contextLabel})`,
|
|
176
153
|
);
|
|
177
154
|
}
|
|
178
|
-
const valueSet = ns.entries.valueSet?.[ref.
|
|
155
|
+
const valueSet = ns.entries.valueSet?.[ref.entityName];
|
|
179
156
|
if (!valueSet) {
|
|
180
157
|
throw new Error(
|
|
181
|
-
`resolveValueSetValues: value-set "${ref.
|
|
158
|
+
`resolveValueSetValues: value-set "${ref.entityName}" not found in namespace "${ref.namespaceId}" (${contextLabel})`,
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
// Only TEXT enums ship a CHECK-constraint round-trip in this slice. A
|
|
162
|
+
// non-string value-set is a numeric enum, whose CHECK rendering/verification
|
|
163
|
+
// is future work; fail loudly rather than emit a wrong numeric-as-text check.
|
|
164
|
+
const values = valueSet.values;
|
|
165
|
+
if (!allStrings(values)) {
|
|
166
|
+
throw new Error(
|
|
167
|
+
`resolveValueSetValues: value-set "${ref.entityName}" in namespace "${ref.namespaceId}" has a non-string value; numeric-enum CHECK constraints are not yet supported (${contextLabel})`,
|
|
182
168
|
);
|
|
183
169
|
}
|
|
184
|
-
return
|
|
170
|
+
return values;
|
|
185
171
|
}
|
|
186
172
|
|
|
187
173
|
/**
|
|
@@ -316,7 +302,7 @@ export function detectDestructiveChanges(
|
|
|
316
302
|
if (!fromTables) continue;
|
|
317
303
|
|
|
318
304
|
for (const tableName of Object.keys(fromTables)) {
|
|
319
|
-
const toTableRaw = toNs?.entries.table[tableName];
|
|
305
|
+
const toTableRaw = toNs?.entries.table?.[tableName];
|
|
320
306
|
if (!(toTableRaw instanceof StorageTable)) {
|
|
321
307
|
conflicts.push({
|
|
322
308
|
kind: 'tableRemoved',
|
|
@@ -349,13 +335,13 @@ export interface ContractToSchemaIROptions {
|
|
|
349
335
|
readonly expandNativeType?: NativeTypeExpander;
|
|
350
336
|
readonly renderDefault?: DefaultRenderer;
|
|
351
337
|
/**
|
|
352
|
-
* Target-supplied resolver
|
|
353
|
-
*
|
|
354
|
-
*
|
|
355
|
-
* `readExistingEnumValues` lookup. Targets without
|
|
356
|
-
* storage (SQLite) omit it; enums are absent there.
|
|
338
|
+
* Target-supplied resolver mapping a namespace to the live database schema
|
|
339
|
+
* its enums are stored under. When provided (Postgres), namespace-scoped
|
|
340
|
+
* enums are nested by that schema in `enumTypes` so the projection matches
|
|
341
|
+
* the target's `readExistingEnumValues` lookup. Targets without
|
|
342
|
+
* schema-scoped enum storage (SQLite) omit it; enums are absent there.
|
|
357
343
|
*/
|
|
358
|
-
readonly
|
|
344
|
+
readonly resolveEnumNamespaceSchema?: EnumNamespaceSchemaResolver;
|
|
359
345
|
}
|
|
360
346
|
|
|
361
347
|
/**
|
|
@@ -385,24 +371,12 @@ export function contractToSchemaIR(
|
|
|
385
371
|
}
|
|
386
372
|
|
|
387
373
|
const storage = contract.storage;
|
|
388
|
-
const
|
|
374
|
+
const storageTypes: ResolvedStorageTypes = {
|
|
389
375
|
...((storage.types ?? {}) as ResolvedStorageTypes),
|
|
390
376
|
};
|
|
391
|
-
for (const ns of Object.values(storage.namespaces)) {
|
|
392
|
-
const nsEnums = ns.entries['type'];
|
|
393
|
-
if (nsEnums) {
|
|
394
|
-
for (const [k, v] of Object.entries(nsEnums)) {
|
|
395
|
-
allTypes[k] = blindCast<
|
|
396
|
-
PostgresEnumStorageEntry | StorageTypeInstance,
|
|
397
|
-
'entries.type holds postgres-specific enum entries at runtime'
|
|
398
|
-
>(v);
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
const storageTypes = allTypes as ResolvedStorageTypes;
|
|
403
377
|
const tables: Record<string, SqlTableIR> = {};
|
|
404
378
|
for (const ns of Object.values(storage.namespaces)) {
|
|
405
|
-
for (const [tableName, tableDefRaw] of Object.entries(ns.entries.table)) {
|
|
379
|
+
for (const [tableName, tableDefRaw] of Object.entries(ns.entries.table ?? {})) {
|
|
406
380
|
if (!(tableDefRaw instanceof StorageTable)) {
|
|
407
381
|
throw new Error(
|
|
408
382
|
`contractToSchemaIR: expected StorageTable at namespaces.${ns.id}.entries.table.${tableName}`,
|
|
@@ -428,7 +402,7 @@ export function contractToSchemaIR(
|
|
|
428
402
|
const annotations = deriveAnnotations(
|
|
429
403
|
storage,
|
|
430
404
|
options.annotationNamespace,
|
|
431
|
-
options.
|
|
405
|
+
options.resolveEnumNamespaceSchema,
|
|
432
406
|
);
|
|
433
407
|
|
|
434
408
|
return {
|
|
@@ -437,61 +411,22 @@ export function contractToSchemaIR(
|
|
|
437
411
|
};
|
|
438
412
|
}
|
|
439
413
|
|
|
440
|
-
/**
|
|
441
|
-
* Normalises a native enum storage entry to the codec-typed annotation shape
|
|
442
|
-
* `{codecId, nativeType, typeParams}` the introspector writes and
|
|
443
|
-
* `readExistingEnumValues` reads (`existing.codecId` + `existing.typeParams.values`).
|
|
444
|
-
* Without this the projector would emit the raw `PostgresEnumStorageEntry`
|
|
445
|
-
* shape (top-level `values`, no `typeParams`) and the enum would read as new.
|
|
446
|
-
*/
|
|
447
|
-
function normalizeEnumAnnotation(entry: PostgresEnumStorageEntry): StorageTypeInstance {
|
|
448
|
-
return toStorageTypeInstance({
|
|
449
|
-
codecId: entry.codecId,
|
|
450
|
-
nativeType: entry.nativeType,
|
|
451
|
-
typeParams: { values: entry.values },
|
|
452
|
-
});
|
|
453
|
-
}
|
|
454
|
-
|
|
455
414
|
function deriveAnnotations(
|
|
456
415
|
storage: SqlStorage,
|
|
457
416
|
annotationNamespace: string,
|
|
458
|
-
|
|
417
|
+
_resolveEnumNamespaceSchema: EnumNamespaceSchemaResolver | undefined,
|
|
459
418
|
): SqlAnnotations | undefined {
|
|
460
419
|
const storageTypes: Record<string, StorageTypeInstance> = {};
|
|
461
420
|
|
|
462
|
-
// Top-level `storage.types`: codec-typed entries (vector, decimal, …) keyed
|
|
463
|
-
// by bare `nativeType` (unchanged). Post-S1.B enums live in
|
|
464
|
-
// `namespaces[*].entries.type`, not here; a defensive top-level enum is still
|
|
465
|
-
// namespace/schema-qualified via the resolver under the unbound coordinate
|
|
466
|
-
// so it never collides on a bare name.
|
|
467
421
|
for (const typeInstance of Object.values((storage.types ?? {}) as ResolvedStorageTypes)) {
|
|
468
|
-
if (isPostgresEnumStorageEntry(typeInstance)) {
|
|
469
|
-
const key = resolveEnumStorageKey
|
|
470
|
-
? resolveEnumStorageKey(storage, UNBOUND_NAMESPACE_ID, typeInstance.nativeType)
|
|
471
|
-
: typeInstance.nativeType;
|
|
472
|
-
storageTypes[key] = normalizeEnumAnnotation(typeInstance);
|
|
473
|
-
continue;
|
|
474
|
-
}
|
|
475
422
|
if (isStorageTypeInstance(typeInstance)) {
|
|
476
423
|
storageTypes[typeInstance.nativeType] = typeInstance;
|
|
477
424
|
}
|
|
478
425
|
}
|
|
479
426
|
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
if (!nsEnums) continue;
|
|
486
|
-
for (const entry of Object.values(nsEnums)) {
|
|
487
|
-
if (!isPostgresEnumStorageEntry(entry)) continue;
|
|
488
|
-
const key = resolveEnumStorageKey
|
|
489
|
-
? resolveEnumStorageKey(storage, namespaceId, entry.nativeType)
|
|
490
|
-
: entry.nativeType;
|
|
491
|
-
storageTypes[key] = normalizeEnumAnnotation(entry);
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
if (Object.keys(storageTypes).length === 0) return undefined;
|
|
496
|
-
return { [annotationNamespace]: { storageTypes } };
|
|
427
|
+
const envelope = {
|
|
428
|
+
...(Object.keys(storageTypes).length > 0 ? { storageTypes } : {}),
|
|
429
|
+
};
|
|
430
|
+
if (Object.keys(envelope).length === 0) return undefined;
|
|
431
|
+
return { [annotationNamespace]: envelope };
|
|
497
432
|
}
|
|
@@ -251,7 +251,10 @@ export interface SqlMigrationPlan<TTargetDetails> extends MigrationPlan {
|
|
|
251
251
|
* Destination contract identity that the plan intends to reach.
|
|
252
252
|
*/
|
|
253
253
|
readonly destination: SqlMigrationPlanContractInfo;
|
|
254
|
-
readonly operations: readonly
|
|
254
|
+
readonly operations: readonly (
|
|
255
|
+
| SqlMigrationPlanOperation<TTargetDetails>
|
|
256
|
+
| Promise<SqlMigrationPlanOperation<TTargetDetails>>
|
|
257
|
+
)[];
|
|
255
258
|
/**
|
|
256
259
|
* Sorted, deduplicated invariant ids declared by this plan's data-transform
|
|
257
260
|
* ops. Required at the SQL-family layer (the SQL runners consume this as
|
|
@@ -63,8 +63,6 @@ const PARAMETERIZED_NATIVE_TYPES: Record<
|
|
|
63
63
|
|
|
64
64
|
const PARAMETERIZED_TYPE_PATTERN = /^(.+?)\((.+)\)$/;
|
|
65
65
|
|
|
66
|
-
const ENUM_CODEC_ID = 'pg/enum@1';
|
|
67
|
-
|
|
68
66
|
function getOwnMappingValue(map: Record<string, string>, key: string): string | undefined {
|
|
69
67
|
return Object.hasOwn(map, key) ? map[key] : undefined;
|
|
70
68
|
}
|
|
@@ -132,21 +130,15 @@ export function createPostgresTypeMap(enumTypeNames?: ReadonlySet<string>): PslT
|
|
|
132
130
|
|
|
133
131
|
export function extractEnumInfo(annotations?: Record<string, unknown>): EnumInfo {
|
|
134
132
|
const pgAnnotations = annotations?.['pg'] as Record<string, unknown> | undefined;
|
|
135
|
-
const
|
|
136
|
-
| Record<string, { codecId: string; nativeType: string; typeParams?: Record<string, unknown> }>
|
|
137
|
-
| undefined;
|
|
133
|
+
const nativeEnumTypeNames = pgAnnotations?.['nativeEnumTypeNames'];
|
|
138
134
|
|
|
139
135
|
const typeNames = new Set<string>();
|
|
140
136
|
const definitions = new Map<string, readonly string[]>();
|
|
141
137
|
|
|
142
|
-
if (
|
|
143
|
-
for (const
|
|
144
|
-
if (
|
|
145
|
-
typeNames.add(
|
|
146
|
-
const values = typeInstance.typeParams?.['values'];
|
|
147
|
-
if (Array.isArray(values)) {
|
|
148
|
-
definitions.set(key, values as string[]);
|
|
149
|
-
}
|
|
138
|
+
if (Array.isArray(nativeEnumTypeNames)) {
|
|
139
|
+
for (const name of nativeEnumTypeNames) {
|
|
140
|
+
if (typeof name === 'string') {
|
|
141
|
+
typeNames.add(name);
|
|
150
142
|
}
|
|
151
143
|
}
|
|
152
144
|
}
|
|
@@ -3,7 +3,6 @@ import type {
|
|
|
3
3
|
PslAttribute,
|
|
4
4
|
PslAttributeArgument,
|
|
5
5
|
PslDocumentAst,
|
|
6
|
-
PslEnum,
|
|
7
6
|
PslField,
|
|
8
7
|
PslFieldAttribute,
|
|
9
8
|
PslModel,
|
|
@@ -20,11 +19,10 @@ import {
|
|
|
20
19
|
import type { SqlColumnIR, SqlSchemaIR, SqlTableIR } from '@prisma-next/sql-schema-ir/types';
|
|
21
20
|
import type { DefaultMappingOptions } from './default-mapping';
|
|
22
21
|
import { mapDefault } from './default-mapping';
|
|
23
|
-
import {
|
|
22
|
+
import { toFieldName, toModelName, toNamedTypeName } from './name-transforms';
|
|
24
23
|
import { createPostgresDefaultMapping } from './postgres-default-mapping';
|
|
25
24
|
import { createPostgresTypeMap, extractEnumInfo } from './postgres-type-map';
|
|
26
25
|
import type {
|
|
27
|
-
EnumInfo,
|
|
28
26
|
PslNativeTypeAttribute,
|
|
29
27
|
PslPrinterOptions,
|
|
30
28
|
PslTypeMap,
|
|
@@ -82,10 +80,19 @@ type TopLevelNameResult = {
|
|
|
82
80
|
*/
|
|
83
81
|
export function sqlSchemaIrToPslAst(schemaIR: SqlSchemaIR): PslDocumentAst {
|
|
84
82
|
const enumInfo = extractEnumInfo(schemaIR.annotations);
|
|
83
|
+
if (enumInfo.typeNames.size > 0) {
|
|
84
|
+
const names = [...enumInfo.typeNames].join(', ');
|
|
85
|
+
throw new Error(
|
|
86
|
+
`contract infer: the database contains native Postgres enum type(s): ${names}. ` +
|
|
87
|
+
'Native Postgres enums (CREATE TYPE … AS ENUM) are not adoptable by contract infer. ' +
|
|
88
|
+
'Drop the native type and replace each column with a text column carrying a CHECK constraint, ' +
|
|
89
|
+
`then re-run contract infer. The domain enum (enum Name { @@type("pg/text@1") … }) authoring ` +
|
|
90
|
+
'surface generates the required check automatically.',
|
|
91
|
+
);
|
|
92
|
+
}
|
|
85
93
|
const options: PslPrinterOptions = {
|
|
86
|
-
typeMap: createPostgresTypeMap(
|
|
94
|
+
typeMap: createPostgresTypeMap(new Set()),
|
|
87
95
|
defaultMapping: createPostgresDefaultMapping(),
|
|
88
|
-
enumInfo,
|
|
89
96
|
parseRawDefault,
|
|
90
97
|
};
|
|
91
98
|
|
|
@@ -93,12 +100,7 @@ export function sqlSchemaIrToPslAst(schemaIR: SqlSchemaIR): PslDocumentAst {
|
|
|
93
100
|
}
|
|
94
101
|
|
|
95
102
|
function buildPslDocumentAst(schemaIR: SqlSchemaIR, options: PslPrinterOptions): PslDocumentAst {
|
|
96
|
-
const { typeMap, defaultMapping,
|
|
97
|
-
const emptyEnumInfo: EnumInfo = {
|
|
98
|
-
typeNames: new Set<string>(),
|
|
99
|
-
definitions: new Map<string, readonly string[]>(),
|
|
100
|
-
};
|
|
101
|
-
const { typeNames: enumTypeNames, definitions: enumDefinitions } = enumInfo ?? emptyEnumInfo;
|
|
103
|
+
const { typeMap, defaultMapping, parseRawDefault: rawDefaultParser } = options;
|
|
102
104
|
|
|
103
105
|
const modelNames = buildTopLevelNameMap(
|
|
104
106
|
Object.keys(schemaIR.tables),
|
|
@@ -106,20 +108,15 @@ function buildPslDocumentAst(schemaIR: SqlSchemaIR, options: PslPrinterOptions):
|
|
|
106
108
|
'model',
|
|
107
109
|
'table',
|
|
108
110
|
);
|
|
109
|
-
const enumNames = buildTopLevelNameMap(enumTypeNames, toEnumName, 'enum', 'enum type');
|
|
110
|
-
assertNoCrossKindNameCollisions(modelNames, enumNames);
|
|
111
111
|
|
|
112
112
|
const modelNameMap = new Map(
|
|
113
113
|
[...modelNames].map(([tableName, result]) => [tableName, result.name]),
|
|
114
114
|
);
|
|
115
|
-
const
|
|
116
|
-
[...enumNames].map(([pgTypeName, result]) => [pgTypeName, result.name]),
|
|
117
|
-
);
|
|
118
|
-
const reservedNamedTypeNames = createReservedNamedTypeNames(modelNames, enumNames);
|
|
115
|
+
const reservedNamedTypeNames = createReservedNamedTypeNames(modelNames);
|
|
119
116
|
|
|
120
117
|
const fieldNamesByTable = buildFieldNamesByTable(schemaIR.tables);
|
|
121
118
|
const { relationsByTable } = inferRelations(schemaIR.tables, modelNameMap);
|
|
122
|
-
const namedTypes = seedNamedTypeRegistry(schemaIR, typeMap,
|
|
119
|
+
const namedTypes = seedNamedTypeRegistry(schemaIR, typeMap, new Map(), reservedNamedTypeNames);
|
|
123
120
|
|
|
124
121
|
const models: PslModel[] = [];
|
|
125
122
|
for (const table of Object.values(schemaIR.tables)) {
|
|
@@ -127,7 +124,7 @@ function buildPslDocumentAst(schemaIR: SqlSchemaIR, options: PslPrinterOptions):
|
|
|
127
124
|
buildModel(
|
|
128
125
|
table,
|
|
129
126
|
typeMap,
|
|
130
|
-
|
|
127
|
+
new Map(),
|
|
131
128
|
fieldNamesByTable,
|
|
132
129
|
namedTypes,
|
|
133
130
|
defaultMapping,
|
|
@@ -139,13 +136,6 @@ function buildPslDocumentAst(schemaIR: SqlSchemaIR, options: PslPrinterOptions):
|
|
|
139
136
|
|
|
140
137
|
const sortedModels = topologicalSort(models, schemaIR.tables, modelNameMap);
|
|
141
138
|
|
|
142
|
-
const enums: PslEnum[] = [];
|
|
143
|
-
for (const [pgTypeName, values] of enumDefinitions) {
|
|
144
|
-
const enumName = enumNames.get(pgTypeName) as TopLevelNameResult;
|
|
145
|
-
enums.push(buildEnum(enumName, values));
|
|
146
|
-
}
|
|
147
|
-
enums.sort((a, b) => a.name.localeCompare(b.name));
|
|
148
|
-
|
|
149
139
|
const namedTypeEntries = [...namedTypes.entriesByKey.values()].sort((a, b) =>
|
|
150
140
|
a.name.localeCompare(b.name),
|
|
151
141
|
);
|
|
@@ -171,7 +161,7 @@ function buildPslDocumentAst(schemaIR: SqlSchemaIR, options: PslPrinterOptions):
|
|
|
171
161
|
makePslNamespace({
|
|
172
162
|
kind: 'namespace',
|
|
173
163
|
name: UNSPECIFIED_PSL_NAMESPACE_ID,
|
|
174
|
-
entries: makePslNamespaceEntries(sortedModels,
|
|
164
|
+
entries: makePslNamespaceEntries(sortedModels, [], []),
|
|
175
165
|
span: SYNTHETIC_SPAN,
|
|
176
166
|
}),
|
|
177
167
|
],
|
|
@@ -485,24 +475,6 @@ function namedArg(name: string, value: string): PslAttributeArgument {
|
|
|
485
475
|
return { kind: 'named', name, value, span: SYNTHETIC_SPAN };
|
|
486
476
|
}
|
|
487
477
|
|
|
488
|
-
function buildEnum(name: TopLevelNameResult, values: readonly string[]): PslEnum {
|
|
489
|
-
const attrs: PslAttribute[] = [];
|
|
490
|
-
if (name.map) {
|
|
491
|
-
attrs.push(buildMapAttribute('enum', name.map));
|
|
492
|
-
}
|
|
493
|
-
return {
|
|
494
|
-
kind: 'enum',
|
|
495
|
-
name: name.name,
|
|
496
|
-
values: values.map((value) => ({
|
|
497
|
-
kind: 'enumValue',
|
|
498
|
-
name: value,
|
|
499
|
-
span: SYNTHETIC_SPAN,
|
|
500
|
-
})),
|
|
501
|
-
attributes: attrs,
|
|
502
|
-
span: SYNTHETIC_SPAN,
|
|
503
|
-
};
|
|
504
|
-
}
|
|
505
|
-
|
|
506
478
|
function buildNamedTypeDeclaration(entry: NamedTypeRegistryEntry): PslNamedTypeDeclaration {
|
|
507
479
|
const attribute = buildAttribute(
|
|
508
480
|
'namedType',
|
|
@@ -647,29 +619,8 @@ function buildTopLevelNameMap(
|
|
|
647
619
|
return results;
|
|
648
620
|
}
|
|
649
621
|
|
|
650
|
-
function assertNoCrossKindNameCollisions(
|
|
651
|
-
modelNames: ReadonlyMap<string, TopLevelNameResult>,
|
|
652
|
-
enumNames: ReadonlyMap<string, TopLevelNameResult>,
|
|
653
|
-
): void {
|
|
654
|
-
const enumSourceByName = new Map([...enumNames].map(([source, result]) => [result.name, source]));
|
|
655
|
-
|
|
656
|
-
const collisions = [...modelNames.entries()]
|
|
657
|
-
.map(([tableName, result]) => {
|
|
658
|
-
const enumSource = enumSourceByName.get(result.name);
|
|
659
|
-
return enumSource
|
|
660
|
-
? `- identifier "${result.name}" from table "${tableName}" collides with enum type "${enumSource}"`
|
|
661
|
-
: undefined;
|
|
662
|
-
})
|
|
663
|
-
.filter((detail): detail is string => detail !== undefined);
|
|
664
|
-
|
|
665
|
-
if (collisions.length > 0) {
|
|
666
|
-
throw new Error(`PSL top-level name collisions detected:\n${collisions.join('\n')}`);
|
|
667
|
-
}
|
|
668
|
-
}
|
|
669
|
-
|
|
670
622
|
function createReservedNamedTypeNames(
|
|
671
623
|
modelNames: ReadonlyMap<string, TopLevelNameResult>,
|
|
672
|
-
enumNames: ReadonlyMap<string, TopLevelNameResult>,
|
|
673
624
|
): Set<string> {
|
|
674
625
|
const reservedNames = new Set<string>(PSL_SCALAR_TYPE_NAMES);
|
|
675
626
|
|
|
@@ -677,10 +628,6 @@ function createReservedNamedTypeNames(
|
|
|
677
628
|
reservedNames.add(result.name);
|
|
678
629
|
}
|
|
679
630
|
|
|
680
|
-
for (const result of enumNames.values()) {
|
|
681
|
-
reservedNames.add(result.name);
|
|
682
|
-
}
|
|
683
|
-
|
|
684
631
|
return reservedNames;
|
|
685
632
|
}
|
|
686
633
|
|