@prisma-next/target-postgres 0.3.0-pr.99.5 → 0.3.0
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/LICENSE +201 -0
- package/README.md +17 -8
- package/dist/control.d.mts +19 -0
- package/dist/control.d.mts.map +1 -0
- package/dist/control.mjs +5382 -0
- package/dist/control.mjs.map +1 -0
- package/dist/descriptor-meta-CAf16lsJ.mjs +32 -0
- package/dist/descriptor-meta-CAf16lsJ.mjs.map +1 -0
- package/dist/migration-builders.d.mts +88 -0
- package/dist/migration-builders.d.mts.map +1 -0
- package/dist/migration-builders.mjs +3 -0
- package/dist/operation-descriptors-CxymFSgK.mjs +52 -0
- package/dist/operation-descriptors-CxymFSgK.mjs.map +1 -0
- package/dist/pack.d.mts +45 -0
- package/dist/pack.d.mts.map +1 -0
- package/dist/pack.mjs +9 -0
- package/dist/pack.mjs.map +1 -0
- package/dist/runtime.d.mts +9 -0
- package/dist/runtime.d.mts.map +1 -0
- package/dist/runtime.mjs +20 -0
- package/dist/runtime.mjs.map +1 -0
- package/package.json +30 -28
- package/src/core/authoring.ts +15 -0
- package/src/core/descriptor-meta.ts +5 -0
- package/src/core/migrations/descriptor-planner.ts +466 -0
- package/src/core/migrations/operation-descriptors.ts +166 -0
- package/src/core/migrations/operation-resolver.ts +929 -0
- package/src/core/migrations/planner-ddl-builders.ts +256 -0
- package/src/core/migrations/planner-identity-values.ts +135 -0
- package/src/core/migrations/planner-recipes.ts +91 -0
- package/src/core/migrations/planner-reconciliation.ts +798 -0
- package/src/core/migrations/planner-schema-lookup.ts +54 -0
- package/src/core/migrations/planner-sql-checks.ts +322 -0
- package/src/core/migrations/planner-strategies.ts +262 -0
- package/src/core/migrations/planner-target-details.ts +38 -0
- package/src/core/migrations/planner-type-resolution.ts +26 -0
- package/src/core/migrations/planner.ts +410 -460
- package/src/core/migrations/runner.ts +134 -38
- package/src/core/migrations/statement-builders.ts +6 -6
- package/src/core/types.ts +5 -0
- package/src/exports/control.ts +182 -12
- package/src/exports/migration-builders.ts +56 -0
- package/src/exports/pack.ts +7 -3
- package/src/exports/runtime.ts +6 -12
- package/dist/chunk-RKEXRSSI.js +0 -14
- package/dist/chunk-RKEXRSSI.js.map +0 -1
- package/dist/core/descriptor-meta.d.ts +0 -9
- package/dist/core/descriptor-meta.d.ts.map +0 -1
- package/dist/core/migrations/planner.d.ts +0 -14
- package/dist/core/migrations/planner.d.ts.map +0 -1
- package/dist/core/migrations/runner.d.ts +0 -8
- package/dist/core/migrations/runner.d.ts.map +0 -1
- package/dist/core/migrations/statement-builders.d.ts +0 -30
- package/dist/core/migrations/statement-builders.d.ts.map +0 -1
- package/dist/exports/control.d.ts +0 -8
- package/dist/exports/control.d.ts.map +0 -1
- package/dist/exports/control.js +0 -1260
- package/dist/exports/control.js.map +0 -1
- package/dist/exports/pack.d.ts +0 -4
- package/dist/exports/pack.d.ts.map +0 -1
- package/dist/exports/pack.js +0 -11
- package/dist/exports/pack.js.map +0 -1
- package/dist/exports/runtime.d.ts +0 -12
- package/dist/exports/runtime.d.ts.map +0 -1
- package/dist/exports/runtime.js +0 -19
- package/dist/exports/runtime.js.map +0 -1
|
@@ -0,0 +1,466 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Descriptor-based migration planner.
|
|
3
|
+
*
|
|
4
|
+
* Takes schema issues (from verifySqlSchema) and emits PostgresMigrationOpDescriptor[].
|
|
5
|
+
* Migration strategies consume issues they recognize and produce specialized op
|
|
6
|
+
* sequences (e.g., NOT NULL backfill → addColumn(nullable) + dataTransform + setNotNull).
|
|
7
|
+
* Remaining issues get default descriptor mapping.
|
|
8
|
+
*
|
|
9
|
+
* This planner does NOT produce SqlMigrationPlanOperation — that's the resolver's job.
|
|
10
|
+
* The separation means the same descriptors work for both planner-generated and
|
|
11
|
+
* user-authored migrations.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type { Contract } from '@prisma-next/contract/types';
|
|
15
|
+
import type { SqlPlannerConflict } from '@prisma-next/family-sql/control';
|
|
16
|
+
import type { SchemaIssue } from '@prisma-next/framework-components/control';
|
|
17
|
+
import type { SqlStorage } from '@prisma-next/sql-contract/types';
|
|
18
|
+
import type { Result } from '@prisma-next/utils/result';
|
|
19
|
+
import { notOk, ok } from '@prisma-next/utils/result';
|
|
20
|
+
import {
|
|
21
|
+
addColumn,
|
|
22
|
+
addForeignKey,
|
|
23
|
+
addPrimaryKey,
|
|
24
|
+
addUnique,
|
|
25
|
+
alterColumnType,
|
|
26
|
+
createDependency,
|
|
27
|
+
createEnumType,
|
|
28
|
+
createIndex,
|
|
29
|
+
createTable,
|
|
30
|
+
dropColumn,
|
|
31
|
+
dropConstraint,
|
|
32
|
+
dropDefault,
|
|
33
|
+
dropIndex,
|
|
34
|
+
dropNotNull,
|
|
35
|
+
dropTable,
|
|
36
|
+
type PostgresMigrationOpDescriptor,
|
|
37
|
+
setDefault,
|
|
38
|
+
setNotNull,
|
|
39
|
+
} from './operation-descriptors';
|
|
40
|
+
import {
|
|
41
|
+
type MigrationStrategy,
|
|
42
|
+
migrationPlanStrategies,
|
|
43
|
+
type StrategyContext,
|
|
44
|
+
} from './planner-strategies';
|
|
45
|
+
|
|
46
|
+
export type { MigrationStrategy, StrategyContext };
|
|
47
|
+
|
|
48
|
+
// ============================================================================
|
|
49
|
+
// Issue kind ordering (dependency order)
|
|
50
|
+
// ============================================================================
|
|
51
|
+
|
|
52
|
+
const ISSUE_KIND_ORDER: Record<string, number> = {
|
|
53
|
+
// Dependencies and types first
|
|
54
|
+
dependency_missing: 1,
|
|
55
|
+
type_missing: 2,
|
|
56
|
+
type_values_mismatch: 3,
|
|
57
|
+
enum_values_changed: 3,
|
|
58
|
+
|
|
59
|
+
// Drops (reconciliation — clear the way for creates)
|
|
60
|
+
// FKs dropped first (they depend on other constraints)
|
|
61
|
+
extra_foreign_key: 10,
|
|
62
|
+
extra_unique_constraint: 11,
|
|
63
|
+
extra_primary_key: 12,
|
|
64
|
+
extra_index: 13,
|
|
65
|
+
extra_default: 14,
|
|
66
|
+
extra_column: 15,
|
|
67
|
+
extra_table: 16,
|
|
68
|
+
|
|
69
|
+
// Tables before columns
|
|
70
|
+
missing_table: 20,
|
|
71
|
+
|
|
72
|
+
// Columns before constraints
|
|
73
|
+
missing_column: 30,
|
|
74
|
+
|
|
75
|
+
// Reconciliation alters (on existing objects)
|
|
76
|
+
type_mismatch: 40,
|
|
77
|
+
nullability_mismatch: 41,
|
|
78
|
+
default_missing: 42,
|
|
79
|
+
default_mismatch: 43,
|
|
80
|
+
|
|
81
|
+
// Constraints after columns exist
|
|
82
|
+
primary_key_mismatch: 50,
|
|
83
|
+
unique_constraint_mismatch: 51,
|
|
84
|
+
index_mismatch: 52,
|
|
85
|
+
foreign_key_mismatch: 60,
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
function issueOrder(issue: SchemaIssue): number {
|
|
89
|
+
return ISSUE_KIND_ORDER[issue.kind] ?? 99;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// ============================================================================
|
|
93
|
+
// Conflict helpers
|
|
94
|
+
// ============================================================================
|
|
95
|
+
|
|
96
|
+
function issueConflict(
|
|
97
|
+
kind: SqlPlannerConflict['kind'],
|
|
98
|
+
summary: string,
|
|
99
|
+
location?: SqlPlannerConflict['location'],
|
|
100
|
+
): SqlPlannerConflict {
|
|
101
|
+
return {
|
|
102
|
+
kind,
|
|
103
|
+
summary,
|
|
104
|
+
why: 'Use `migration new` to author a custom migration for this change.',
|
|
105
|
+
...(location ? { location } : {}),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// ============================================================================
|
|
110
|
+
// Default issue-to-descriptor mapping
|
|
111
|
+
// ============================================================================
|
|
112
|
+
|
|
113
|
+
function isMissing(issue: SchemaIssue): boolean {
|
|
114
|
+
if (issue.kind === 'enum_values_changed') return false;
|
|
115
|
+
return issue.actual === undefined;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function mapIssue(
|
|
119
|
+
issue: SchemaIssue,
|
|
120
|
+
ctx: StrategyContext,
|
|
121
|
+
): Result<readonly PostgresMigrationOpDescriptor[], SqlPlannerConflict> {
|
|
122
|
+
switch (issue.kind) {
|
|
123
|
+
// Additive — missing structures
|
|
124
|
+
case 'missing_table': {
|
|
125
|
+
if (!issue.table)
|
|
126
|
+
return notOk(
|
|
127
|
+
issueConflict('unsupportedOperation', 'Missing table issue has no table name'),
|
|
128
|
+
);
|
|
129
|
+
const contractTable = ctx.toContract.storage.tables[issue.table];
|
|
130
|
+
if (!contractTable) {
|
|
131
|
+
return notOk(
|
|
132
|
+
issueConflict(
|
|
133
|
+
'unsupportedOperation',
|
|
134
|
+
`Table "${issue.table}" reported missing but not found in destination contract`,
|
|
135
|
+
),
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
const ops: PostgresMigrationOpDescriptor[] = [createTable(issue.table)];
|
|
139
|
+
for (const index of contractTable.indexes) {
|
|
140
|
+
ops.push(createIndex(issue.table, [...index.columns]));
|
|
141
|
+
}
|
|
142
|
+
const explicitIndexColumnSets = new Set(
|
|
143
|
+
contractTable.indexes.map((idx) => idx.columns.join(',')),
|
|
144
|
+
);
|
|
145
|
+
for (const fk of contractTable.foreignKeys) {
|
|
146
|
+
if (fk.constraint) {
|
|
147
|
+
ops.push(addForeignKey(issue.table, [...fk.columns]));
|
|
148
|
+
}
|
|
149
|
+
if (fk.index && !explicitIndexColumnSets.has(fk.columns.join(','))) {
|
|
150
|
+
ops.push(createIndex(issue.table, [...fk.columns]));
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
for (const unique of contractTable.uniques) {
|
|
154
|
+
ops.push(addUnique(issue.table, [...unique.columns]));
|
|
155
|
+
}
|
|
156
|
+
return ok(ops);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
case 'missing_column':
|
|
160
|
+
if (!issue.table || !issue.column)
|
|
161
|
+
return notOk(
|
|
162
|
+
issueConflict('unsupportedOperation', 'Missing column issue has no table/column name'),
|
|
163
|
+
);
|
|
164
|
+
return ok([addColumn(issue.table, issue.column)]);
|
|
165
|
+
|
|
166
|
+
case 'default_missing':
|
|
167
|
+
if (!issue.table || !issue.column)
|
|
168
|
+
return notOk(
|
|
169
|
+
issueConflict('unsupportedOperation', 'Default missing issue has no table/column name'),
|
|
170
|
+
);
|
|
171
|
+
return ok([setDefault(issue.table, issue.column)]);
|
|
172
|
+
|
|
173
|
+
// Destructive — extra structures
|
|
174
|
+
case 'extra_table':
|
|
175
|
+
if (!issue.table)
|
|
176
|
+
return notOk(issueConflict('unsupportedOperation', 'Extra table issue has no table name'));
|
|
177
|
+
return ok([dropTable(issue.table)]);
|
|
178
|
+
|
|
179
|
+
case 'extra_column':
|
|
180
|
+
if (!issue.table || !issue.column)
|
|
181
|
+
return notOk(
|
|
182
|
+
issueConflict('unsupportedOperation', 'Extra column issue has no table/column name'),
|
|
183
|
+
);
|
|
184
|
+
return ok([dropColumn(issue.table, issue.column)]);
|
|
185
|
+
|
|
186
|
+
case 'extra_index':
|
|
187
|
+
if (!issue.table || !issue.indexOrConstraint)
|
|
188
|
+
return notOk(
|
|
189
|
+
issueConflict('unsupportedOperation', 'Extra index issue has no table/index name'),
|
|
190
|
+
);
|
|
191
|
+
return ok([dropIndex(issue.table, issue.indexOrConstraint)]);
|
|
192
|
+
|
|
193
|
+
case 'extra_unique_constraint':
|
|
194
|
+
case 'extra_foreign_key':
|
|
195
|
+
case 'extra_primary_key':
|
|
196
|
+
if (!issue.table || !issue.indexOrConstraint)
|
|
197
|
+
return notOk(
|
|
198
|
+
issueConflict(
|
|
199
|
+
'unsupportedOperation',
|
|
200
|
+
'Extra constraint issue has no table/constraint name',
|
|
201
|
+
),
|
|
202
|
+
);
|
|
203
|
+
return ok([dropConstraint(issue.table, issue.indexOrConstraint)]);
|
|
204
|
+
|
|
205
|
+
case 'extra_default':
|
|
206
|
+
if (!issue.table || !issue.column)
|
|
207
|
+
return notOk(
|
|
208
|
+
issueConflict('unsupportedOperation', 'Extra default issue has no table/column name'),
|
|
209
|
+
);
|
|
210
|
+
return ok([dropDefault(issue.table, issue.column)]);
|
|
211
|
+
|
|
212
|
+
// Nullability changes
|
|
213
|
+
case 'nullability_mismatch': {
|
|
214
|
+
if (!issue.table || !issue.column)
|
|
215
|
+
return notOk(
|
|
216
|
+
issueConflict('nullabilityConflict', 'Nullability mismatch has no table/column name'),
|
|
217
|
+
);
|
|
218
|
+
const column = ctx.toContract.storage.tables[issue.table]?.columns[issue.column];
|
|
219
|
+
if (!column)
|
|
220
|
+
return notOk(
|
|
221
|
+
issueConflict(
|
|
222
|
+
'nullabilityConflict',
|
|
223
|
+
`Column "${issue.table}"."${issue.column}" not found in destination contract`,
|
|
224
|
+
),
|
|
225
|
+
);
|
|
226
|
+
return ok(
|
|
227
|
+
column.nullable
|
|
228
|
+
? [dropNotNull(issue.table, issue.column)]
|
|
229
|
+
: [setNotNull(issue.table, issue.column)],
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Type changes
|
|
234
|
+
case 'type_mismatch':
|
|
235
|
+
if (!issue.table || !issue.column)
|
|
236
|
+
return notOk(issueConflict('typeMismatch', 'Type mismatch has no table/column name'));
|
|
237
|
+
return ok([alterColumnType(issue.table, issue.column)]);
|
|
238
|
+
|
|
239
|
+
// Default changes
|
|
240
|
+
case 'default_mismatch':
|
|
241
|
+
if (!issue.table || !issue.column)
|
|
242
|
+
return notOk(
|
|
243
|
+
issueConflict('unsupportedOperation', 'Default mismatch has no table/column name'),
|
|
244
|
+
);
|
|
245
|
+
return ok([setDefault(issue.table, issue.column)]);
|
|
246
|
+
|
|
247
|
+
// Constraints — missing (actual undefined) vs mismatched (actual defined)
|
|
248
|
+
case 'primary_key_mismatch':
|
|
249
|
+
if (!issue.table)
|
|
250
|
+
return notOk(issueConflict('indexIncompatible', 'Primary key issue has no table name'));
|
|
251
|
+
if (isMissing(issue)) return ok([addPrimaryKey(issue.table)]);
|
|
252
|
+
return notOk(
|
|
253
|
+
issueConflict(
|
|
254
|
+
'indexIncompatible',
|
|
255
|
+
`Primary key on "${issue.table}" has different columns (expected: ${issue.expected}, actual: ${issue.actual})`,
|
|
256
|
+
{ table: issue.table },
|
|
257
|
+
),
|
|
258
|
+
);
|
|
259
|
+
|
|
260
|
+
case 'unique_constraint_mismatch':
|
|
261
|
+
if (!issue.table)
|
|
262
|
+
return notOk(
|
|
263
|
+
issueConflict('indexIncompatible', 'Unique constraint issue has no table name'),
|
|
264
|
+
);
|
|
265
|
+
if (isMissing(issue) && issue.expected) {
|
|
266
|
+
const columns = issue.expected.split(', ');
|
|
267
|
+
return ok([addUnique(issue.table, columns)]);
|
|
268
|
+
}
|
|
269
|
+
return notOk(
|
|
270
|
+
issueConflict(
|
|
271
|
+
'indexIncompatible',
|
|
272
|
+
`Unique constraint on "${issue.table}" differs (expected: ${issue.expected}, actual: ${issue.actual})`,
|
|
273
|
+
{ table: issue.table },
|
|
274
|
+
),
|
|
275
|
+
);
|
|
276
|
+
|
|
277
|
+
case 'index_mismatch':
|
|
278
|
+
if (!issue.table)
|
|
279
|
+
return notOk(issueConflict('indexIncompatible', 'Index issue has no table name'));
|
|
280
|
+
if (isMissing(issue) && issue.expected) {
|
|
281
|
+
const columns = issue.expected.split(', ');
|
|
282
|
+
return ok([createIndex(issue.table, columns)]);
|
|
283
|
+
}
|
|
284
|
+
return notOk(
|
|
285
|
+
issueConflict(
|
|
286
|
+
'indexIncompatible',
|
|
287
|
+
`Index on "${issue.table}" differs (expected: ${issue.expected}, actual: ${issue.actual})`,
|
|
288
|
+
{ table: issue.table },
|
|
289
|
+
),
|
|
290
|
+
);
|
|
291
|
+
|
|
292
|
+
case 'foreign_key_mismatch':
|
|
293
|
+
if (!issue.table)
|
|
294
|
+
return notOk(issueConflict('foreignKeyConflict', 'Foreign key issue has no table name'));
|
|
295
|
+
if (isMissing(issue) && issue.expected) {
|
|
296
|
+
const arrowIdx = issue.expected.indexOf(' -> ');
|
|
297
|
+
if (arrowIdx >= 0) {
|
|
298
|
+
const columns = issue.expected.slice(0, arrowIdx).split(', ');
|
|
299
|
+
return ok([addForeignKey(issue.table, columns)]);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
return notOk(
|
|
303
|
+
issueConflict(
|
|
304
|
+
'foreignKeyConflict',
|
|
305
|
+
`Foreign key on "${issue.table}" differs (expected: ${issue.expected}, actual: ${issue.actual})`,
|
|
306
|
+
{ table: issue.table },
|
|
307
|
+
),
|
|
308
|
+
);
|
|
309
|
+
|
|
310
|
+
// Types
|
|
311
|
+
case 'type_missing': {
|
|
312
|
+
if (!issue.typeName)
|
|
313
|
+
return notOk(issueConflict('unsupportedOperation', 'Type missing issue has no typeName'));
|
|
314
|
+
const typeInstance = ctx.toContract.storage.types?.[issue.typeName];
|
|
315
|
+
if (!typeInstance) {
|
|
316
|
+
return notOk(
|
|
317
|
+
issueConflict(
|
|
318
|
+
'unsupportedOperation',
|
|
319
|
+
`Type "${issue.typeName}" reported missing but not found in destination contract`,
|
|
320
|
+
),
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
// TODO: codec-specific descriptor dispatch should be driven by a registry, not hardcoded prefix checks
|
|
324
|
+
if (typeInstance.codecId.startsWith('pg/enum')) {
|
|
325
|
+
return ok([createEnumType(issue.typeName)]);
|
|
326
|
+
}
|
|
327
|
+
return notOk(
|
|
328
|
+
issueConflict(
|
|
329
|
+
'unsupportedOperation',
|
|
330
|
+
`Type "${issue.typeName}" uses codec "${typeInstance.codecId}" — only enum types are supported by the descriptor planner`,
|
|
331
|
+
),
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
case 'type_values_mismatch':
|
|
336
|
+
return notOk(
|
|
337
|
+
issueConflict(
|
|
338
|
+
'unsupportedOperation',
|
|
339
|
+
`Type "${issue.typeName ?? 'unknown'}" values differ — type alteration not yet supported by descriptor planner`,
|
|
340
|
+
),
|
|
341
|
+
);
|
|
342
|
+
|
|
343
|
+
// Dependencies
|
|
344
|
+
case 'dependency_missing':
|
|
345
|
+
if (!issue.dependencyId)
|
|
346
|
+
return notOk(
|
|
347
|
+
issueConflict('unsupportedOperation', 'Dependency missing issue has no dependencyId'),
|
|
348
|
+
);
|
|
349
|
+
return ok([createDependency(issue.dependencyId)]);
|
|
350
|
+
default:
|
|
351
|
+
return notOk(
|
|
352
|
+
issueConflict(
|
|
353
|
+
'unsupportedOperation',
|
|
354
|
+
`Unhandled issue kind: ${(issue as SchemaIssue).kind}`,
|
|
355
|
+
),
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// ============================================================================
|
|
361
|
+
// Planner entry point
|
|
362
|
+
// ============================================================================
|
|
363
|
+
|
|
364
|
+
export interface DescriptorPlannerOptions {
|
|
365
|
+
readonly issues: readonly SchemaIssue[];
|
|
366
|
+
readonly toContract: Contract<SqlStorage>;
|
|
367
|
+
readonly fromContract: Contract<SqlStorage> | null;
|
|
368
|
+
readonly strategies?: readonly MigrationStrategy[];
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
export interface DescriptorPlannerValue {
|
|
372
|
+
readonly descriptors: readonly PostgresMigrationOpDescriptor[];
|
|
373
|
+
readonly needsDataMigration: boolean;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
export function planDescriptors(
|
|
377
|
+
options: DescriptorPlannerOptions,
|
|
378
|
+
): Result<DescriptorPlannerValue, readonly SqlPlannerConflict[]> {
|
|
379
|
+
const context: StrategyContext = {
|
|
380
|
+
toContract: options.toContract,
|
|
381
|
+
fromContract: options.fromContract,
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
const strategies = options.strategies ?? migrationPlanStrategies;
|
|
385
|
+
|
|
386
|
+
// Phase 1: Pattern matching — consume recognized issues
|
|
387
|
+
let remaining = options.issues;
|
|
388
|
+
const patternOps: PostgresMigrationOpDescriptor[] = [];
|
|
389
|
+
|
|
390
|
+
for (const strategy of strategies) {
|
|
391
|
+
const result = strategy(remaining, context);
|
|
392
|
+
if (result.kind === 'match') {
|
|
393
|
+
remaining = result.issues;
|
|
394
|
+
patternOps.push(...result.ops);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// Phase 2: Sort remaining issues by dependency order
|
|
399
|
+
const sorted = [...remaining].sort((a, b) => issueOrder(a) - issueOrder(b));
|
|
400
|
+
|
|
401
|
+
// Phase 3: Map remaining issues to descriptors, collecting conflicts
|
|
402
|
+
const defaultOps: PostgresMigrationOpDescriptor[] = [];
|
|
403
|
+
const conflicts: SqlPlannerConflict[] = [];
|
|
404
|
+
|
|
405
|
+
for (const issue of sorted) {
|
|
406
|
+
const result = mapIssue(issue, context);
|
|
407
|
+
if (result.ok) {
|
|
408
|
+
defaultOps.push(...result.value);
|
|
409
|
+
} else {
|
|
410
|
+
conflicts.push(result.failure);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
if (conflicts.length > 0) {
|
|
415
|
+
return notOk(conflicts);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// Phase 4: Order descriptors by operation kind
|
|
419
|
+
const depOps = defaultOps.filter(
|
|
420
|
+
(op) =>
|
|
421
|
+
op.kind === 'createDependency' ||
|
|
422
|
+
op.kind === 'createEnumType' ||
|
|
423
|
+
op.kind === 'addEnumValues' ||
|
|
424
|
+
op.kind === 'dropEnumType' ||
|
|
425
|
+
op.kind === 'renameType',
|
|
426
|
+
);
|
|
427
|
+
const dropOps = defaultOps.filter(
|
|
428
|
+
(op) =>
|
|
429
|
+
op.kind === 'dropTable' ||
|
|
430
|
+
op.kind === 'dropColumn' ||
|
|
431
|
+
op.kind === 'dropConstraint' ||
|
|
432
|
+
op.kind === 'dropIndex' ||
|
|
433
|
+
op.kind === 'dropDefault',
|
|
434
|
+
);
|
|
435
|
+
const tableOps = defaultOps.filter((op) => op.kind === 'createTable');
|
|
436
|
+
const columnOps = defaultOps.filter((op) => op.kind === 'addColumn');
|
|
437
|
+
const alterOps = defaultOps.filter(
|
|
438
|
+
(op) =>
|
|
439
|
+
op.kind === 'alterColumnType' ||
|
|
440
|
+
op.kind === 'setNotNull' ||
|
|
441
|
+
op.kind === 'dropNotNull' ||
|
|
442
|
+
op.kind === 'setDefault',
|
|
443
|
+
);
|
|
444
|
+
const constraintOps = defaultOps.filter(
|
|
445
|
+
(op) =>
|
|
446
|
+
op.kind === 'addPrimaryKey' ||
|
|
447
|
+
op.kind === 'addUnique' ||
|
|
448
|
+
op.kind === 'createIndex' ||
|
|
449
|
+
op.kind === 'addForeignKey',
|
|
450
|
+
);
|
|
451
|
+
|
|
452
|
+
const descriptors: PostgresMigrationOpDescriptor[] = [
|
|
453
|
+
...depOps,
|
|
454
|
+
...dropOps,
|
|
455
|
+
...tableOps,
|
|
456
|
+
...columnOps,
|
|
457
|
+
...patternOps,
|
|
458
|
+
...alterOps,
|
|
459
|
+
...constraintOps,
|
|
460
|
+
];
|
|
461
|
+
|
|
462
|
+
return ok({
|
|
463
|
+
descriptors,
|
|
464
|
+
needsDataMigration: descriptors.some((op) => op.kind === 'dataTransform'),
|
|
465
|
+
});
|
|
466
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Postgres migration operation descriptors.
|
|
3
|
+
*
|
|
4
|
+
* Re-exports all structural SQL descriptors from @prisma-next/family-sql
|
|
5
|
+
* and adds data transform support with typed query builder callbacks.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { Db, TableProxyContract } from '@prisma-next/sql-builder/types';
|
|
9
|
+
import type { SqlQueryPlan } from '@prisma-next/sql-relational-core/plan';
|
|
10
|
+
|
|
11
|
+
// Re-export structural descriptors from sql-family
|
|
12
|
+
export {
|
|
13
|
+
type AddColumnDescriptor,
|
|
14
|
+
type AddEnumValuesDescriptor,
|
|
15
|
+
type AddForeignKeyDescriptor,
|
|
16
|
+
type AddPrimaryKeyDescriptor,
|
|
17
|
+
type AddUniqueDescriptor,
|
|
18
|
+
type AlterColumnTypeDescriptor,
|
|
19
|
+
addColumn,
|
|
20
|
+
addEnumValues,
|
|
21
|
+
addForeignKey,
|
|
22
|
+
addPrimaryKey,
|
|
23
|
+
addUnique,
|
|
24
|
+
alterColumnType,
|
|
25
|
+
type Buildable,
|
|
26
|
+
type CreateDependencyDescriptor,
|
|
27
|
+
type CreateEnumTypeDescriptor,
|
|
28
|
+
type CreateIndexDescriptor,
|
|
29
|
+
type CreateTableDescriptor,
|
|
30
|
+
createDependency,
|
|
31
|
+
createEnumType,
|
|
32
|
+
createIndex,
|
|
33
|
+
createTable,
|
|
34
|
+
type DropColumnDescriptor,
|
|
35
|
+
type DropConstraintDescriptor,
|
|
36
|
+
type DropDefaultDescriptor,
|
|
37
|
+
type DropEnumTypeDescriptor,
|
|
38
|
+
type DropIndexDescriptor,
|
|
39
|
+
type DropNotNullDescriptor,
|
|
40
|
+
type DropTableDescriptor,
|
|
41
|
+
dropColumn,
|
|
42
|
+
dropConstraint,
|
|
43
|
+
dropDefault,
|
|
44
|
+
dropEnumType,
|
|
45
|
+
dropIndex,
|
|
46
|
+
dropNotNull,
|
|
47
|
+
dropTable,
|
|
48
|
+
type RenameTypeDescriptor,
|
|
49
|
+
renameType,
|
|
50
|
+
type SetDefaultDescriptor,
|
|
51
|
+
type SetNotNullDescriptor,
|
|
52
|
+
type SqlMigrationOpDescriptor,
|
|
53
|
+
setDefault,
|
|
54
|
+
setNotNull,
|
|
55
|
+
TODO,
|
|
56
|
+
type TodoMarker,
|
|
57
|
+
} from '@prisma-next/family-sql/operation-descriptors';
|
|
58
|
+
|
|
59
|
+
import {
|
|
60
|
+
type Buildable,
|
|
61
|
+
type DataTransformDescriptor,
|
|
62
|
+
type SqlMigrationOpDescriptor,
|
|
63
|
+
builders as structuralBuilders,
|
|
64
|
+
TODO,
|
|
65
|
+
type TodoMarker,
|
|
66
|
+
} from '@prisma-next/family-sql/operation-descriptors';
|
|
67
|
+
|
|
68
|
+
export type { DataTransformDescriptor };
|
|
69
|
+
|
|
70
|
+
// ============================================================================
|
|
71
|
+
// Typed data transform inputs (for createBuilders<Contract>())
|
|
72
|
+
// ============================================================================
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* A single query plan input — callback, pre-built plan, or TODO placeholder.
|
|
76
|
+
* @template TContract - The contract type for the Db client. Defaults to any
|
|
77
|
+
* (untyped). Use createBuilders<Contract>() to get typed callbacks.
|
|
78
|
+
*/
|
|
79
|
+
// biome-ignore lint/suspicious/noExplicitAny: default is untyped; createBuilders narrows this
|
|
80
|
+
export type QueryPlanInput<TContract extends TableProxyContract = any> =
|
|
81
|
+
| ((db: Db<TContract>) => Buildable)
|
|
82
|
+
| SqlQueryPlan
|
|
83
|
+
| TodoMarker;
|
|
84
|
+
|
|
85
|
+
/** Run input — a callback returning one or many buildables, or a pre-built plan/TODO. */
|
|
86
|
+
// biome-ignore lint/suspicious/noExplicitAny: default is untyped; createBuilders narrows this
|
|
87
|
+
export type RunInput<TContract extends TableProxyContract = any> =
|
|
88
|
+
| ((db: Db<TContract>) => Buildable | readonly Buildable[])
|
|
89
|
+
| SqlQueryPlan
|
|
90
|
+
| TodoMarker;
|
|
91
|
+
|
|
92
|
+
// ============================================================================
|
|
93
|
+
// Postgres descriptor union = SQL structural + data transforms
|
|
94
|
+
// ============================================================================
|
|
95
|
+
|
|
96
|
+
export type PostgresMigrationOpDescriptor = SqlMigrationOpDescriptor | DataTransformDescriptor;
|
|
97
|
+
|
|
98
|
+
// ============================================================================
|
|
99
|
+
// Data transform builder
|
|
100
|
+
// ============================================================================
|
|
101
|
+
|
|
102
|
+
function resolveInput(input: QueryPlanInput): QueryPlanInput {
|
|
103
|
+
if (typeof input === 'symbol' || typeof input === 'function') return input;
|
|
104
|
+
if ('build' in input && typeof (input as Buildable).build === 'function') {
|
|
105
|
+
return (input as Buildable).build();
|
|
106
|
+
}
|
|
107
|
+
return input;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// biome-ignore lint/suspicious/noExplicitAny: default is untyped; createBuilders narrows this
|
|
111
|
+
export function dataTransform<TContract extends TableProxyContract = any>(
|
|
112
|
+
name: string,
|
|
113
|
+
options: {
|
|
114
|
+
check: QueryPlanInput<TContract> | Buildable | boolean;
|
|
115
|
+
run: RunInput<TContract> | Buildable;
|
|
116
|
+
},
|
|
117
|
+
): DataTransformDescriptor {
|
|
118
|
+
const check =
|
|
119
|
+
typeof options.check === 'boolean'
|
|
120
|
+
? options.check
|
|
121
|
+
: resolveInput(options.check as QueryPlanInput);
|
|
122
|
+
|
|
123
|
+
const run: (symbol | object | ((...args: never[]) => unknown))[] = [];
|
|
124
|
+
if (typeof options.run === 'function') {
|
|
125
|
+
run.push(options.run);
|
|
126
|
+
} else if (typeof options.run === 'symbol') {
|
|
127
|
+
run.push(options.run);
|
|
128
|
+
} else {
|
|
129
|
+
run.push(resolveInput(options.run as QueryPlanInput));
|
|
130
|
+
}
|
|
131
|
+
return {
|
|
132
|
+
kind: 'dataTransform' as const,
|
|
133
|
+
name,
|
|
134
|
+
source: 'migration.ts',
|
|
135
|
+
check,
|
|
136
|
+
run,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Creates typed migration builder functions parameterized by the contract type.
|
|
142
|
+
* The dataTransform callback receives a fully typed Db<TContract> client.
|
|
143
|
+
*
|
|
144
|
+
* Usage:
|
|
145
|
+
* ```typescript
|
|
146
|
+
* import type { Contract } from "../../src/prisma/contract.d"
|
|
147
|
+
* import { createBuilders } from "@prisma-next/target-postgres/migration-builders"
|
|
148
|
+
*
|
|
149
|
+
* const { addColumn, dataTransform, setNotNull } = createBuilders<Contract>()
|
|
150
|
+
* ```
|
|
151
|
+
*/
|
|
152
|
+
export function createBuilders<TContract extends TableProxyContract>() {
|
|
153
|
+
return {
|
|
154
|
+
...structuralBuilders,
|
|
155
|
+
dataTransform: dataTransform<TContract>,
|
|
156
|
+
TODO,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* All builder functions keyed by descriptor kind.
|
|
162
|
+
*/
|
|
163
|
+
export const builders = {
|
|
164
|
+
...structuralBuilders,
|
|
165
|
+
dataTransform,
|
|
166
|
+
} as const;
|