@prisma-next/family-sql 0.13.0-dev.28 → 0.13.0-dev.29
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-CN60DEWb.mjs → authoring-type-constructors-CjFfO6LM.mjs} +20 -20
- package/dist/authoring-type-constructors-CjFfO6LM.mjs.map +1 -0
- package/dist/{control-adapter-B_s-UMXg.d.mts → control-adapter-Cmw9LvEP.d.mts} +4 -20
- package/dist/control-adapter-Cmw9LvEP.d.mts.map +1 -0
- package/dist/control-adapter.d.mts +1 -1
- package/dist/control.d.mts +4 -4
- package/dist/control.d.mts.map +1 -1
- package/dist/control.mjs +17 -73
- package/dist/control.mjs.map +1 -1
- package/dist/migration.d.mts +1 -1
- package/dist/pack.d.mts +3 -3
- package/dist/pack.mjs +1 -1
- package/dist/schema-verify.d.mts +1 -1
- package/dist/schema-verify.mjs +1 -1
- package/dist/{types-BR5vHjvX.d.mts → types-kgstZ_Zd.d.mts} +2 -2
- package/dist/{types-BR5vHjvX.d.mts.map → types-kgstZ_Zd.d.mts.map} +1 -1
- 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-CC7EOR2x.mjs → verify-sql-schema-xT4udQLQ.mjs} +10 -117
- package/dist/verify-sql-schema-xT4udQLQ.mjs.map +1 -0
- package/package.json +21 -21
- package/src/core/authoring-entity-types.ts +20 -20
- package/src/core/control-adapter.ts +2 -37
- package/src/core/control-instance.ts +0 -4
- package/src/core/migrations/contract-to-schema-ir.ts +4 -83
- package/src/core/psl-contract-infer/postgres-type-map.ts +5 -22
- package/src/core/psl-contract-infer/sql-schema-ir-to-psl-ast.ts +17 -70
- package/src/core/schema-verify/verify-sql-schema.ts +8 -144
- package/dist/authoring-type-constructors-CN60DEWb.mjs.map +0 -1
- package/dist/control-adapter-B_s-UMXg.d.mts.map +0 -1
- package/dist/verify-sql-schema-CC7EOR2x.mjs.map +0 -1
- package/dist/verify-sql-schema-DcMaT5Zj.d.mts.map +0 -1
|
@@ -1,18 +1,14 @@
|
|
|
1
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
|
/**
|
|
@@ -105,17 +100,7 @@ function convertColumn(
|
|
|
105
100
|
};
|
|
106
101
|
}
|
|
107
102
|
|
|
108
|
-
|
|
109
|
-
* `storageTypes` is polymorphic per Decision 18 (Option B) — codec-typed
|
|
110
|
-
* entries match `StorageTypeInstance`; enum entries match the structural
|
|
111
|
-
* `PostgresEnumStorageEntry` shape (Postgres-only; cross-domain layering
|
|
112
|
-
* keeps target IR classes out of the family layer). Both shapes resolve
|
|
113
|
-
* into the same `(codecId, nativeType, typeParams)` triplet at the
|
|
114
|
-
* column-resolution boundary so downstream walks stay uniform.
|
|
115
|
-
*/
|
|
116
|
-
type ResolvedStorageTypes = Readonly<
|
|
117
|
-
Record<string, StorageTypeInstance | PostgresEnumStorageEntry>
|
|
118
|
-
>;
|
|
103
|
+
type ResolvedStorageTypes = Readonly<Record<string, StorageTypeInstance>>;
|
|
119
104
|
|
|
120
105
|
function resolveColumnTypeMetadata(
|
|
121
106
|
column: StorageColumn,
|
|
@@ -130,13 +115,6 @@ function resolveColumnTypeMetadata(
|
|
|
130
115
|
`Column references storage type "${column.typeRef}" but it is not defined in storage.types.`,
|
|
131
116
|
);
|
|
132
117
|
}
|
|
133
|
-
if (isPostgresEnumStorageEntry(referenced)) {
|
|
134
|
-
return {
|
|
135
|
-
codecId: referenced.codecId,
|
|
136
|
-
nativeType: referenced.nativeType,
|
|
137
|
-
typeParams: { values: referenced.values } as Record<string, unknown>,
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
118
|
if (isStorageTypeInstance(referenced)) {
|
|
141
119
|
return {
|
|
142
120
|
codecId: referenced.codecId,
|
|
@@ -145,7 +123,7 @@ function resolveColumnTypeMetadata(
|
|
|
145
123
|
};
|
|
146
124
|
}
|
|
147
125
|
throw new Error(
|
|
148
|
-
`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.`,
|
|
149
127
|
);
|
|
150
128
|
}
|
|
151
129
|
|
|
@@ -393,21 +371,9 @@ export function contractToSchemaIR(
|
|
|
393
371
|
}
|
|
394
372
|
|
|
395
373
|
const storage = contract.storage;
|
|
396
|
-
const
|
|
374
|
+
const storageTypes: ResolvedStorageTypes = {
|
|
397
375
|
...((storage.types ?? {}) as ResolvedStorageTypes),
|
|
398
376
|
};
|
|
399
|
-
for (const ns of Object.values(storage.namespaces)) {
|
|
400
|
-
const nsEnums = ns.entries['type'];
|
|
401
|
-
if (nsEnums) {
|
|
402
|
-
for (const [k, v] of Object.entries(nsEnums)) {
|
|
403
|
-
allTypes[k] = blindCast<
|
|
404
|
-
PostgresEnumStorageEntry | StorageTypeInstance,
|
|
405
|
-
'entries.type holds postgres-specific enum entries at runtime'
|
|
406
|
-
>(v);
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
const storageTypes = allTypes as ResolvedStorageTypes;
|
|
411
377
|
const tables: Record<string, SqlTableIR> = {};
|
|
412
378
|
for (const ns of Object.values(storage.namespaces)) {
|
|
413
379
|
for (const [tableName, tableDefRaw] of Object.entries(ns.entries.table ?? {})) {
|
|
@@ -445,66 +411,21 @@ export function contractToSchemaIR(
|
|
|
445
411
|
};
|
|
446
412
|
}
|
|
447
413
|
|
|
448
|
-
/**
|
|
449
|
-
* Normalises a native enum storage entry to the codec-typed annotation shape
|
|
450
|
-
* `{codecId, nativeType, typeParams}` the introspector writes and
|
|
451
|
-
* `readExistingEnumValues` reads (`existing.codecId` + `existing.typeParams.values`).
|
|
452
|
-
* Without this the projector would emit the raw `PostgresEnumStorageEntry`
|
|
453
|
-
* shape (top-level `values`, no `typeParams`) and the enum would read as new.
|
|
454
|
-
*/
|
|
455
|
-
function normalizeEnumAnnotation(entry: PostgresEnumStorageEntry): StorageTypeInstance {
|
|
456
|
-
return toStorageTypeInstance({
|
|
457
|
-
codecId: entry.codecId,
|
|
458
|
-
nativeType: entry.nativeType,
|
|
459
|
-
typeParams: { values: entry.values },
|
|
460
|
-
});
|
|
461
|
-
}
|
|
462
|
-
|
|
463
414
|
function deriveAnnotations(
|
|
464
415
|
storage: SqlStorage,
|
|
465
416
|
annotationNamespace: string,
|
|
466
|
-
|
|
417
|
+
_resolveEnumNamespaceSchema: EnumNamespaceSchemaResolver | undefined,
|
|
467
418
|
): SqlAnnotations | undefined {
|
|
468
419
|
const storageTypes: Record<string, StorageTypeInstance> = {};
|
|
469
|
-
const enumTypes: Record<string, Record<string, StorageTypeInstance>> = {};
|
|
470
|
-
|
|
471
|
-
const addEnum = (namespaceId: string, entry: PostgresEnumStorageEntry): void => {
|
|
472
|
-
const schemaName = resolveEnumNamespaceSchema
|
|
473
|
-
? resolveEnumNamespaceSchema(storage, namespaceId)
|
|
474
|
-
: 'public';
|
|
475
|
-
const bySchema = enumTypes[schemaName] ?? {};
|
|
476
|
-
bySchema[entry.nativeType] = normalizeEnumAnnotation(entry);
|
|
477
|
-
enumTypes[schemaName] = bySchema;
|
|
478
|
-
};
|
|
479
420
|
|
|
480
|
-
// Top-level `storage.types`: non-enum codec entries (vector, decimal, …) keyed
|
|
481
|
-
// by bare `nativeType`. Post-S1.B enums live in `namespaces[*].entries.type`;
|
|
482
|
-
// a defensive top-level enum is nested under the unbound coordinate's schema.
|
|
483
421
|
for (const typeInstance of Object.values((storage.types ?? {}) as ResolvedStorageTypes)) {
|
|
484
|
-
if (isPostgresEnumStorageEntry(typeInstance)) {
|
|
485
|
-
addEnum(UNBOUND_NAMESPACE_ID, typeInstance);
|
|
486
|
-
continue;
|
|
487
|
-
}
|
|
488
422
|
if (isStorageTypeInstance(typeInstance)) {
|
|
489
423
|
storageTypes[typeInstance.nativeType] = typeInstance;
|
|
490
424
|
}
|
|
491
425
|
}
|
|
492
426
|
|
|
493
|
-
// Namespace-scoped enums: nested by live schema so two namespaces sharing a
|
|
494
|
-
// native type resolve to distinct live-database types, matching the target's
|
|
495
|
-
// `readExistingEnumValues` read side (`enumTypes[schema][nativeType]`).
|
|
496
|
-
for (const [namespaceId, ns] of Object.entries(storage.namespaces)) {
|
|
497
|
-
const nsEnums = ns.entries['type'];
|
|
498
|
-
if (!nsEnums) continue;
|
|
499
|
-
for (const entry of Object.values(nsEnums)) {
|
|
500
|
-
if (!isPostgresEnumStorageEntry(entry)) continue;
|
|
501
|
-
addEnum(namespaceId, entry);
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
|
|
505
427
|
const envelope = {
|
|
506
428
|
...(Object.keys(storageTypes).length > 0 ? { storageTypes } : {}),
|
|
507
|
-
...(Object.keys(enumTypes).length > 0 ? { enumTypes } : {}),
|
|
508
429
|
};
|
|
509
430
|
if (Object.keys(envelope).length === 0) return undefined;
|
|
510
431
|
return { [annotationNamespace]: envelope };
|
|
@@ -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,30 +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<
|
|
137
|
-
string,
|
|
138
|
-
Record<
|
|
139
|
-
string,
|
|
140
|
-
{ codecId: string; nativeType: string; typeParams?: Record<string, unknown> }
|
|
141
|
-
>
|
|
142
|
-
>
|
|
143
|
-
| undefined;
|
|
133
|
+
const nativeEnumTypeNames = pgAnnotations?.['nativeEnumTypeNames'];
|
|
144
134
|
|
|
145
135
|
const typeNames = new Set<string>();
|
|
146
136
|
const definitions = new Map<string, readonly string[]>();
|
|
147
137
|
|
|
148
|
-
if (
|
|
149
|
-
for (const
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
const nativeType = typeInstance.nativeType;
|
|
153
|
-
typeNames.add(nativeType);
|
|
154
|
-
const values = typeInstance.typeParams?.['values'];
|
|
155
|
-
if (Array.isArray(values) && values.every((v): v is string => typeof v === 'string')) {
|
|
156
|
-
definitions.set(nativeType, values);
|
|
157
|
-
}
|
|
158
|
-
}
|
|
138
|
+
if (Array.isArray(nativeEnumTypeNames)) {
|
|
139
|
+
for (const name of nativeEnumTypeNames) {
|
|
140
|
+
if (typeof name === 'string') {
|
|
141
|
+
typeNames.add(name);
|
|
159
142
|
}
|
|
160
143
|
}
|
|
161
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
|
|
|
@@ -18,9 +18,7 @@ import type {
|
|
|
18
18
|
} from '@prisma-next/framework-components/control';
|
|
19
19
|
|
|
20
20
|
import {
|
|
21
|
-
isPostgresEnumStorageEntry,
|
|
22
21
|
isStorageTypeInstance,
|
|
23
|
-
type PostgresEnumStorageEntry,
|
|
24
22
|
type SqlStorage,
|
|
25
23
|
type StorageColumn,
|
|
26
24
|
StorageTable,
|
|
@@ -28,7 +26,6 @@ import {
|
|
|
28
26
|
} from '@prisma-next/sql-contract/types';
|
|
29
27
|
import type { SqlSchemaIR } from '@prisma-next/sql-schema-ir/types';
|
|
30
28
|
import { canonicalStringify } from '@prisma-next/utils/canonical-stringify';
|
|
31
|
-
import { blindCast } from '@prisma-next/utils/casts';
|
|
32
29
|
import { ifDefined } from '@prisma-next/utils/defined';
|
|
33
30
|
import { extractCodecControlHooks } from '../assembly';
|
|
34
31
|
import { resolveValueSetValues } from '../migrations/contract-to-schema-ir';
|
|
@@ -92,22 +89,6 @@ export interface VerifySqlSchemaOptions {
|
|
|
92
89
|
* with contract native types (e.g., Postgres 'varchar' → 'character varying').
|
|
93
90
|
*/
|
|
94
91
|
readonly normalizeNativeType?: NativeTypeNormalizer;
|
|
95
|
-
/**
|
|
96
|
-
* Bridging adapter that resolves the existing values for a `PostgresEnumStorageEntry`
|
|
97
|
-
* (looked up by its native type) from the introspected schema IR. Targets
|
|
98
|
-
* supply this so the family-level verifier can walk `PostgresEnumStorageEntry` instances
|
|
99
|
-
* natively without reaching into target-specific `schema.annotations`
|
|
100
|
-
* shapes itself.
|
|
101
|
-
*
|
|
102
|
-
* Returning `null` indicates the type is missing from the database; the
|
|
103
|
-
* verifier emits a `type_missing` issue. A non-null array triggers a
|
|
104
|
-
* value-set comparison against the contract's `PostgresEnumStorageEntry.values`.
|
|
105
|
-
*/
|
|
106
|
-
readonly resolveExistingEnumValues?: (
|
|
107
|
-
schema: SqlSchemaIR,
|
|
108
|
-
enumType: PostgresEnumStorageEntry,
|
|
109
|
-
namespaceId: string,
|
|
110
|
-
) => readonly string[] | null;
|
|
111
92
|
}
|
|
112
93
|
|
|
113
94
|
/**
|
|
@@ -129,7 +110,6 @@ export function verifySqlSchema(options: VerifySqlSchemaOptions): VerifyDatabase
|
|
|
129
110
|
typeMetadataRegistry,
|
|
130
111
|
normalizeDefault,
|
|
131
112
|
normalizeNativeType,
|
|
132
|
-
resolveExistingEnumValues,
|
|
133
113
|
} = options;
|
|
134
114
|
const startTime = Date.now();
|
|
135
115
|
|
|
@@ -138,28 +118,7 @@ export function verifySqlSchema(options: VerifySqlSchemaOptions): VerifyDatabase
|
|
|
138
118
|
|
|
139
119
|
const { contractStorageHash, contractProfileHash, contractTarget } =
|
|
140
120
|
extractContractMetadata(contract);
|
|
141
|
-
|
|
142
|
-
// (columns carry bare `typeRef`s). Used by `verifySchemaTables` only.
|
|
143
|
-
const allStorageTypesMap: Record<string, PostgresEnumStorageEntry | StorageTypeInstance> = {
|
|
144
|
-
...((contract.storage.types ?? {}) as Record<
|
|
145
|
-
string,
|
|
146
|
-
PostgresEnumStorageEntry | StorageTypeInstance
|
|
147
|
-
>),
|
|
148
|
-
};
|
|
149
|
-
for (const ns of Object.values(contract.storage.namespaces)) {
|
|
150
|
-
const nsEnums = blindCast<
|
|
151
|
-
{ readonly type?: Readonly<Record<string, PostgresEnumStorageEntry | StorageTypeInstance>> },
|
|
152
|
-
'postgres target namespace entries carry a type slot beyond the family-shared SqlNamespace.entries type'
|
|
153
|
-
>(ns.entries).type;
|
|
154
|
-
if (nsEnums) {
|
|
155
|
-
for (const [k, v] of Object.entries(nsEnums)) {
|
|
156
|
-
allStorageTypesMap[k] = v;
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
const storageTypes = allStorageTypesMap as Readonly<
|
|
161
|
-
Record<string, PostgresEnumStorageEntry | StorageTypeInstance>
|
|
162
|
-
>;
|
|
121
|
+
const storageTypes: Readonly<Record<string, StorageTypeInstance>> = contract.storage.types ?? {};
|
|
163
122
|
const { issues, rootChildren } = verifySchemaTables({
|
|
164
123
|
contract,
|
|
165
124
|
schema,
|
|
@@ -173,13 +132,6 @@ export function verifySqlSchema(options: VerifySqlSchemaOptions): VerifyDatabase
|
|
|
173
132
|
|
|
174
133
|
validateFrameworkComponentsForExtensions(contract, options.frameworkComponents);
|
|
175
134
|
|
|
176
|
-
// Verify storage type instances. Codec-typed `storage.types` entries dispatch
|
|
177
|
-
// through the generic codec-hook `verifyType` path (keyed by bare name).
|
|
178
|
-
// `PostgresEnumStorageEntry` enums are walked natively *per namespace* (using
|
|
179
|
-
// the bridging adapter `resolveExistingEnumValues`) so two namespaces that
|
|
180
|
-
// declare an enum with the same name are each verified with their own
|
|
181
|
-
// namespace coordinate — a bare-name aggregation would collapse them
|
|
182
|
-
// (last-write-wins) and verify only one.
|
|
183
135
|
const typeNodes: SchemaVerificationNode[] = [];
|
|
184
136
|
// Storage-type findings dispatch through the same control policy as tables
|
|
185
137
|
// and columns: each issue's disposition (fail / warn / suppress) is resolved
|
|
@@ -233,29 +185,6 @@ export function verifySqlSchema(options: VerifySqlSchemaOptions): VerifyDatabase
|
|
|
233
185
|
}
|
|
234
186
|
}
|
|
235
187
|
|
|
236
|
-
// Namespace-scoped enums, verified per `(namespaceId, typeName)`.
|
|
237
|
-
for (const nsId of Object.keys(contract.storage.namespaces)) {
|
|
238
|
-
const ns = contract.storage.namespaces[nsId];
|
|
239
|
-
if (!ns) continue;
|
|
240
|
-
const nsEnums = ns.entries['type'];
|
|
241
|
-
if (!nsEnums) continue;
|
|
242
|
-
for (const [typeName, entry] of Object.entries(nsEnums)) {
|
|
243
|
-
if (!isPostgresEnumStorageEntry(entry)) continue;
|
|
244
|
-
pushTypeNode(
|
|
245
|
-
typeName,
|
|
246
|
-
`storage.namespaces.${nsId}.entries.type.${typeName}`,
|
|
247
|
-
verifyEnumType({
|
|
248
|
-
typeName,
|
|
249
|
-
typeInstance: entry,
|
|
250
|
-
schema,
|
|
251
|
-
resolveExistingEnumValues,
|
|
252
|
-
namespaceId: nsId,
|
|
253
|
-
}),
|
|
254
|
-
effectiveControlPolicy(entry.control, contract.defaultControlPolicy),
|
|
255
|
-
);
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
|
|
259
188
|
if (typeNodes.length > 0) {
|
|
260
189
|
const typesStatus: VerificationStatus = typeNodes.some((n) => n.status === 'fail')
|
|
261
190
|
? 'fail'
|
|
@@ -321,63 +250,6 @@ export function verifySqlSchema(options: VerifySqlSchemaOptions): VerifyDatabase
|
|
|
321
250
|
};
|
|
322
251
|
}
|
|
323
252
|
|
|
324
|
-
/**
|
|
325
|
-
* Native verification walk for `PostgresEnumStorageEntry` instances (no codec hook).
|
|
326
|
-
*
|
|
327
|
-
* Bridges the native `PostgresEnumStorageEntry.values` against the introspected schema
|
|
328
|
-
* IR via the target-supplied `resolveExistingEnumValues` adapter. Without an
|
|
329
|
-
* adapter, the verifier conservatively reports the enum as missing — there
|
|
330
|
-
* is no other way for the family layer to learn about live enum types.
|
|
331
|
-
*/
|
|
332
|
-
function verifyEnumType(options: {
|
|
333
|
-
readonly typeName: string;
|
|
334
|
-
readonly typeInstance: PostgresEnumStorageEntry;
|
|
335
|
-
readonly schema: SqlSchemaIR;
|
|
336
|
-
readonly namespaceId: string;
|
|
337
|
-
readonly resolveExistingEnumValues?:
|
|
338
|
-
| ((
|
|
339
|
-
schema: SqlSchemaIR,
|
|
340
|
-
enumType: PostgresEnumStorageEntry,
|
|
341
|
-
namespaceId: string,
|
|
342
|
-
) => readonly string[] | null)
|
|
343
|
-
| undefined;
|
|
344
|
-
}): readonly SchemaIssue[] {
|
|
345
|
-
const { typeName, typeInstance, schema, namespaceId, resolveExistingEnumValues } = options;
|
|
346
|
-
const desired = typeInstance.values;
|
|
347
|
-
const existing = resolveExistingEnumValues?.(schema, typeInstance, namespaceId) ?? null;
|
|
348
|
-
if (!existing) {
|
|
349
|
-
return [
|
|
350
|
-
{
|
|
351
|
-
kind: 'type_missing',
|
|
352
|
-
typeName,
|
|
353
|
-
namespaceId,
|
|
354
|
-
message: `Type "${typeName}" is missing from database`,
|
|
355
|
-
},
|
|
356
|
-
];
|
|
357
|
-
}
|
|
358
|
-
if (arraysEqual(existing, desired)) {
|
|
359
|
-
return [];
|
|
360
|
-
}
|
|
361
|
-
const existingSet = new Set(existing);
|
|
362
|
-
const desiredSet = new Set(desired);
|
|
363
|
-
const addedValues = desired.filter((v) => !existingSet.has(v));
|
|
364
|
-
const removedValues = existing.filter((v) => !desiredSet.has(v));
|
|
365
|
-
const message =
|
|
366
|
-
removedValues.length === 0
|
|
367
|
-
? `Enum type "${typeName}" needs new values: ${addedValues.join(', ')}`
|
|
368
|
-
: `Enum type "${typeName}" values changed (requires rebuild): +[${addedValues.join(', ')}] -[${removedValues.join(', ')}]`;
|
|
369
|
-
return [
|
|
370
|
-
{
|
|
371
|
-
kind: 'enum_values_changed' as const,
|
|
372
|
-
namespaceId,
|
|
373
|
-
typeName,
|
|
374
|
-
addedValues,
|
|
375
|
-
removedValues,
|
|
376
|
-
message,
|
|
377
|
-
},
|
|
378
|
-
];
|
|
379
|
-
}
|
|
380
|
-
|
|
381
253
|
function extractContractMetadata(contract: Contract<SqlStorage>): {
|
|
382
254
|
contractStorageHash: SqlStorage['storageHash'];
|
|
383
255
|
contractProfileHash?: Contract<SqlStorage>['profileHash'] | undefined;
|
|
@@ -399,7 +271,7 @@ function verifySchemaTables(options: {
|
|
|
399
271
|
strict: boolean;
|
|
400
272
|
typeMetadataRegistry: ReadonlyMap<string, { nativeType?: string }>;
|
|
401
273
|
codecHooks: Map<string, CodecControlHooks>;
|
|
402
|
-
storageTypes: Readonly<Record<string, StorageTypeInstance
|
|
274
|
+
storageTypes: Readonly<Record<string, StorageTypeInstance>>;
|
|
403
275
|
normalizeDefault?: DefaultNormalizer;
|
|
404
276
|
normalizeNativeType?: NativeTypeNormalizer;
|
|
405
277
|
}): { issues: SchemaIssue[]; rootChildren: SchemaVerificationNode[] } {
|
|
@@ -533,7 +405,7 @@ function verifyTableChildren(options: {
|
|
|
533
405
|
strict: boolean;
|
|
534
406
|
typeMetadataRegistry: ReadonlyMap<string, { nativeType?: string }>;
|
|
535
407
|
codecHooks: Map<string, CodecControlHooks>;
|
|
536
|
-
storageTypes: Readonly<Record<string, StorageTypeInstance
|
|
408
|
+
storageTypes: Readonly<Record<string, StorageTypeInstance>>;
|
|
537
409
|
normalizeDefault?: DefaultNormalizer;
|
|
538
410
|
normalizeNativeType?: NativeTypeNormalizer;
|
|
539
411
|
contractStorage: SqlStorage;
|
|
@@ -717,7 +589,6 @@ function verifyTableChildren(options: {
|
|
|
717
589
|
// Verify check constraints when the contract declares checks for this table OR
|
|
718
590
|
// when strict mode is on (so extra live checks on zero-check tables are detected).
|
|
719
591
|
// schemaTable.checks carries the introspected live checks (parsed value sets).
|
|
720
|
-
// This call is additive: verifyEnumType (the native enum path) is untouched.
|
|
721
592
|
const contractCheckIRs = (contractTable.checks ?? []).map((c) => ({
|
|
722
593
|
name: c.name,
|
|
723
594
|
column: c.column,
|
|
@@ -751,7 +622,7 @@ function collectContractColumnNodes(options: {
|
|
|
751
622
|
strict: boolean;
|
|
752
623
|
typeMetadataRegistry: ReadonlyMap<string, { nativeType?: string }>;
|
|
753
624
|
codecHooks: Map<string, CodecControlHooks>;
|
|
754
|
-
storageTypes: Readonly<Record<string, StorageTypeInstance
|
|
625
|
+
storageTypes: Readonly<Record<string, StorageTypeInstance>>;
|
|
755
626
|
normalizeDefault?: DefaultNormalizer;
|
|
756
627
|
normalizeNativeType?: NativeTypeNormalizer;
|
|
757
628
|
}): SchemaVerificationNode[] {
|
|
@@ -889,7 +760,7 @@ function verifyColumn(options: {
|
|
|
889
760
|
strict: boolean;
|
|
890
761
|
typeMetadataRegistry: ReadonlyMap<string, { nativeType?: string }>;
|
|
891
762
|
codecHooks: Map<string, CodecControlHooks>;
|
|
892
|
-
storageTypes: Readonly<Record<string, StorageTypeInstance
|
|
763
|
+
storageTypes: Readonly<Record<string, StorageTypeInstance>>;
|
|
893
764
|
normalizeDefault?: DefaultNormalizer;
|
|
894
765
|
normalizeNativeType?: NativeTypeNormalizer;
|
|
895
766
|
}): SchemaVerificationNode {
|
|
@@ -1260,7 +1131,7 @@ function validateFrameworkComponentsForExtensions(
|
|
|
1260
1131
|
*/
|
|
1261
1132
|
function renderExpectedNativeType(
|
|
1262
1133
|
contractColumn: StorageColumn,
|
|
1263
|
-
storageTypes: Readonly<Record<string, StorageTypeInstance
|
|
1134
|
+
storageTypes: Readonly<Record<string, StorageTypeInstance>>,
|
|
1264
1135
|
codecHooks: Map<string, CodecControlHooks>,
|
|
1265
1136
|
context?: {
|
|
1266
1137
|
readonly tableName: string;
|
|
@@ -1290,7 +1161,7 @@ function renderExpectedNativeType(
|
|
|
1290
1161
|
|
|
1291
1162
|
function resolveContractColumnTypeMetadata(
|
|
1292
1163
|
contractColumn: StorageColumn,
|
|
1293
|
-
storageTypes: Readonly<Record<string, StorageTypeInstance
|
|
1164
|
+
storageTypes: Readonly<Record<string, StorageTypeInstance>>,
|
|
1294
1165
|
context?: {
|
|
1295
1166
|
readonly tableName: string;
|
|
1296
1167
|
readonly columnName: string;
|
|
@@ -1310,13 +1181,6 @@ function resolveContractColumnTypeMetadata(
|
|
|
1310
1181
|
);
|
|
1311
1182
|
}
|
|
1312
1183
|
|
|
1313
|
-
if (isPostgresEnumStorageEntry(referencedType)) {
|
|
1314
|
-
return {
|
|
1315
|
-
codecId: referencedType.codecId,
|
|
1316
|
-
nativeType: referencedType.nativeType,
|
|
1317
|
-
typeParams: { values: referencedType.values } as Record<string, unknown>,
|
|
1318
|
-
};
|
|
1319
|
-
}
|
|
1320
1184
|
if (isStorageTypeInstance(referencedType)) {
|
|
1321
1185
|
return {
|
|
1322
1186
|
codecId: referencedType.codecId,
|
|
@@ -1325,7 +1189,7 @@ function resolveContractColumnTypeMetadata(
|
|
|
1325
1189
|
};
|
|
1326
1190
|
}
|
|
1327
1191
|
throw new Error(
|
|
1328
|
-
`Storage type "${contractColumn.typeRef}" has an unknown
|
|
1192
|
+
`Storage type "${contractColumn.typeRef}" has an unknown kind; expected a codec-typed StorageTypeInstance.`,
|
|
1329
1193
|
);
|
|
1330
1194
|
}
|
|
1331
1195
|
|