@prisma-next/target-postgres 0.13.0-dev.27 → 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/{codec-ids-C_-Hj6bL.mjs → codec-ids-BvytN2P8.mjs} +2 -3
- package/dist/codec-ids-BvytN2P8.mjs.map +1 -0
- package/dist/{codec-ids-BzrFF-I4.d.mts → codec-ids-CnXu9Qy3.d.mts} +2 -3
- package/dist/codec-ids-CnXu9Qy3.d.mts.map +1 -0
- package/dist/codec-ids.d.mts +2 -2
- package/dist/codec-ids.mjs +2 -2
- package/dist/{codec-types-B0WT0obB.d.mts → codec-types-DHCkwPKE.d.mts} +2 -3
- package/dist/codec-types-DHCkwPKE.d.mts.map +1 -0
- package/dist/codec-types.d.mts +1 -1
- package/dist/{codecs-CX56Smsj.d.mts → codecs--0A5_4Bq.d.mts} +3 -23
- package/dist/codecs--0A5_4Bq.d.mts.map +1 -0
- package/dist/codecs.d.mts +2 -2
- package/dist/codecs.mjs +2 -38
- package/dist/codecs.mjs.map +1 -1
- package/dist/contract-free.mjs +1 -1
- package/dist/control.d.mts.map +1 -1
- package/dist/control.mjs +7 -10
- package/dist/control.mjs.map +1 -1
- package/dist/{descriptor-meta-C7O6XHfh.mjs → descriptor-meta-BKma_hQ5.mjs} +2 -2
- package/dist/{descriptor-meta-C7O6XHfh.mjs.map → descriptor-meta-BKma_hQ5.mjs.map} +1 -1
- package/dist/{descriptor-meta-runtime-BToWdas9.mjs → descriptor-meta-runtime-e5f2tscJ.mjs} +2 -39
- package/dist/descriptor-meta-runtime-e5f2tscJ.mjs.map +1 -0
- package/dist/{issue-planner-CK-XWGB0.mjs → issue-planner-DsjB7xDj.mjs} +17 -227
- package/dist/issue-planner-DsjB7xDj.mjs.map +1 -0
- package/dist/issue-planner.d.mts +8 -11
- package/dist/issue-planner.d.mts.map +1 -1
- package/dist/issue-planner.mjs +1 -1
- package/dist/migration.d.mts +1 -11
- package/dist/migration.d.mts.map +1 -1
- package/dist/migration.mjs +3 -3
- package/dist/{op-factory-call-DxjpXw-A.d.mts → op-factory-call-CdtMyrlU.d.mts} +3 -48
- package/dist/{op-factory-call-DxjpXw-A.d.mts.map → op-factory-call-CdtMyrlU.d.mts.map} +1 -1
- package/dist/{op-factory-call-CHvtj70z.mjs → op-factory-call-CjR846f7.mjs} +4 -159
- package/dist/op-factory-call-CjR846f7.mjs.map +1 -0
- package/dist/op-factory-call.d.mts +2 -2
- package/dist/op-factory-call.mjs +2 -2
- package/dist/pack.d.mts +2 -18
- package/dist/pack.d.mts.map +1 -1
- package/dist/pack.mjs +1 -1
- package/dist/{planner-4FbY_95H.mjs → planner-_FOL4I21.mjs} +9 -41
- package/dist/planner-_FOL4I21.mjs.map +1 -0
- package/dist/{planner-ddl-builders-Cw2n2llW.mjs → planner-ddl-builders-B2wOwLqI.mjs} +2 -2
- package/dist/planner-ddl-builders-B2wOwLqI.mjs.map +1 -0
- package/dist/planner-ddl-builders.d.mts +4 -4
- package/dist/planner-ddl-builders.d.mts.map +1 -1
- package/dist/planner-ddl-builders.mjs +1 -1
- package/dist/{planner-identity-values-BIpa5p2I.mjs → planner-identity-values-CJPha2Sz.mjs} +3 -9
- package/dist/planner-identity-values-CJPha2Sz.mjs.map +1 -0
- package/dist/planner-identity-values.d.mts +1 -1
- package/dist/planner-identity-values.d.mts.map +1 -1
- package/dist/planner-identity-values.mjs +1 -1
- package/dist/{planner-produced-postgres-migration-qfkCkGVe.mjs → planner-produced-postgres-migration-BmCpyWLJ.mjs} +2 -2
- package/dist/{planner-produced-postgres-migration-qfkCkGVe.mjs.map → planner-produced-postgres-migration-BmCpyWLJ.mjs.map} +1 -1
- package/dist/planner-produced-postgres-migration.mjs +1 -1
- package/dist/{planner-sql-checks-BLgdXLsA.mjs → planner-sql-checks-CJJtPfDH.mjs} +3 -3
- package/dist/planner-sql-checks-CJJtPfDH.mjs.map +1 -0
- package/dist/planner-sql-checks.d.mts +2 -2
- package/dist/planner-sql-checks.d.mts.map +1 -1
- package/dist/planner-sql-checks.mjs +1 -1
- package/dist/{planner-type-resolution-836DExFN.mjs → planner-type-resolution-Bt2f_q-F.mjs} +1 -6
- package/dist/planner-type-resolution-Bt2f_q-F.mjs.map +1 -0
- package/dist/planner.d.mts.map +1 -1
- package/dist/planner.mjs +1 -1
- package/dist/{postgres-contract-serializer-CTxVcCVW.mjs → postgres-contract-serializer-CyAe8ZFv.mjs} +12 -29
- package/dist/postgres-contract-serializer-CyAe8ZFv.mjs.map +1 -0
- package/dist/{postgres-migration-DF5ApLqQ.mjs → postgres-migration-dG-J0aI8.mjs} +2 -2
- package/dist/{postgres-migration-DF5ApLqQ.mjs.map → postgres-migration-dG-J0aI8.mjs.map} +1 -1
- package/dist/{postgres-schema-C7c9rhGk.mjs → postgres-schema-CTKYiTHu.mjs} +7 -21
- package/dist/postgres-schema-CTKYiTHu.mjs.map +1 -0
- package/dist/runtime.d.mts +0 -2
- package/dist/runtime.d.mts.map +1 -1
- package/dist/runtime.mjs +2 -2
- package/dist/types.d.mts +5 -25
- package/dist/types.d.mts.map +1 -1
- package/dist/types.mjs +2 -4
- package/package.json +17 -18
- package/src/core/authoring.ts +1 -44
- package/src/core/codec-helpers.ts +0 -17
- package/src/core/codec-ids.ts +0 -1
- package/src/core/codec-type-map.ts +0 -2
- package/src/core/codecs.ts +0 -49
- package/src/core/migrations/control-policy.ts +3 -45
- package/src/core/migrations/issue-planner.ts +9 -52
- package/src/core/migrations/op-factory-call.ts +2 -124
- package/src/core/migrations/planner-ddl-builders.ts +3 -4
- package/src/core/migrations/planner-identity-values.ts +4 -28
- package/src/core/migrations/planner-recipes.ts +2 -6
- package/src/core/migrations/planner-sql-checks.ts +2 -6
- package/src/core/migrations/planner-strategies.ts +13 -353
- package/src/core/migrations/planner-type-resolution.ts +2 -20
- package/src/core/migrations/planner.ts +0 -2
- package/src/core/migrations/runner.ts +0 -4
- package/src/core/postgres-contract-serializer.ts +9 -67
- package/src/core/postgres-schema.ts +6 -37
- package/src/exports/codecs.ts +0 -2
- package/src/exports/control.ts +0 -15
- package/src/exports/migration.ts +0 -6
- package/src/exports/op-factory-call.ts +0 -4
- package/src/exports/types.ts +0 -2
- package/dist/codec-ids-BzrFF-I4.d.mts.map +0 -1
- package/dist/codec-ids-C_-Hj6bL.mjs.map +0 -1
- package/dist/codec-types-B0WT0obB.d.mts.map +0 -1
- package/dist/codecs-CX56Smsj.d.mts.map +0 -1
- package/dist/descriptor-meta-runtime-BToWdas9.mjs.map +0 -1
- package/dist/enum-planning-D8z4FH7y.mjs +0 -129
- package/dist/enum-planning-D8z4FH7y.mjs.map +0 -1
- package/dist/enum-planning.d.mts +0 -92
- package/dist/enum-planning.d.mts.map +0 -1
- package/dist/enum-planning.mjs +0 -2
- package/dist/issue-planner-CK-XWGB0.mjs.map +0 -1
- package/dist/op-factory-call-CHvtj70z.mjs.map +0 -1
- package/dist/planner-4FbY_95H.mjs.map +0 -1
- package/dist/planner-ddl-builders-Cw2n2llW.mjs.map +0 -1
- package/dist/planner-identity-values-BIpa5p2I.mjs.map +0 -1
- package/dist/planner-sql-checks-BLgdXLsA.mjs.map +0 -1
- package/dist/planner-type-resolution-836DExFN.mjs.map +0 -1
- package/dist/postgres-contract-serializer-CTxVcCVW.mjs.map +0 -1
- package/dist/postgres-enum-type-BVn63a89.d.mts +0 -72
- package/dist/postgres-enum-type-BVn63a89.d.mts.map +0 -1
- package/dist/postgres-enum-type-DPKqCBem.mjs +0 -62
- package/dist/postgres-enum-type-DPKqCBem.mjs.map +0 -1
- package/dist/postgres-enum-type-schema-DZBTtvBF.mjs +0 -20
- package/dist/postgres-enum-type-schema-DZBTtvBF.mjs.map +0 -1
- package/dist/postgres-schema-C7c9rhGk.mjs.map +0 -1
- package/src/core/migrations/enum-planning.ts +0 -217
- package/src/core/migrations/operations/enums.ts +0 -114
- package/src/core/postgres-enum-type.ts +0 -89
- package/src/exports/enum-planning.ts +0 -10
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* two journeys differ only in `policy.allowedOperationClasses`:
|
|
12
12
|
*
|
|
13
13
|
* - When `'data'` is in the policy, data-safe strategies (NOT NULL backfill,
|
|
14
|
-
* nullability tightening, unsafe type changes
|
|
14
|
+
* nullability tightening, unsafe type changes) emit
|
|
15
15
|
* `DataTransformCall` placeholders that the user fills in.
|
|
16
16
|
* - When `'data'` is excluded, those strategies short-circuit so the
|
|
17
17
|
* downstream walk-schema strategies (codec-hook type ops and temp-default
|
|
@@ -29,8 +29,6 @@ import type { TargetBoundComponentDescriptor } from '@prisma-next/framework-comp
|
|
|
29
29
|
import type { SchemaIssue } from '@prisma-next/framework-components/control';
|
|
30
30
|
import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir';
|
|
31
31
|
import {
|
|
32
|
-
isPostgresEnumStorageEntry,
|
|
33
|
-
type PostgresEnumStorageEntry,
|
|
34
32
|
type SqlStorage,
|
|
35
33
|
StorageTable,
|
|
36
34
|
type StorageTypeInstance,
|
|
@@ -41,26 +39,16 @@ import type { SqlSchemaIR } from '@prisma-next/sql-schema-ir/types';
|
|
|
41
39
|
import { blindCast } from '@prisma-next/utils/casts';
|
|
42
40
|
import { ifDefined } from '@prisma-next/utils/defined';
|
|
43
41
|
import type { JsonValue } from '@prisma-next/utils/json';
|
|
44
|
-
import { PostgresEnumType } from '../postgres-enum-type';
|
|
45
42
|
import { isPostgresSchema } from '../postgres-schema';
|
|
46
|
-
import {
|
|
47
|
-
determineEnumDiff,
|
|
48
|
-
readExistingEnumValues,
|
|
49
|
-
resolveDdlSchemaForNamespaceStorage,
|
|
50
|
-
} from './enum-planning';
|
|
51
43
|
import {
|
|
52
44
|
AddCheckConstraintCall,
|
|
53
45
|
AddColumnCall,
|
|
54
|
-
AddEnumValuesCall,
|
|
55
46
|
AlterColumnTypeCall,
|
|
56
|
-
CreateEnumTypeCall,
|
|
57
47
|
DataTransformCall,
|
|
58
48
|
DropCheckConstraintCall,
|
|
59
|
-
DropEnumTypeCall,
|
|
60
49
|
type PostgresOpFactoryCall,
|
|
61
50
|
postgresDefaultToDdlColumnDefault,
|
|
62
51
|
RawSqlCall,
|
|
63
|
-
RenameTypeCall,
|
|
64
52
|
SetNotNullCall,
|
|
65
53
|
} from './op-factory-call';
|
|
66
54
|
import { buildAddColumnSql, buildColumnTypeSql } from './planner-ddl-builders';
|
|
@@ -80,8 +68,6 @@ import {
|
|
|
80
68
|
import { buildTargetDetails, type PostgresPlanTargetDetails } from './planner-target-details';
|
|
81
69
|
import { resolveColumnTypeMetadata } from './planner-type-resolution';
|
|
82
70
|
|
|
83
|
-
const REBUILD_SUFFIX = '__prisma_next_new';
|
|
84
|
-
|
|
85
71
|
/**
|
|
86
72
|
* Look up a storage table by its explicit namespace coordinate. Returns
|
|
87
73
|
* `undefined` when the namespace has no table by that name (or no such
|
|
@@ -133,63 +119,6 @@ export function resolveDdlSchemaForNamespace(ctx: StrategyContext, namespaceId:
|
|
|
133
119
|
return namespaceId;
|
|
134
120
|
}
|
|
135
121
|
|
|
136
|
-
/** Default Postgres enum landing namespace — where contract-level (`types:`)
|
|
137
|
-
* enums are placed by the authoring builder when no explicit namespace is
|
|
138
|
-
* given. Mirrors `POSTGRES_ENUM_NAMESPACE_ID` in the contract-ts builder. */
|
|
139
|
-
const DEFAULT_ENUM_NAMESPACE_ID = 'public';
|
|
140
|
-
|
|
141
|
-
function namespaceHasEnum(storage: SqlStorage, namespaceId: string, typeName: string): boolean {
|
|
142
|
-
const ns = storage.namespaces[namespaceId];
|
|
143
|
-
if (!isPostgresSchema(ns)) return false;
|
|
144
|
-
return ns.type[typeName] !== undefined;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* Resolves which namespace's enum a column's bare `typeRef` binds to.
|
|
149
|
-
*
|
|
150
|
-
* Columns carry a bare (non-namespace-qualified) `typeRef`; the enum it names
|
|
151
|
-
* may live in a different namespace than the column's own (the authoring
|
|
152
|
-
* builder places contract-level `types:` enums in the default `public`
|
|
153
|
-
* namespace while a model's table may sit in the unbound namespace). The
|
|
154
|
-
* binding rule: an enum declared in the column's *own* namespace shadows
|
|
155
|
-
* everything; otherwise the column references the ambient enum — the sole
|
|
156
|
-
* namespace that defines `typeName`, preferring the default `public`
|
|
157
|
-
* namespace when several do. Returns `undefined` when no namespace defines it.
|
|
158
|
-
*/
|
|
159
|
-
function resolveColumnEnumNamespace(
|
|
160
|
-
storage: SqlStorage,
|
|
161
|
-
columnNamespaceId: string,
|
|
162
|
-
typeName: string,
|
|
163
|
-
): string | undefined {
|
|
164
|
-
if (namespaceHasEnum(storage, columnNamespaceId, typeName)) return columnNamespaceId;
|
|
165
|
-
const owners = Object.keys(storage.namespaces).filter((nsId) =>
|
|
166
|
-
namespaceHasEnum(storage, nsId, typeName),
|
|
167
|
-
);
|
|
168
|
-
if (owners.length === 1) return owners[0];
|
|
169
|
-
if (owners.includes(DEFAULT_ENUM_NAMESPACE_ID)) return DEFAULT_ENUM_NAMESPACE_ID;
|
|
170
|
-
return owners[0];
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* Finds a type entry by explicit namespace coordinate. Namespace types (e.g.
|
|
175
|
-
* Postgres enums) live under `storage.namespaces[nsId].entries.type`. Returns the
|
|
176
|
-
* entry from the named namespace only — never scans other namespaces, so two
|
|
177
|
-
* namespaces that hold an enum with the same name resolve independently.
|
|
178
|
-
*/
|
|
179
|
-
function locateNamespaceType(
|
|
180
|
-
storage: SqlStorage,
|
|
181
|
-
namespaceId: string,
|
|
182
|
-
typeName: string,
|
|
183
|
-
): PostgresEnumStorageEntry | undefined {
|
|
184
|
-
const ns = storage.namespaces[namespaceId];
|
|
185
|
-
const raw = ns?.entries['type']?.[typeName];
|
|
186
|
-
if (raw === undefined) return undefined;
|
|
187
|
-
return blindCast<
|
|
188
|
-
PostgresEnumStorageEntry,
|
|
189
|
-
'postgres type slot carries PostgresEnumStorageEntry at the postgres target layer'
|
|
190
|
-
>(raw);
|
|
191
|
-
}
|
|
192
|
-
|
|
193
122
|
// ============================================================================
|
|
194
123
|
// Strategy types
|
|
195
124
|
// ============================================================================
|
|
@@ -208,7 +137,7 @@ export interface StrategyContext {
|
|
|
208
137
|
readonly fromContract: Contract<SqlStorage> | null;
|
|
209
138
|
readonly schemaName: string;
|
|
210
139
|
readonly codecHooks: ReadonlyMap<string, CodecControlHooks>;
|
|
211
|
-
readonly storageTypes: Readonly<Record<string, StorageTypeInstance
|
|
140
|
+
readonly storageTypes: Readonly<Record<string, StorageTypeInstance>>;
|
|
212
141
|
readonly schema: SqlSchemaIR;
|
|
213
142
|
readonly policy: MigrationOperationPolicy;
|
|
214
143
|
readonly frameworkComponents: ReadonlyArray<TargetBoundComponentDescriptor<'sql', string>>;
|
|
@@ -229,12 +158,9 @@ export type CallMigrationStrategy = (
|
|
|
229
158
|
/**
|
|
230
159
|
* `true` for strategies that emit cohesive sequential recipes whose
|
|
231
160
|
* calls must stay contiguous and in the returned order — e.g.
|
|
232
|
-
* `
|
|
233
|
-
*
|
|
234
|
-
*
|
|
235
|
-
* (addColumn → dataTransform → setNotNull). Defaults to `false`,
|
|
236
|
-
* which lets `planIssues` hoist individual calls into their DDL
|
|
237
|
-
* sequencing bucket.
|
|
161
|
+
* `notNullBackfillCallStrategy` (addColumn → dataTransform → setNotNull).
|
|
162
|
+
* Defaults to `false`, which lets `planIssues` hoist individual calls
|
|
163
|
+
* into their DDL sequencing bucket.
|
|
238
164
|
*/
|
|
239
165
|
recipe?: boolean;
|
|
240
166
|
}
|
|
@@ -251,10 +177,7 @@ function buildColumnSpec(
|
|
|
251
177
|
if (!storageCol)
|
|
252
178
|
throw new Error(`Column "${table}"."${column}" not found in destination contract`);
|
|
253
179
|
const mutableHooks = ctx.codecHooks as Map<string, CodecControlHooks>;
|
|
254
|
-
const mutableTypes = ctx.storageTypes as Record<
|
|
255
|
-
string,
|
|
256
|
-
StorageTypeInstance | PostgresEnumStorageEntry
|
|
257
|
-
>;
|
|
180
|
+
const mutableTypes = ctx.storageTypes as Record<string, StorageTypeInstance>;
|
|
258
181
|
const typeSql = buildColumnTypeSql(storageCol, mutableHooks, mutableTypes);
|
|
259
182
|
const ddlDefault = postgresDefaultToDdlColumnDefault(storageCol.default);
|
|
260
183
|
const resolved = resolveColumnTypeMetadata(storageCol, mutableTypes);
|
|
@@ -289,10 +212,7 @@ function buildAlterTypeOptions(
|
|
|
289
212
|
const col = tableAt(ctx.toContract.storage, namespaceId, table)?.columns[column];
|
|
290
213
|
if (!col) throw new Error(`Column "${table}"."${column}" not found in destination contract`);
|
|
291
214
|
const mutableHooks = ctx.codecHooks as Map<string, CodecControlHooks>;
|
|
292
|
-
const mutableTypes = ctx.storageTypes as Record<
|
|
293
|
-
string,
|
|
294
|
-
StorageTypeInstance | PostgresEnumStorageEntry
|
|
295
|
-
>;
|
|
215
|
+
const mutableTypes = ctx.storageTypes as Record<string, StorageTypeInstance>;
|
|
296
216
|
const qualifiedTargetType = buildColumnTypeSql(col, mutableHooks, mutableTypes, false);
|
|
297
217
|
const formatTypeExpected = buildExpectedFormatType(col, mutableHooks, mutableTypes);
|
|
298
218
|
return {
|
|
@@ -435,246 +355,6 @@ export const nullableTighteningCallStrategy: CallMigrationStrategy = (issues, ct
|
|
|
435
355
|
};
|
|
436
356
|
};
|
|
437
357
|
|
|
438
|
-
function enumRebuildCallRecipe(
|
|
439
|
-
namespaceId: string,
|
|
440
|
-
typeName: string,
|
|
441
|
-
ctx: StrategyContext,
|
|
442
|
-
): readonly PostgresOpFactoryCall[] {
|
|
443
|
-
const toType = locateNamespaceType(ctx.toContract.storage, namespaceId, typeName);
|
|
444
|
-
if (!toType) return [];
|
|
445
|
-
const isEnum = isPostgresEnumStorageEntry(toType);
|
|
446
|
-
const nativeType = toType.nativeType;
|
|
447
|
-
const desiredValues: readonly string[] = isEnum
|
|
448
|
-
? toType.values
|
|
449
|
-
: (((toType as StorageTypeInstance).typeParams['values'] ?? []) as readonly string[]);
|
|
450
|
-
const tempName = `${nativeType}${REBUILD_SUFFIX}`;
|
|
451
|
-
// Type DDL targets the enum's real schema — the unbound coordinate resolves
|
|
452
|
-
// to the introspected `current_schema()`, never the `__unbound__` sentinel.
|
|
453
|
-
const ddlSchema = resolveDdlSchemaForNamespaceStorage(
|
|
454
|
-
ctx.toContract.storage,
|
|
455
|
-
namespaceId,
|
|
456
|
-
ctx.schema,
|
|
457
|
-
);
|
|
458
|
-
|
|
459
|
-
// Migrate every column whose `typeRef` binds to *this* enum. The column's
|
|
460
|
-
// bare `typeRef` resolves to an enum namespace (own-namespace shadows;
|
|
461
|
-
// otherwise the ambient/default `public` enum), so a column in the unbound
|
|
462
|
-
// namespace correctly binds to a `public`-namespace enum, while two
|
|
463
|
-
// same-named enums in distinct namespaces keep their columns disjoint.
|
|
464
|
-
const columnRefs: { namespaceId: string; table: string; column: string }[] = [];
|
|
465
|
-
for (const [nsId, ns] of Object.entries(ctx.toContract.storage.namespaces)) {
|
|
466
|
-
for (const [tableName, table] of Object.entries(ns.entries.table ?? {})) {
|
|
467
|
-
for (const [columnName, column] of Object.entries(table.columns)) {
|
|
468
|
-
if (
|
|
469
|
-
column.typeRef === typeName &&
|
|
470
|
-
resolveColumnEnumNamespace(ctx.toContract.storage, nsId, typeName) === namespaceId
|
|
471
|
-
) {
|
|
472
|
-
columnRefs.push({ namespaceId: nsId, table: tableName, column: columnName });
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
return [
|
|
479
|
-
new CreateEnumTypeCall(ddlSchema, tempName, desiredValues),
|
|
480
|
-
...columnRefs.map((ref) => {
|
|
481
|
-
const using = `${ref.column}::text::${tempName}`;
|
|
482
|
-
return new AlterColumnTypeCall(
|
|
483
|
-
resolveDdlSchemaForNamespace(ctx, ref.namespaceId),
|
|
484
|
-
ref.table,
|
|
485
|
-
ref.column,
|
|
486
|
-
{
|
|
487
|
-
qualifiedTargetType: tempName,
|
|
488
|
-
formatTypeExpected: tempName,
|
|
489
|
-
rawTargetTypeForLabel: tempName,
|
|
490
|
-
using,
|
|
491
|
-
},
|
|
492
|
-
);
|
|
493
|
-
}),
|
|
494
|
-
new DropEnumTypeCall(ddlSchema, nativeType),
|
|
495
|
-
new RenameTypeCall(ddlSchema, tempName, nativeType),
|
|
496
|
-
];
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
// ============================================================================
|
|
500
|
-
// Native enum planner strategy
|
|
501
|
-
// ============================================================================
|
|
502
|
-
|
|
503
|
-
/**
|
|
504
|
-
* Single planner strategy for `PostgresEnumType` instances. Walks
|
|
505
|
-
* `toContract.storage.types` directly (no codec-hook dispatch) and
|
|
506
|
-
* resolves existing values via `readExistingEnumValues`, the same
|
|
507
|
-
* Postgres bridging adapter the verifier uses.
|
|
508
|
-
*
|
|
509
|
-
* Per-enum dispatch:
|
|
510
|
-
*
|
|
511
|
-
* - No existing type → `CreateEnumTypeCall` with the contract's desired
|
|
512
|
-
* values.
|
|
513
|
-
* - Diff is `unchanged` → no calls emitted (consumes the matching
|
|
514
|
-
* `enum_values_changed` issue if present).
|
|
515
|
-
* - Diff is `add_values` → `AddEnumValuesCall` with the new labels.
|
|
516
|
-
* - Diff is `rebuild` → the create-temp / migrate-columns /
|
|
517
|
-
* drop-original / rename rebuild recipe. When
|
|
518
|
-
* `policy.allowedOperationClasses` includes `'data'` and the rebuild
|
|
519
|
-
* removes labels (`removedValues.length > 0`), prepend a
|
|
520
|
-
* `DataTransformCall` placeholder so the user can author the value
|
|
521
|
-
* remap before the destructive recipe runs. Without `'data'` in the
|
|
522
|
-
* policy (`db update` / `db init`), the rebuild's PG `USING ::text`
|
|
523
|
-
* cast surfaces any value-removal data loss as a runtime error rather
|
|
524
|
-
* than silent loss.
|
|
525
|
-
*
|
|
526
|
-
* Returns `recipe: true` only when a rebuild recipe was emitted (its
|
|
527
|
-
* `createEnumType(temp) → alterColumnType → dropEnumType(orig) →
|
|
528
|
-
* renameType` sequence mixes `dep`-class and `alter`-class calls that
|
|
529
|
-
* would mis-order if the planner hoisted them into its DDL sequencing
|
|
530
|
-
* buckets). For the create-only and add-values paths the strategy
|
|
531
|
-
* returns `recipe: false` so the planner hoists `CreateEnumTypeCall`
|
|
532
|
-
* into the `dep` bucket — i.e. `CREATE TYPE` runs before any
|
|
533
|
-
* `CreateTableCall` that references the new enum.
|
|
534
|
-
*/
|
|
535
|
-
/**
|
|
536
|
-
* Separator character for compound enum map keys (`namespaceId\u0000typeName`).
|
|
537
|
-
* NUL (`\u0000`) is invalid in both Postgres identifiers and TypeScript symbol
|
|
538
|
-
* names so it cannot appear in either component — unambiguous separator.
|
|
539
|
-
*/
|
|
540
|
-
const COMPOUND_KEY_SEP = '\u0000';
|
|
541
|
-
|
|
542
|
-
/** Builds the compound map key for a namespace-qualified enum entry. */
|
|
543
|
-
function enumCompoundKey(namespaceId: string, typeName: string): string {
|
|
544
|
-
return `${namespaceId}${COMPOUND_KEY_SEP}${typeName}`;
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
export const nativeEnumPlanCallStrategy: CallMigrationStrategy = (issues, ctx) => {
|
|
548
|
-
const enumTypes = collectPostgresEnumTypes(ctx.toContract.storage);
|
|
549
|
-
if (enumTypes.size === 0) return { kind: 'no_match' };
|
|
550
|
-
|
|
551
|
-
const dataAllowed = ctx.policy.allowedOperationClasses.includes('data');
|
|
552
|
-
|
|
553
|
-
const calls: PostgresOpFactoryCall[] = [];
|
|
554
|
-
const handledKeys = new Set<string>();
|
|
555
|
-
const introducedKeys = new Set<string>();
|
|
556
|
-
const rebuiltKeys = new Set<string>();
|
|
557
|
-
let emittedRebuildRecipe = false;
|
|
558
|
-
|
|
559
|
-
for (const [key, enumType] of enumTypes) {
|
|
560
|
-
const sepIdx = key.indexOf(COMPOUND_KEY_SEP);
|
|
561
|
-
const enumNamespaceId = key.slice(0, sepIdx);
|
|
562
|
-
const typeName = key.slice(sepIdx + 1);
|
|
563
|
-
|
|
564
|
-
const desired = enumType.values;
|
|
565
|
-
// The enum's live schema: for the unbound coordinate this resolves to the
|
|
566
|
-
// introspected `current_schema()` (e.g. `public`), never the `__unbound__`
|
|
567
|
-
// DDL-emit sentinel — so both the existing-values lookup key and the
|
|
568
|
-
// emitted `CREATE TYPE` / `ALTER TYPE` target the real schema the type
|
|
569
|
-
// lives in. Named namespaces resolve to their own DDL schema.
|
|
570
|
-
const ddlSchema = resolveDdlSchemaForNamespaceStorage(
|
|
571
|
-
ctx.toContract.storage,
|
|
572
|
-
enumNamespaceId,
|
|
573
|
-
ctx.schema,
|
|
574
|
-
);
|
|
575
|
-
const existing = readExistingEnumValues(ctx.schema, ddlSchema, enumType.nativeType);
|
|
576
|
-
if (!existing) {
|
|
577
|
-
calls.push(new CreateEnumTypeCall(ddlSchema, typeName, desired, enumType.nativeType));
|
|
578
|
-
handledKeys.add(key);
|
|
579
|
-
introducedKeys.add(key);
|
|
580
|
-
continue;
|
|
581
|
-
}
|
|
582
|
-
const diff = determineEnumDiff(existing, desired);
|
|
583
|
-
if (diff.kind === 'unchanged') {
|
|
584
|
-
handledKeys.add(key);
|
|
585
|
-
continue;
|
|
586
|
-
}
|
|
587
|
-
if (diff.kind === 'add_values') {
|
|
588
|
-
calls.push(new AddEnumValuesCall(ddlSchema, typeName, enumType.nativeType, diff.values));
|
|
589
|
-
handledKeys.add(key);
|
|
590
|
-
continue;
|
|
591
|
-
}
|
|
592
|
-
if (dataAllowed && diff.removedValues.length > 0) {
|
|
593
|
-
calls.push(
|
|
594
|
-
new DataTransformCall(
|
|
595
|
-
`migrate-${typeName}-values`,
|
|
596
|
-
`migrate-${typeName}-values:check`,
|
|
597
|
-
`migrate-${typeName}-values:run`,
|
|
598
|
-
),
|
|
599
|
-
);
|
|
600
|
-
}
|
|
601
|
-
calls.push(...enumRebuildCallRecipe(enumNamespaceId, typeName, ctx));
|
|
602
|
-
emittedRebuildRecipe = true;
|
|
603
|
-
handledKeys.add(key);
|
|
604
|
-
rebuiltKeys.add(key);
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
// The strategy emits a single `recipe` flag for the entire pass,
|
|
608
|
-
// which routes every emitted call to either the contiguous recipe
|
|
609
|
-
// slot (rebuild path) or the `dep` bucket (introduce / add-values
|
|
610
|
-
// path). A plan that needs both shapes simultaneously cannot be
|
|
611
|
-
// expressed today — the introduced `CreateEnumTypeCall` would land
|
|
612
|
-
// in the recipe slot and any `CreateTableCall` referencing the new
|
|
613
|
-
// enum would fail at runtime with a confusing `type "X" does not
|
|
614
|
-
// exist` error. Surface the unrepresentable case here as a
|
|
615
|
-
// planner-time error so the failure mode is loud, not silent.
|
|
616
|
-
if (introducedKeys.size > 0 && rebuiltKeys.size > 0) {
|
|
617
|
-
const introducedDisplay = [...introducedKeys]
|
|
618
|
-
.sort()
|
|
619
|
-
.map((k) => k.replace(COMPOUND_KEY_SEP, '.'))
|
|
620
|
-
.join(', ');
|
|
621
|
-
const rebuiltDisplay = [...rebuiltKeys]
|
|
622
|
-
.sort()
|
|
623
|
-
.map((k) => k.replace(COMPOUND_KEY_SEP, '.'))
|
|
624
|
-
.join(', ');
|
|
625
|
-
throw new Error(
|
|
626
|
-
`nativeEnumPlanCallStrategy: cannot emit both a brand-new enum and a rebuild on a different enum in the same plan; the single recipe flag cannot route them to different buckets. Introduced: [${introducedDisplay}]; rebuilt: [${rebuiltDisplay}]. Split the strategy or grow the \`match\` return type before this case lands.`,
|
|
627
|
-
);
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
const remaining = issues.filter(
|
|
631
|
-
(issue) =>
|
|
632
|
-
!(
|
|
633
|
-
(issue.kind === 'type_missing' || issue.kind === 'enum_values_changed') &&
|
|
634
|
-
issue.typeName &&
|
|
635
|
-
handledKeys.has(enumCompoundKey(resolveNamespaceIdForIssue(issue), issue.typeName))
|
|
636
|
-
),
|
|
637
|
-
);
|
|
638
|
-
|
|
639
|
-
if (calls.length === 0 && remaining.length === issues.length) {
|
|
640
|
-
return { kind: 'no_match' };
|
|
641
|
-
}
|
|
642
|
-
// `recipe: true` is required for the rebuild path — its
|
|
643
|
-
// `createEnumType(temp) → alterColumnType → dropEnumType(orig) →
|
|
644
|
-
// renameType` mixes `dep`-class and `alter`-class calls that would
|
|
645
|
-
// mis-order if the planner hoisted them into its DDL sequencing
|
|
646
|
-
// buckets. For the type_missing / add_values paths we want the
|
|
647
|
-
// opposite: hoisted into the `dep` bucket so a brand-new
|
|
648
|
-
// `CreateEnumTypeCall` runs *before* the `CreateTableCall` that
|
|
649
|
-
// references it. The two cases never co-occur in the same plan
|
|
650
|
-
// (introducing a new enum type and rebuilding an existing one in
|
|
651
|
-
// one shot would require both buckets — a shape today's interface
|
|
652
|
-
// does not surface; if that combination ever needs to land we'd
|
|
653
|
-
// split this strategy or grow the `match` return type).
|
|
654
|
-
return { kind: 'match', issues: remaining, calls, recipe: emittedRebuildRecipe };
|
|
655
|
-
};
|
|
656
|
-
|
|
657
|
-
/**
|
|
658
|
-
* Collects every `PostgresEnumType` instance across all declared namespaces,
|
|
659
|
-
* returning a compound-keyed map (`${namespaceId}\u0000${typeName}`). Two
|
|
660
|
-
* namespaces that declare an enum with the same name produce two distinct
|
|
661
|
-
* entries — no name collision, no last-write-wins.
|
|
662
|
-
*
|
|
663
|
-
* Entries within each namespace are sorted by name for deterministic ordering.
|
|
664
|
-
*/
|
|
665
|
-
function collectPostgresEnumTypes(storage: SqlStorage): ReadonlyMap<string, PostgresEnumType> {
|
|
666
|
-
const result = new Map<string, PostgresEnumType>();
|
|
667
|
-
for (const [nsId, ns] of Object.entries(storage.namespaces)) {
|
|
668
|
-
if (!isPostgresSchema(ns)) continue;
|
|
669
|
-
for (const [name, instance] of Object.entries(ns.type).sort(([a], [b]) => a.localeCompare(b))) {
|
|
670
|
-
if (instance instanceof PostgresEnumType) {
|
|
671
|
-
result.set(enumCompoundKey(nsId, name), instance);
|
|
672
|
-
}
|
|
673
|
-
}
|
|
674
|
-
}
|
|
675
|
-
return result;
|
|
676
|
-
}
|
|
677
|
-
|
|
678
358
|
/**
|
|
679
359
|
* Collects every check constraint from a table in the contract storage.
|
|
680
360
|
* Returns an empty array when the table has no checks or the table is absent.
|
|
@@ -723,8 +403,6 @@ function checkValueSetsEqual(a: readonly string[], b: readonly string[]): boolea
|
|
|
723
403
|
* be altered in place).
|
|
724
404
|
*
|
|
725
405
|
* Consumes `check_missing`, `check_removed`, and `check_mismatch` issues.
|
|
726
|
-
* Does not touch the native enum path (`nativeEnumPlanCallStrategy` is
|
|
727
|
-
* unchanged).
|
|
728
406
|
*/
|
|
729
407
|
export const checkConstraintPlanCallStrategy: CallMigrationStrategy = (issues, ctx) => {
|
|
730
408
|
const calls: PostgresOpFactoryCall[] = [];
|
|
@@ -803,10 +481,9 @@ export const checkConstraintPlanCallStrategy: CallMigrationStrategy = (issues, c
|
|
|
803
481
|
};
|
|
804
482
|
|
|
805
483
|
/**
|
|
806
|
-
* Dispatches
|
|
484
|
+
* Dispatches codec-typed storage types through their codec's
|
|
807
485
|
* `planTypeOperations` hook (the authoritative source for codec-driven DDL
|
|
808
|
-
* such as custom type creation).
|
|
809
|
-
* `nativeEnumPlanCallStrategy` and no longer relies on codec hooks.
|
|
486
|
+
* such as custom type creation).
|
|
810
487
|
*/
|
|
811
488
|
export const storageTypePlanCallStrategy: CallMigrationStrategy = (issues, ctx) => {
|
|
812
489
|
const storageTypes = ctx.toContract.storage.types ?? {};
|
|
@@ -818,10 +495,6 @@ export const storageTypePlanCallStrategy: CallMigrationStrategy = (issues, ctx)
|
|
|
818
495
|
for (const [typeName, typeInstance] of Object.entries(storageTypes).sort(([a], [b]) =>
|
|
819
496
|
a.localeCompare(b),
|
|
820
497
|
)) {
|
|
821
|
-
// Enums walk natively in `nativeEnumPlanCallStrategy`; codec-hook
|
|
822
|
-
// dispatch here is reserved for genuinely codec-typed entries
|
|
823
|
-
// (decimal, varchar, pgvector, …).
|
|
824
|
-
if (isPostgresEnumStorageEntry(typeInstance)) continue;
|
|
825
498
|
const codecInstance = typeInstance as StorageTypeInstance;
|
|
826
499
|
const hook = ctx.codecHooks.get(codecInstance.codecId);
|
|
827
500
|
if (!hook?.planTypeOperations) continue;
|
|
@@ -889,10 +562,7 @@ export const notNullAddColumnCallStrategy: CallMigrationStrategy = (issues, ctx)
|
|
|
889
562
|
const schemaLookups = buildSchemaLookupMap(ctx.schema);
|
|
890
563
|
|
|
891
564
|
const mutableCodecHooks = ctx.codecHooks as Map<string, CodecControlHooks>;
|
|
892
|
-
const mutableStorageTypes = ctx.storageTypes as Record<
|
|
893
|
-
string,
|
|
894
|
-
StorageTypeInstance | PostgresEnumType
|
|
895
|
-
>;
|
|
565
|
+
const mutableStorageTypes = ctx.storageTypes as Record<string, StorageTypeInstance>;
|
|
896
566
|
|
|
897
567
|
for (const issue of issues) {
|
|
898
568
|
if (issue.kind !== 'missing_column' || !issue.table || !issue.column) continue;
|
|
@@ -1048,8 +718,7 @@ function canUseSharedTemporaryDefaultStrategy(options: {
|
|
|
1048
718
|
*
|
|
1049
719
|
* - When `'data'` is allowed (`migration plan`), the data-safe strategies
|
|
1050
720
|
* (`notNullBackfillCallStrategy`, `typeChangeCallStrategy`,
|
|
1051
|
-
* `nullableTighteningCallStrategy`)
|
|
1052
|
-
* (`nativeEnumPlanCallStrategy`) consume their matching issues and emit
|
|
721
|
+
* `nullableTighteningCallStrategy`) consume their matching issues and emit
|
|
1053
722
|
* `DataTransformCall` placeholders or recipe ops.
|
|
1054
723
|
*
|
|
1055
724
|
* - When `'data'` is not allowed (`db update` / `db init`), the
|
|
@@ -1057,23 +726,14 @@ function canUseSharedTemporaryDefaultStrategy(options: {
|
|
|
1057
726
|
* the issue for the downstream walk-schema strategies
|
|
1058
727
|
* (`storageTypePlanCallStrategy`, `notNullAddColumnCallStrategy`) or the
|
|
1059
728
|
* `mapIssueToCall` default to handle with direct DDL.
|
|
1060
|
-
* `nativeEnumPlanCallStrategy` runs in both modes; under `db update` /
|
|
1061
|
-
* `db init` it emits the rebuild recipe without the data-transform
|
|
1062
|
-
* placeholder so value-removal data loss surfaces as a runtime cast
|
|
1063
|
-
* error rather than silent loss.
|
|
1064
729
|
*
|
|
1065
|
-
*
|
|
1066
|
-
* `
|
|
1067
|
-
* rebuild recipe (`recipe: true`, contiguous slot) or hoist the call
|
|
1068
|
-
* into the `dep` bucket (`recipe: false`, so a brand-new
|
|
1069
|
-
* `CreateEnumTypeCall` runs before any `CreateTableCall` referencing
|
|
1070
|
-
* it). Codec-typed entries continue through `storageTypePlanCallStrategy`.
|
|
730
|
+
* Codec-typed storage type entries are dispatched through
|
|
731
|
+
* `storageTypePlanCallStrategy`.
|
|
1071
732
|
*/
|
|
1072
733
|
export const postgresPlannerStrategies: readonly CallMigrationStrategy[] = [
|
|
1073
734
|
notNullBackfillCallStrategy,
|
|
1074
735
|
typeChangeCallStrategy,
|
|
1075
736
|
nullableTighteningCallStrategy,
|
|
1076
|
-
nativeEnumPlanCallStrategy,
|
|
1077
737
|
checkConstraintPlanCallStrategy,
|
|
1078
738
|
storageTypePlanCallStrategy,
|
|
1079
739
|
notNullAddColumnCallStrategy,
|
|
@@ -1,9 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
isPostgresEnumStorageEntry,
|
|
3
|
-
type PostgresEnumStorageEntry,
|
|
4
|
-
type StorageColumn,
|
|
5
|
-
type StorageTypeInstance,
|
|
6
|
-
} from '@prisma-next/sql-contract/types';
|
|
1
|
+
import type { StorageColumn, StorageTypeInstance } from '@prisma-next/sql-contract/types';
|
|
7
2
|
|
|
8
3
|
export type ResolvedColumnTypeMetadata = Pick<
|
|
9
4
|
StorageColumn,
|
|
@@ -12,7 +7,7 @@ export type ResolvedColumnTypeMetadata = Pick<
|
|
|
12
7
|
|
|
13
8
|
export function resolveColumnTypeMetadata(
|
|
14
9
|
column: StorageColumn,
|
|
15
|
-
storageTypes: Readonly<Record<string, StorageTypeInstance
|
|
10
|
+
storageTypes: Readonly<Record<string, StorageTypeInstance>>,
|
|
16
11
|
): ResolvedColumnTypeMetadata {
|
|
17
12
|
if (!column.typeRef) {
|
|
18
13
|
return column;
|
|
@@ -23,19 +18,6 @@ export function resolveColumnTypeMetadata(
|
|
|
23
18
|
return column;
|
|
24
19
|
}
|
|
25
20
|
|
|
26
|
-
if (isPostgresEnumStorageEntry(referencedType)) {
|
|
27
|
-
// Enum types are referenced by name (`quoteIdentifier(nativeType)`),
|
|
28
|
-
// not via parameterised codec expansion. The structural shape
|
|
29
|
-
// carries `codecId` as an enumerable property (mirroring the
|
|
30
|
-
// codec-typed view); `typeParams` is intentionally omitted here so
|
|
31
|
-
// `expandParameterizedTypeSql` does not try to look up a
|
|
32
|
-
// (deliberately absent) `expandNativeType` hook for `pg/enum@*`.
|
|
33
|
-
return {
|
|
34
|
-
codecId: referencedType.codecId,
|
|
35
|
-
nativeType: referencedType.nativeType,
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
|
|
39
21
|
return {
|
|
40
22
|
codecId: referencedType.codecId,
|
|
41
23
|
nativeType: referencedType.nativeType,
|
|
@@ -31,7 +31,6 @@ import {
|
|
|
31
31
|
resolvePostgresIssueControlPolicySubject,
|
|
32
32
|
resolvePostgresIssueCreationFactoryName,
|
|
33
33
|
} from './control-policy';
|
|
34
|
-
import { createResolveExistingEnumValues } from './enum-planning';
|
|
35
34
|
import { planIssues } from './issue-planner';
|
|
36
35
|
import type { PostgresOpFactoryCall } from './op-factory-call';
|
|
37
36
|
import { TypeScriptRenderablePostgresMigration } from './planner-produced-postgres-migration';
|
|
@@ -268,7 +267,6 @@ export class PostgresMigrationPlanner implements MigrationPlanner<'sql', 'postgr
|
|
|
268
267
|
frameworkComponents: options.frameworkComponents,
|
|
269
268
|
normalizeDefault: parsePostgresDefault,
|
|
270
269
|
normalizeNativeType: normalizeSchemaNativeType,
|
|
271
|
-
resolveExistingEnumValues: createResolveExistingEnumValues(options.contract.storage),
|
|
272
270
|
};
|
|
273
271
|
const verifyResult = verifySqlSchema(verifyOptions);
|
|
274
272
|
// Schema presence is a Postgres-specific concern (no equivalent in
|
|
@@ -25,7 +25,6 @@ import type { Result } from '@prisma-next/utils/result';
|
|
|
25
25
|
import { notOk, ok, okVoid } from '@prisma-next/utils/result';
|
|
26
26
|
import { parsePostgresDefault } from '../default-normalizer';
|
|
27
27
|
import { normalizeSchemaNativeType } from '../native-type-normalizer';
|
|
28
|
-
import { createResolveExistingEnumValues } from './enum-planning';
|
|
29
28
|
import type { PostgresPlanTargetDetails } from './planner-target-details';
|
|
30
29
|
|
|
31
30
|
interface ApplyPlanSuccessValue {
|
|
@@ -144,9 +143,6 @@ class PostgresMigrationRunner implements SqlMigrationRunner<PostgresPlanTargetDe
|
|
|
144
143
|
frameworkComponents: options.frameworkComponents,
|
|
145
144
|
normalizeDefault: parsePostgresDefault,
|
|
146
145
|
normalizeNativeType: normalizeSchemaNativeType,
|
|
147
|
-
resolveExistingEnumValues: createResolveExistingEnumValues(
|
|
148
|
-
options.destinationContract.storage,
|
|
149
|
-
),
|
|
150
146
|
});
|
|
151
147
|
if (!schemaVerifyResult.ok) {
|
|
152
148
|
return runnerFailure('SCHEMA_VERIFY_FAILED', schemaVerifyResult.summary, {
|