@prisma-next/target-postgres 0.9.0-dev.7 → 0.9.0-dev.9
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-RvYfmUmi.d.mts → codec-ids-D9fJ4HP5.d.mts} +1 -1
- package/dist/{codec-ids-RvYfmUmi.d.mts.map → codec-ids-D9fJ4HP5.d.mts.map} +1 -1
- package/dist/codec-ids.d.mts +1 -1
- package/dist/{codec-types-667FxIW8.d.mts → codec-types-CRlHq7Cz.d.mts} +2 -2
- package/dist/{codec-types-667FxIW8.d.mts.map → codec-types-CRlHq7Cz.d.mts.map} +1 -1
- package/dist/codec-types.d.mts +1 -1
- package/dist/{codecs-DXeDABSO.d.mts → codecs-Dud5KDNk.d.mts} +2 -2
- package/dist/{codecs-DXeDABSO.d.mts.map → codecs-Dud5KDNk.d.mts.map} +1 -1
- package/dist/codecs.d.mts +1 -1
- package/dist/control.d.mts +1 -1
- package/dist/control.mjs +5 -5
- package/dist/{data-transform-COkGR6Ns.mjs → data-transform-CdtGUWp2.mjs} +1 -1
- package/dist/{data-transform-COkGR6Ns.mjs.map → data-transform-CdtGUWp2.mjs.map} +1 -1
- package/dist/{data-transform-B6p02mFJ.d.mts → data-transform-bmOKkygi.d.mts} +2 -2
- package/dist/{data-transform-B6p02mFJ.d.mts.map → data-transform-bmOKkygi.d.mts.map} +1 -1
- package/dist/data-transform.d.mts +1 -1
- package/dist/data-transform.mjs +1 -1
- package/dist/descriptor-meta-zrZzWmJF.mjs +91 -0
- package/dist/{descriptor-meta-DFUCClk_.mjs.map → descriptor-meta-zrZzWmJF.mjs.map} +1 -1
- package/dist/enum-planning.d.mts +1 -1
- package/dist/{errors-BiOloWUh.mjs → errors--zafB5_n.mjs} +1 -1
- package/dist/{errors-BiOloWUh.mjs.map → errors--zafB5_n.mjs.map} +1 -1
- package/dist/errors.mjs +1 -1
- package/dist/{issue-planner-BhWVYyE1.mjs → issue-planner-qalHRCI2.mjs} +179 -77
- package/dist/issue-planner-qalHRCI2.mjs.map +1 -0
- package/dist/issue-planner.d.mts +2 -2
- package/dist/issue-planner.d.mts.map +1 -1
- package/dist/issue-planner.mjs +1 -1
- package/dist/migration.d.mts +3 -3
- package/dist/migration.mjs +3 -3
- package/dist/{op-factory-call-c1zELk3U.d.mts → op-factory-call-Drccm_JD.d.mts} +3 -3
- package/dist/{op-factory-call-c1zELk3U.d.mts.map → op-factory-call-Drccm_JD.d.mts.map} +1 -1
- package/dist/{op-factory-call-DerP9BoT.mjs → op-factory-call-Zsrdty3k.mjs} +2 -2
- package/dist/{op-factory-call-DerP9BoT.mjs.map → op-factory-call-Zsrdty3k.mjs.map} +1 -1
- package/dist/op-factory-call.d.mts +1 -1
- package/dist/op-factory-call.mjs +1 -1
- package/dist/pack.d.mts +2 -2
- package/dist/pack.mjs +1 -1
- package/dist/{planner-DnzPpv1j.mjs → planner-C8yhbXOq.mjs} +84 -5
- package/dist/planner-C8yhbXOq.mjs.map +1 -0
- package/dist/{planner-ddl-builders-5QIyhBUF.mjs → planner-ddl-builders-DINYrbJ3.mjs} +4 -4
- package/dist/{planner-ddl-builders-5QIyhBUF.mjs.map → planner-ddl-builders-DINYrbJ3.mjs.map} +1 -1
- package/dist/planner-ddl-builders.d.mts +1 -1
- package/dist/planner-ddl-builders.mjs +1 -1
- package/dist/{planner-identity-values-BUYNOCwb.mjs → planner-identity-values-ojX-6cPV.mjs} +1 -1
- package/dist/{planner-identity-values-BUYNOCwb.mjs.map → planner-identity-values-ojX-6cPV.mjs.map} +1 -1
- package/dist/planner-identity-values.mjs +1 -1
- package/dist/{planner-produced-postgres-migration-FYsKh26I.mjs → planner-produced-postgres-migration-BqGLw7VT.mjs} +4 -4
- package/dist/{planner-produced-postgres-migration-FYsKh26I.mjs.map → planner-produced-postgres-migration-BqGLw7VT.mjs.map} +1 -1
- package/dist/{planner-produced-postgres-migration-D34ftfEK.d.mts → planner-produced-postgres-migration-c9lpjPv1.d.mts} +3 -3
- package/dist/{planner-produced-postgres-migration-D34ftfEK.d.mts.map → planner-produced-postgres-migration-c9lpjPv1.d.mts.map} +1 -1
- package/dist/planner-produced-postgres-migration.d.mts +1 -1
- package/dist/planner-produced-postgres-migration.mjs +1 -1
- package/dist/planner-schema-lookup-BGyukuzG.mjs +57 -0
- package/dist/planner-schema-lookup-BGyukuzG.mjs.map +1 -0
- package/dist/planner-schema-lookup.d.mts.map +1 -1
- package/dist/planner-schema-lookup.mjs +1 -1
- package/dist/{planner-sql-checks-Cd016Ycs.mjs → planner-sql-checks-BM4sD6Xc.mjs} +28 -11
- package/dist/planner-sql-checks-BM4sD6Xc.mjs.map +1 -0
- package/dist/planner-sql-checks.d.mts +11 -0
- package/dist/planner-sql-checks.d.mts.map +1 -1
- package/dist/planner-sql-checks.mjs +1 -1
- package/dist/{planner-target-details-iYJwzFHP.d.mts → planner-target-details-CIj61DUj.d.mts} +1 -1
- package/dist/{planner-target-details-iYJwzFHP.d.mts.map → planner-target-details-CIj61DUj.d.mts.map} +1 -1
- package/dist/planner-target-details.d.mts +1 -1
- package/dist/planner.d.mts +1 -1
- package/dist/planner.d.mts.map +1 -1
- package/dist/planner.mjs +1 -1
- package/dist/postgres-contract-serializer-CcZO9ukP.mjs +83 -0
- package/dist/postgres-contract-serializer-CcZO9ukP.mjs.map +1 -0
- package/dist/{postgres-enum-type-CrKq8au9.d.mts → postgres-enum-type-CNhPTDhy.d.mts} +1 -1
- package/dist/{postgres-enum-type-CrKq8au9.d.mts.map → postgres-enum-type-CNhPTDhy.d.mts.map} +1 -1
- package/dist/{postgres-migration-q4oWyNJE.mjs → postgres-migration-C5os-tkl.mjs} +3 -3
- package/dist/{postgres-migration-q4oWyNJE.mjs.map → postgres-migration-C5os-tkl.mjs.map} +1 -1
- package/dist/{postgres-migration-CiQzhcMe.d.mts → postgres-migration-jvsKgUDM.d.mts} +3 -3
- package/dist/{postgres-migration-CiQzhcMe.d.mts.map → postgres-migration-jvsKgUDM.d.mts.map} +1 -1
- package/dist/postgres-schema-BosNxhWq.mjs +163 -0
- package/dist/postgres-schema-BosNxhWq.mjs.map +1 -0
- package/dist/{render-ops-CkiuHSNj.mjs → render-ops-BC2PtCkj.mjs} +1 -1
- package/dist/{render-ops-CkiuHSNj.mjs.map → render-ops-BC2PtCkj.mjs.map} +1 -1
- package/dist/render-ops.d.mts +1 -1
- package/dist/render-ops.mjs +1 -1
- package/dist/{render-typescript-C9XWI8Ld.mjs → render-typescript-nRHbqLbI.mjs} +1 -1
- package/dist/{render-typescript-C9XWI8Ld.mjs.map → render-typescript-nRHbqLbI.mjs.map} +1 -1
- package/dist/render-typescript.mjs +1 -1
- package/dist/runtime.d.mts +7 -17
- package/dist/runtime.d.mts.map +1 -1
- package/dist/runtime.mjs +2 -2
- package/dist/{shared-DLYdmYo-.d.mts → shared-ByhSooBS.d.mts} +6 -4
- package/dist/shared-ByhSooBS.d.mts.map +1 -0
- package/dist/{statement-builders-BSIQMClE.mjs → statement-builders-vImtdfmM.mjs} +1 -1
- package/dist/{statement-builders-BSIQMClE.mjs.map → statement-builders-vImtdfmM.mjs.map} +1 -1
- package/dist/statement-builders.mjs +1 -1
- package/dist/{tables-Ce_Q0I8B.mjs → tables-DZ-5Yxi0.mjs} +3 -3
- package/dist/tables-DZ-5Yxi0.mjs.map +1 -0
- package/dist/{types-Dq74Z3eu.d.mts → types-D-XIpzHA.d.mts} +1 -1
- package/dist/types-D-XIpzHA.d.mts.map +1 -0
- package/dist/types.d.mts +125 -3
- package/dist/types.d.mts.map +1 -0
- package/dist/types.mjs +2 -1
- package/package.json +17 -17
- package/src/core/migrations/issue-planner.ts +131 -41
- package/src/core/migrations/operations/constraints.ts +1 -1
- package/src/core/migrations/operations/shared.ts +4 -2
- package/src/core/migrations/planner-ddl-builders.ts +2 -2
- package/src/core/migrations/planner-schema-lookup.ts +30 -6
- package/src/core/migrations/planner-sql-checks.ts +29 -11
- package/src/core/migrations/planner-strategies.ts +138 -43
- package/src/core/migrations/planner.ts +14 -1
- package/src/core/migrations/verify-postgres-namespaces.ts +87 -0
- package/src/core/postgres-contract-serializer.ts +139 -60
- package/src/core/postgres-schema.ts +208 -0
- package/src/exports/types.ts +5 -0
- package/dist/descriptor-meta-DFUCClk_.mjs +0 -124
- package/dist/issue-planner-BhWVYyE1.mjs.map +0 -1
- package/dist/planner-DnzPpv1j.mjs.map +0 -1
- package/dist/planner-schema-lookup--u9whY_Y.mjs +0 -29
- package/dist/planner-schema-lookup--u9whY_Y.mjs.map +0 -1
- package/dist/planner-sql-checks-Cd016Ycs.mjs.map +0 -1
- package/dist/postgres-contract-serializer-D5VJk6lo.mjs +0 -61
- package/dist/postgres-contract-serializer-D5VJk6lo.mjs.map +0 -1
- package/dist/shared-DLYdmYo-.d.mts.map +0 -1
- package/dist/tables-Ce_Q0I8B.mjs.map +0 -1
- package/dist/types-Dq74Z3eu.d.mts.map +0 -1
|
@@ -26,14 +26,17 @@ import type {
|
|
|
26
26
|
} from '@prisma-next/family-sql/control';
|
|
27
27
|
import type { TargetBoundComponentDescriptor } from '@prisma-next/framework-components/components';
|
|
28
28
|
import type { SchemaIssue } from '@prisma-next/framework-components/control';
|
|
29
|
+
import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir';
|
|
29
30
|
import {
|
|
30
31
|
isPostgresEnumStorageEntry,
|
|
31
32
|
type PostgresEnumStorageEntry,
|
|
32
33
|
type SqlStorage,
|
|
34
|
+
type StorageTable,
|
|
33
35
|
type StorageTypeInstance,
|
|
34
36
|
} from '@prisma-next/sql-contract/types';
|
|
35
37
|
import type { SqlSchemaIR } from '@prisma-next/sql-schema-ir/types';
|
|
36
38
|
import { PostgresEnumType } from '../postgres-enum-type';
|
|
39
|
+
import { isPostgresSchema } from '../postgres-schema';
|
|
37
40
|
import { determineEnumDiff, readExistingEnumValues } from './enum-planning';
|
|
38
41
|
import {
|
|
39
42
|
AddColumnCall,
|
|
@@ -69,6 +72,74 @@ import { buildTargetDetails, type PostgresPlanTargetDetails } from './planner-ta
|
|
|
69
72
|
|
|
70
73
|
const REBUILD_SUFFIX = '__prisma_next_new';
|
|
71
74
|
|
|
75
|
+
/**
|
|
76
|
+
* Look up a storage table by its explicit namespace coordinate. Returns
|
|
77
|
+
* `undefined` when the namespace has no table by that name (or no such
|
|
78
|
+
* namespace exists). Callers that get `undefined` MUST treat it as an
|
|
79
|
+
* explicit conflict — never silently fall back to a global default
|
|
80
|
+
* schema or a name-only walk, because that footgun would resolve a
|
|
81
|
+
* stale or duplicate table name to whichever namespace the iteration
|
|
82
|
+
* order surfaced first (a real data-loss hazard in multi-namespace
|
|
83
|
+
* contracts where two namespaces can carry the same table name).
|
|
84
|
+
*/
|
|
85
|
+
export function tableAt(
|
|
86
|
+
storage: SqlStorage,
|
|
87
|
+
namespaceId: string,
|
|
88
|
+
tableName: string,
|
|
89
|
+
): StorageTable | undefined {
|
|
90
|
+
// Namespace.tables is typed as Record<string, IRNode> at the interface level;
|
|
91
|
+
// SQL family namespaces always hold StorageTable instances.
|
|
92
|
+
return storage.namespaces[namespaceId]?.tables[tableName] as StorageTable | undefined;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Default namespace coordinate for an issue that does not carry one
|
|
97
|
+
* explicitly. Hand-crafted unit-test issues and `extra_table` issues
|
|
98
|
+
* fall back to `__unbound__`, the only namespace any single-namespace
|
|
99
|
+
* contract carries — verifier-emitted issues for legacy
|
|
100
|
+
* single-namespace contracts already stamp this id explicitly. Typed
|
|
101
|
+
* structurally so issue variants without a `namespaceId` slot
|
|
102
|
+
* (e.g. `EnumValuesChangedIssue`) flow through to the same fallback.
|
|
103
|
+
*/
|
|
104
|
+
export function resolveNamespaceIdForIssue(issue: { readonly namespaceId?: string }): string {
|
|
105
|
+
return issue.namespaceId ?? UNBOUND_NAMESPACE_ID;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Resolve the DDL schema name for a namespace coordinate. Postgres-aware
|
|
110
|
+
* namespaces dispatch to their polymorphic `ddlSchemaName` override —
|
|
111
|
+
* named schemas return their own id and the unbound singleton projects
|
|
112
|
+
* to `'public'` (sibling-present) or the framework sentinel
|
|
113
|
+
* (sibling-absent). Legacy single-namespace contracts whose `__unbound__`
|
|
114
|
+
* slot is the framework-default `SqlUnboundNamespace` (rather than the
|
|
115
|
+
* Postgres-aware `PostgresUnboundSchema`) flow the coordinate through
|
|
116
|
+
* unchanged so downstream `qualifyTableName` resolves polymorphically.
|
|
117
|
+
*/
|
|
118
|
+
export function resolveDdlSchemaForNamespace(ctx: StrategyContext, namespaceId: string): string {
|
|
119
|
+
const namespace = ctx.toContract.storage.namespaces[namespaceId];
|
|
120
|
+
if (isPostgresSchema(namespace)) {
|
|
121
|
+
return namespace.ddlSchemaName(ctx.toContract.storage);
|
|
122
|
+
}
|
|
123
|
+
return namespaceId;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Finds a type entry by name across all namespace type registries.
|
|
128
|
+
* Namespace types (e.g. Postgres enums) live under
|
|
129
|
+
* `storage.namespaces[nsId].types`, not under `storage.types`.
|
|
130
|
+
*/
|
|
131
|
+
function locateNamespaceType(
|
|
132
|
+
storage: SqlStorage,
|
|
133
|
+
typeName: string,
|
|
134
|
+
): PostgresEnumStorageEntry | undefined {
|
|
135
|
+
for (const ns of Object.values(storage.namespaces)) {
|
|
136
|
+
if (!('types' in ns) || ns.types == null) continue;
|
|
137
|
+
const entry = (ns.types as Record<string, PostgresEnumStorageEntry>)[typeName];
|
|
138
|
+
if (entry !== undefined) return entry;
|
|
139
|
+
}
|
|
140
|
+
return undefined;
|
|
141
|
+
}
|
|
142
|
+
|
|
72
143
|
// ============================================================================
|
|
73
144
|
// Strategy types
|
|
74
145
|
// ============================================================================
|
|
@@ -120,12 +191,13 @@ export type CallMigrationStrategy = (
|
|
|
120
191
|
| { kind: 'no_match' };
|
|
121
192
|
|
|
122
193
|
function buildColumnSpec(
|
|
194
|
+
namespaceId: string,
|
|
123
195
|
table: string,
|
|
124
196
|
column: string,
|
|
125
197
|
ctx: StrategyContext,
|
|
126
198
|
overrides?: { nullable?: boolean },
|
|
127
199
|
) {
|
|
128
|
-
const col = ctx.toContract.storage
|
|
200
|
+
const col = tableAt(ctx.toContract.storage, namespaceId, table)?.columns[column];
|
|
129
201
|
if (!col) throw new Error(`Column "${table}"."${column}" not found in destination contract`);
|
|
130
202
|
const mutableHooks = ctx.codecHooks as Map<string, CodecControlHooks>;
|
|
131
203
|
const mutableTypes = ctx.storageTypes as Record<
|
|
@@ -141,12 +213,13 @@ function buildColumnSpec(
|
|
|
141
213
|
}
|
|
142
214
|
|
|
143
215
|
function buildAlterTypeOptions(
|
|
216
|
+
namespaceId: string,
|
|
144
217
|
table: string,
|
|
145
218
|
column: string,
|
|
146
219
|
ctx: StrategyContext,
|
|
147
220
|
using?: string,
|
|
148
221
|
) {
|
|
149
|
-
const col = ctx.toContract.storage
|
|
222
|
+
const col = tableAt(ctx.toContract.storage, namespaceId, table)?.columns[column];
|
|
150
223
|
if (!col) throw new Error(`Column "${table}"."${column}" not found in destination contract`);
|
|
151
224
|
const mutableHooks = ctx.codecHooks as Map<string, CodecControlHooks>;
|
|
152
225
|
const mutableTypes = ctx.storageTypes as Record<
|
|
@@ -175,20 +248,22 @@ export const notNullBackfillCallStrategy: CallMigrationStrategy = (issues, ctx)
|
|
|
175
248
|
for (const issue of issues) {
|
|
176
249
|
if (issue.kind !== 'missing_column' || !issue.table || !issue.column) continue;
|
|
177
250
|
|
|
178
|
-
const
|
|
251
|
+
const namespaceId = resolveNamespaceIdForIssue(issue);
|
|
252
|
+
const column = tableAt(ctx.toContract.storage, namespaceId, issue.table)?.columns[issue.column];
|
|
179
253
|
if (!column) continue;
|
|
180
254
|
if (column.nullable === true || column.default !== undefined) continue;
|
|
181
255
|
|
|
182
256
|
matched.push(issue);
|
|
183
|
-
const spec = buildColumnSpec(issue.table, issue.column, ctx, { nullable: true });
|
|
257
|
+
const spec = buildColumnSpec(namespaceId, issue.table, issue.column, ctx, { nullable: true });
|
|
258
|
+
const schemaForTable = resolveDdlSchemaForNamespace(ctx, namespaceId);
|
|
184
259
|
calls.push(
|
|
185
|
-
new AddColumnCall(
|
|
260
|
+
new AddColumnCall(schemaForTable, issue.table, spec),
|
|
186
261
|
new DataTransformCall(
|
|
187
262
|
`backfill-${issue.table}-${issue.column}`,
|
|
188
263
|
`backfill-${issue.table}-${issue.column}:check`,
|
|
189
264
|
`backfill-${issue.table}-${issue.column}:run`,
|
|
190
265
|
),
|
|
191
|
-
new SetNotNullCall(
|
|
266
|
+
new SetNotNullCall(schemaForTable, issue.table, issue.column),
|
|
192
267
|
);
|
|
193
268
|
}
|
|
194
269
|
|
|
@@ -217,8 +292,13 @@ export const typeChangeCallStrategy: CallMigrationStrategy = (issues, ctx) => {
|
|
|
217
292
|
for (const issue of issues) {
|
|
218
293
|
if (issue.kind !== 'type_mismatch') continue;
|
|
219
294
|
if (!issue.table || !issue.column) continue;
|
|
220
|
-
const
|
|
221
|
-
const
|
|
295
|
+
const namespaceId = resolveNamespaceIdForIssue(issue);
|
|
296
|
+
const fromColumn = ctx.fromContract
|
|
297
|
+
? tableAt(ctx.fromContract.storage, namespaceId, issue.table)?.columns[issue.column]
|
|
298
|
+
: undefined;
|
|
299
|
+
const toColumn = tableAt(ctx.toContract.storage, namespaceId, issue.table)?.columns[
|
|
300
|
+
issue.column
|
|
301
|
+
];
|
|
222
302
|
if (!fromColumn || !toColumn) continue;
|
|
223
303
|
const fromType = fromColumn.nativeType;
|
|
224
304
|
const toType = toColumn.nativeType;
|
|
@@ -226,9 +306,10 @@ export const typeChangeCallStrategy: CallMigrationStrategy = (issues, ctx) => {
|
|
|
226
306
|
const isSafeWidening = SAFE_WIDENINGS.has(`${fromType}→${toType}`);
|
|
227
307
|
if (!isSafeWidening && !dataAllowed) continue;
|
|
228
308
|
matched.push(issue);
|
|
229
|
-
const alterOpts = buildAlterTypeOptions(issue.table, issue.column, ctx);
|
|
309
|
+
const alterOpts = buildAlterTypeOptions(namespaceId, issue.table, issue.column, ctx);
|
|
310
|
+
const schemaForTable = resolveDdlSchemaForNamespace(ctx, namespaceId);
|
|
230
311
|
if (isSafeWidening) {
|
|
231
|
-
calls.push(new AlterColumnTypeCall(
|
|
312
|
+
calls.push(new AlterColumnTypeCall(schemaForTable, issue.table, issue.column, alterOpts));
|
|
232
313
|
} else {
|
|
233
314
|
calls.push(
|
|
234
315
|
new DataTransformCall(
|
|
@@ -236,7 +317,7 @@ export const typeChangeCallStrategy: CallMigrationStrategy = (issues, ctx) => {
|
|
|
236
317
|
`typechange-${issue.table}-${issue.column}:check`,
|
|
237
318
|
`typechange-${issue.table}-${issue.column}:run`,
|
|
238
319
|
),
|
|
239
|
-
new AlterColumnTypeCall(
|
|
320
|
+
new AlterColumnTypeCall(schemaForTable, issue.table, issue.column, alterOpts),
|
|
240
321
|
);
|
|
241
322
|
}
|
|
242
323
|
}
|
|
@@ -261,18 +342,20 @@ export const nullableTighteningCallStrategy: CallMigrationStrategy = (issues, ct
|
|
|
261
342
|
for (const issue of issues) {
|
|
262
343
|
if (issue.kind !== 'nullability_mismatch' || !issue.table || !issue.column) continue;
|
|
263
344
|
|
|
264
|
-
const
|
|
345
|
+
const namespaceId = resolveNamespaceIdForIssue(issue);
|
|
346
|
+
const column = tableAt(ctx.toContract.storage, namespaceId, issue.table)?.columns[issue.column];
|
|
265
347
|
if (!column) continue;
|
|
266
348
|
if (column.nullable === true) continue;
|
|
267
349
|
|
|
268
350
|
matched.push(issue);
|
|
351
|
+
const schemaForTable = resolveDdlSchemaForNamespace(ctx, namespaceId);
|
|
269
352
|
calls.push(
|
|
270
353
|
new DataTransformCall(
|
|
271
354
|
`handle-nulls-${issue.table}-${issue.column}`,
|
|
272
355
|
`handle-nulls-${issue.table}-${issue.column}:check`,
|
|
273
356
|
`handle-nulls-${issue.table}-${issue.column}:run`,
|
|
274
357
|
),
|
|
275
|
-
new SetNotNullCall(
|
|
358
|
+
new SetNotNullCall(schemaForTable, issue.table, issue.column),
|
|
276
359
|
);
|
|
277
360
|
}
|
|
278
361
|
|
|
@@ -289,7 +372,7 @@ function enumRebuildCallRecipe(
|
|
|
289
372
|
typeName: string,
|
|
290
373
|
ctx: StrategyContext,
|
|
291
374
|
): readonly PostgresOpFactoryCall[] {
|
|
292
|
-
const toType = ctx.toContract.storage
|
|
375
|
+
const toType = locateNamespaceType(ctx.toContract.storage, typeName);
|
|
293
376
|
if (!toType) return [];
|
|
294
377
|
const isEnum = isPostgresEnumStorageEntry(toType);
|
|
295
378
|
const nativeType = toType.nativeType;
|
|
@@ -298,11 +381,14 @@ function enumRebuildCallRecipe(
|
|
|
298
381
|
: (((toType as StorageTypeInstance).typeParams['values'] ?? []) as readonly string[]);
|
|
299
382
|
const tempName = `${nativeType}${REBUILD_SUFFIX}`;
|
|
300
383
|
|
|
301
|
-
const columnRefs: { table: string; column: string }[] = [];
|
|
302
|
-
for (const [
|
|
303
|
-
for (const [
|
|
304
|
-
|
|
305
|
-
|
|
384
|
+
const columnRefs: { namespaceId: string; table: string; column: string }[] = [];
|
|
385
|
+
for (const [nsId, ns] of Object.entries(ctx.toContract.storage.namespaces)) {
|
|
386
|
+
for (const [tableName, tableNode] of Object.entries(ns.tables)) {
|
|
387
|
+
const table = tableNode as StorageTable;
|
|
388
|
+
for (const [columnName, column] of Object.entries(table.columns)) {
|
|
389
|
+
if (column.typeRef === typeName) {
|
|
390
|
+
columnRefs.push({ namespaceId: nsId, table: tableName, column: columnName });
|
|
391
|
+
}
|
|
306
392
|
}
|
|
307
393
|
}
|
|
308
394
|
}
|
|
@@ -311,12 +397,17 @@ function enumRebuildCallRecipe(
|
|
|
311
397
|
new CreateEnumTypeCall(ctx.schemaName, tempName, desiredValues),
|
|
312
398
|
...columnRefs.map((ref) => {
|
|
313
399
|
const using = `${ref.column}::text::${tempName}`;
|
|
314
|
-
return new AlterColumnTypeCall(
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
400
|
+
return new AlterColumnTypeCall(
|
|
401
|
+
resolveDdlSchemaForNamespace(ctx, ref.namespaceId),
|
|
402
|
+
ref.table,
|
|
403
|
+
ref.column,
|
|
404
|
+
{
|
|
405
|
+
qualifiedTargetType: tempName,
|
|
406
|
+
formatTypeExpected: tempName,
|
|
407
|
+
rawTargetTypeForLabel: tempName,
|
|
408
|
+
using,
|
|
409
|
+
},
|
|
410
|
+
);
|
|
320
411
|
}),
|
|
321
412
|
new DropEnumTypeCall(ctx.schemaName, nativeType),
|
|
322
413
|
new RenameTypeCall(ctx.schemaName, tempName, nativeType),
|
|
@@ -360,7 +451,7 @@ function enumRebuildCallRecipe(
|
|
|
360
451
|
* `CreateTableCall` that references the new enum.
|
|
361
452
|
*/
|
|
362
453
|
export const nativeEnumPlanCallStrategy: CallMigrationStrategy = (issues, ctx) => {
|
|
363
|
-
const enumTypes = collectPostgresEnumTypes(ctx.toContract.storage
|
|
454
|
+
const enumTypes = collectPostgresEnumTypes(ctx.toContract.storage);
|
|
364
455
|
if (enumTypes.size === 0) return { kind: 'no_match' };
|
|
365
456
|
|
|
366
457
|
const dataAllowed = ctx.policy.allowedOperationClasses.includes('data');
|
|
@@ -447,15 +538,15 @@ export const nativeEnumPlanCallStrategy: CallMigrationStrategy = (issues, ctx) =
|
|
|
447
538
|
return { kind: 'match', issues: remaining, calls, recipe: emittedRebuildRecipe };
|
|
448
539
|
};
|
|
449
540
|
|
|
450
|
-
function collectPostgresEnumTypes(
|
|
451
|
-
storageTypes: SqlStorage['types'],
|
|
452
|
-
): ReadonlyMap<string, PostgresEnumType> {
|
|
541
|
+
function collectPostgresEnumTypes(storage: SqlStorage): ReadonlyMap<string, PostgresEnumType> {
|
|
453
542
|
const result = new Map<string, PostgresEnumType>();
|
|
454
|
-
for (const
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
543
|
+
for (const ns of Object.values(storage.namespaces)) {
|
|
544
|
+
if (!('types' in ns) || ns.types == null) continue;
|
|
545
|
+
const nsTypes = ns.types as Record<string, unknown>;
|
|
546
|
+
for (const [name, instance] of Object.entries(nsTypes).sort(([a], [b]) => a.localeCompare(b))) {
|
|
547
|
+
if (instance instanceof PostgresEnumType) {
|
|
548
|
+
result.set(name, instance);
|
|
549
|
+
}
|
|
459
550
|
}
|
|
460
551
|
}
|
|
461
552
|
return result;
|
|
@@ -555,7 +646,8 @@ export const notNullAddColumnCallStrategy: CallMigrationStrategy = (issues, ctx)
|
|
|
555
646
|
|
|
556
647
|
for (const issue of issues) {
|
|
557
648
|
if (issue.kind !== 'missing_column' || !issue.table || !issue.column) continue;
|
|
558
|
-
const
|
|
649
|
+
const namespaceId = resolveNamespaceIdForIssue(issue);
|
|
650
|
+
const contractTable = tableAt(ctx.toContract.storage, namespaceId, issue.table);
|
|
559
651
|
const column = contractTable?.columns[issue.column];
|
|
560
652
|
if (!column) continue;
|
|
561
653
|
|
|
@@ -579,11 +671,13 @@ export const notNullAddColumnCallStrategy: CallMigrationStrategy = (issues, ctx)
|
|
|
579
671
|
|
|
580
672
|
matched.push(issue);
|
|
581
673
|
|
|
674
|
+
const schemaForTable = resolveDdlSchemaForNamespace(ctx, namespaceId);
|
|
675
|
+
|
|
582
676
|
if (canUseSharedTempDefault && temporaryDefault !== null) {
|
|
583
677
|
calls.push(
|
|
584
678
|
new RawSqlCall(
|
|
585
679
|
buildAddNotNullColumnWithTemporaryDefaultOperation({
|
|
586
|
-
schema:
|
|
680
|
+
schema: schemaForTable,
|
|
587
681
|
tableName: issue.table,
|
|
588
682
|
columnName: issue.column,
|
|
589
683
|
column,
|
|
@@ -596,16 +690,16 @@ export const notNullAddColumnCallStrategy: CallMigrationStrategy = (issues, ctx)
|
|
|
596
690
|
continue;
|
|
597
691
|
}
|
|
598
692
|
|
|
599
|
-
const qualified = qualifyTableName(
|
|
693
|
+
const qualified = qualifyTableName(schemaForTable, issue.table);
|
|
600
694
|
calls.push(
|
|
601
695
|
new RawSqlCall({
|
|
602
|
-
...buildAddColumnOperationIdentity(
|
|
696
|
+
...buildAddColumnOperationIdentity(schemaForTable, issue.table, issue.column),
|
|
603
697
|
operationClass: 'additive',
|
|
604
698
|
precheck: [
|
|
605
699
|
{
|
|
606
700
|
description: `ensure column "${issue.column}" is missing`,
|
|
607
701
|
sql: columnExistsCheck({
|
|
608
|
-
schema:
|
|
702
|
+
schema: schemaForTable,
|
|
609
703
|
table: issue.table,
|
|
610
704
|
column: issue.column,
|
|
611
705
|
exists: false,
|
|
@@ -633,7 +727,7 @@ export const notNullAddColumnCallStrategy: CallMigrationStrategy = (issues, ctx)
|
|
|
633
727
|
{
|
|
634
728
|
description: `verify column "${issue.column}" exists`,
|
|
635
729
|
sql: columnExistsCheck({
|
|
636
|
-
schema:
|
|
730
|
+
schema: schemaForTable,
|
|
637
731
|
table: issue.table,
|
|
638
732
|
column: issue.column,
|
|
639
733
|
}),
|
|
@@ -641,7 +735,7 @@ export const notNullAddColumnCallStrategy: CallMigrationStrategy = (issues, ctx)
|
|
|
641
735
|
{
|
|
642
736
|
description: `verify column "${issue.column}" is NOT NULL`,
|
|
643
737
|
sql: columnNullabilityCheck({
|
|
644
|
-
schema:
|
|
738
|
+
schema: schemaForTable,
|
|
645
739
|
table: issue.table,
|
|
646
740
|
column: issue.column,
|
|
647
741
|
nullable: false,
|
|
@@ -665,7 +759,7 @@ export const notNullAddColumnCallStrategy: CallMigrationStrategy = (issues, ctx)
|
|
|
665
759
|
// ============================================================================
|
|
666
760
|
|
|
667
761
|
function canUseSharedTemporaryDefaultStrategy(options: {
|
|
668
|
-
readonly table:
|
|
762
|
+
readonly table: StorageTable;
|
|
669
763
|
readonly schemaTable: SqlSchemaIR['tables'][string];
|
|
670
764
|
readonly schemaLookup: ReturnType<typeof buildSchemaLookupMap> extends ReadonlyMap<
|
|
671
765
|
string,
|
|
@@ -687,7 +781,8 @@ function canUseSharedTemporaryDefaultStrategy(options: {
|
|
|
687
781
|
}
|
|
688
782
|
|
|
689
783
|
for (const foreignKey of table.foreignKeys) {
|
|
690
|
-
if (foreignKey.constraint === false || !foreignKey.columns.includes(columnName))
|
|
784
|
+
if (foreignKey.constraint === false || !foreignKey.source.columns.includes(columnName))
|
|
785
|
+
continue;
|
|
691
786
|
if (!schemaLookup || !hasForeignKey(schemaLookup, foreignKey)) return false;
|
|
692
787
|
}
|
|
693
788
|
|
|
@@ -23,6 +23,7 @@ import { readExistingEnumValues } from './enum-planning';
|
|
|
23
23
|
import { planIssues } from './issue-planner';
|
|
24
24
|
import { TypeScriptRenderablePostgresMigration } from './planner-produced-postgres-migration';
|
|
25
25
|
import { postgresPlannerStrategies } from './planner-strategies';
|
|
26
|
+
import { verifyPostgresNamespacePresence } from './verify-postgres-namespaces';
|
|
26
27
|
|
|
27
28
|
type PlannerFrameworkComponents = SqlMigrationPlannerPlanOptions extends {
|
|
28
29
|
readonly frameworkComponents: infer T;
|
|
@@ -222,6 +223,18 @@ export class PostgresMigrationPlanner implements MigrationPlanner<'sql', 'postgr
|
|
|
222
223
|
readExistingEnumValues(schema, enumType.nativeType),
|
|
223
224
|
};
|
|
224
225
|
const verifyResult = verifySqlSchema(verifyOptions);
|
|
225
|
-
|
|
226
|
+
// Schema presence is a Postgres-specific concern (no equivalent in
|
|
227
|
+
// SQLite / Mongo), so the issue emission lives in the target layer
|
|
228
|
+
// rather than in the family verifier. Stitch it in here so a single
|
|
229
|
+
// `SchemaIssue[]` flows through `planIssues` and the planner emits
|
|
230
|
+
// CREATE SCHEMA in the dep bucket before any CreateTableCall.
|
|
231
|
+
const namespaceIssues = verifyPostgresNamespacePresence({
|
|
232
|
+
contract: options.contract,
|
|
233
|
+
schema: options.schema,
|
|
234
|
+
});
|
|
235
|
+
if (namespaceIssues.length === 0) {
|
|
236
|
+
return verifyResult.schema.issues;
|
|
237
|
+
}
|
|
238
|
+
return [...namespaceIssues, ...verifyResult.schema.issues];
|
|
226
239
|
}
|
|
227
240
|
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type { Contract } from '@prisma-next/contract/types';
|
|
2
|
+
import type { SchemaIssue } from '@prisma-next/framework-components/control';
|
|
3
|
+
import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir';
|
|
4
|
+
import type { SqlStorage } from '@prisma-next/sql-contract/types';
|
|
5
|
+
import type { SqlSchemaIR } from '@prisma-next/sql-schema-ir/types';
|
|
6
|
+
import { isPostgresSchema } from '../postgres-schema';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Resolves the live-database schema name for a given namespace
|
|
10
|
+
* coordinate. Mirrors `resolveDdlSchemaForNamespace` in
|
|
11
|
+
* `planner-strategies.ts` so the verifier's projection and the
|
|
12
|
+
* planner's projection always agree — Postgres-aware namespaces (the
|
|
13
|
+
* production path) dispatch to `ddlSchemaName(storage)`, and bare
|
|
14
|
+
* object payloads (used by some tests) fall back to the coordinate
|
|
15
|
+
* itself.
|
|
16
|
+
*/
|
|
17
|
+
function resolveDdlSchemaName(storage: SqlStorage, namespaceId: string): string {
|
|
18
|
+
const namespace = storage.namespaces[namespaceId];
|
|
19
|
+
if (isPostgresSchema(namespace)) {
|
|
20
|
+
return namespace.ddlSchemaName(storage);
|
|
21
|
+
}
|
|
22
|
+
return namespaceId;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Reads the introspected list of schema names from the Postgres-flavoured
|
|
27
|
+
* annotations slot on the schema IR. Defaults to the always-present
|
|
28
|
+
* `public` schema when introspection did not populate the slot — a fresh
|
|
29
|
+
* Postgres database always carries `public` (unless an operator dropped
|
|
30
|
+
* it manually), so any verifier path that runs without an enriched
|
|
31
|
+
* introspection still suppresses the redundant `CREATE SCHEMA "public"`.
|
|
32
|
+
*
|
|
33
|
+
* Production introspection (`PostgresControlAdapter.introspect`) is the
|
|
34
|
+
* authoritative source: it queries `pg_namespace` and writes every
|
|
35
|
+
* non-system schema into `annotations.pg.existingSchemas`. Tests that
|
|
36
|
+
* want to assert against a richer initial state pass the slot
|
|
37
|
+
* explicitly via the schema IR.
|
|
38
|
+
*/
|
|
39
|
+
function existingSchemasFromSchema(schema: SqlSchemaIR): readonly string[] {
|
|
40
|
+
const annotations = (schema as { annotations?: { pg?: { existingSchemas?: unknown } } })
|
|
41
|
+
.annotations;
|
|
42
|
+
const slot = annotations?.pg?.existingSchemas;
|
|
43
|
+
if (Array.isArray(slot)) {
|
|
44
|
+
return slot.filter((s): s is string => typeof s === 'string');
|
|
45
|
+
}
|
|
46
|
+
return ['public'];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Emits a `missing_schema` issue for every contract-declared Postgres
|
|
51
|
+
* namespace whose live container does not yet exist.
|
|
52
|
+
*
|
|
53
|
+
* A namespace's live container is the schema returned by its
|
|
54
|
+
* polymorphic `ddlSchemaName(storage)` method — named schemas resolve
|
|
55
|
+
* to their own id, the unbound singleton projects to `public` (sibling
|
|
56
|
+
* present) or the framework sentinel (sibling absent). Issues are
|
|
57
|
+
* emitted only when the resolved name is a real, creatable schema
|
|
58
|
+
* (not the unbound sentinel) and is missing from the introspected
|
|
59
|
+
* list. `public` is suppressed implicitly because the introspection
|
|
60
|
+
* (or its sensible default) always carries it.
|
|
61
|
+
*
|
|
62
|
+
* Each emitted issue stamps `namespaceId` with the contract namespace
|
|
63
|
+
* coordinate so the downstream `mapIssueToCall` re-resolves the DDL
|
|
64
|
+
* schema name through the same polymorphic path — keeping the
|
|
65
|
+
* coordinate, not the resolved name, as the issue's stable identity.
|
|
66
|
+
*/
|
|
67
|
+
export function verifyPostgresNamespacePresence(input: {
|
|
68
|
+
readonly contract: Contract<SqlStorage>;
|
|
69
|
+
readonly schema: SqlSchemaIR;
|
|
70
|
+
}): readonly SchemaIssue[] {
|
|
71
|
+
const { contract, schema } = input;
|
|
72
|
+
const existing = new Set(existingSchemasFromSchema(schema));
|
|
73
|
+
const issues: SchemaIssue[] = [];
|
|
74
|
+
const namespaceIds = Object.keys(contract.storage.namespaces).sort();
|
|
75
|
+
for (const namespaceId of namespaceIds) {
|
|
76
|
+
if (namespaceId === UNBOUND_NAMESPACE_ID) continue;
|
|
77
|
+
const ddlName = resolveDdlSchemaName(contract.storage, namespaceId);
|
|
78
|
+
if (ddlName === UNBOUND_NAMESPACE_ID) continue;
|
|
79
|
+
if (existing.has(ddlName)) continue;
|
|
80
|
+
issues.push({
|
|
81
|
+
kind: 'missing_schema',
|
|
82
|
+
namespaceId,
|
|
83
|
+
message: `Schema "${ddlName}" is missing from database`,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
return issues;
|
|
87
|
+
}
|