@prisma-next/target-postgres 0.4.0-dev.9 → 0.4.1
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/control.d.mts +1 -9
- package/dist/control.d.mts.map +1 -1
- package/dist/control.mjs +1693 -4798
- package/dist/control.mjs.map +1 -1
- package/dist/migration.d.mts +164 -0
- package/dist/migration.d.mts.map +1 -0
- package/dist/migration.mjs +446 -0
- package/dist/migration.mjs.map +1 -0
- package/dist/planner-target-details-MXb3oeul.d.mts +11 -0
- package/dist/planner-target-details-MXb3oeul.d.mts.map +1 -0
- package/dist/postgres-migration-BsHJHV9O.mjs +2793 -0
- package/dist/postgres-migration-BsHJHV9O.mjs.map +1 -0
- package/package.json +20 -18
- package/src/core/migrations/issue-planner.ts +832 -0
- package/src/core/migrations/op-factory-call.ts +862 -0
- package/src/core/migrations/operations/columns.ts +285 -0
- package/src/core/migrations/operations/constraints.ts +191 -0
- package/src/core/migrations/operations/data-transform.ts +113 -0
- package/src/core/migrations/operations/dependencies.ts +36 -0
- package/src/core/migrations/operations/enums.ts +113 -0
- package/src/core/migrations/operations/indexes.ts +61 -0
- package/src/core/migrations/operations/raw.ts +15 -0
- package/src/core/migrations/operations/shared.ts +67 -0
- package/src/core/migrations/operations/tables.ts +63 -0
- package/src/core/migrations/planner-produced-postgres-migration.ts +67 -0
- package/src/core/migrations/planner-strategies.ts +592 -151
- package/src/core/migrations/planner-target-details.ts +0 -6
- package/src/core/migrations/planner.ts +63 -781
- package/src/core/migrations/postgres-migration.ts +20 -0
- package/src/core/migrations/render-ops.ts +9 -0
- package/src/core/migrations/render-typescript.ts +95 -0
- package/src/exports/control.ts +9 -142
- package/src/exports/migration.ts +40 -0
- package/dist/migration-builders.d.mts +0 -88
- package/dist/migration-builders.d.mts.map +0 -1
- package/dist/migration-builders.mjs +0 -3
- package/dist/operation-descriptors-CxymFSgK.mjs +0 -52
- package/dist/operation-descriptors-CxymFSgK.mjs.map +0 -1
- package/src/core/migrations/descriptor-planner.ts +0 -464
- package/src/core/migrations/operation-descriptors.ts +0 -166
- package/src/core/migrations/operation-resolver.ts +0 -929
- package/src/core/migrations/planner-reconciliation.ts +0 -798
- package/src/core/migrations/scaffolding.ts +0 -140
- package/src/exports/migration-builders.ts +0 -56
|
@@ -1,24 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
normalizeSchemaNativeType,
|
|
3
3
|
parsePostgresDefault,
|
|
4
|
-
quoteIdentifier,
|
|
5
4
|
} from '@prisma-next/adapter-postgres/control';
|
|
6
|
-
import { errorPlanDoesNotSupportAuthoringSurface } from '@prisma-next/errors/migration';
|
|
7
5
|
import type {
|
|
8
|
-
CodecControlHooks,
|
|
9
|
-
ComponentDatabaseDependency,
|
|
10
6
|
MigrationOperationPolicy,
|
|
11
7
|
SqlMigrationPlannerPlanOptions,
|
|
12
|
-
SqlMigrationPlanOperation,
|
|
13
|
-
SqlPlannerConflict,
|
|
14
8
|
SqlPlannerFailureResult,
|
|
15
9
|
} from '@prisma-next/family-sql/control';
|
|
16
|
-
import {
|
|
17
|
-
collectInitDependencies,
|
|
18
|
-
createMigrationPlan,
|
|
19
|
-
extractCodecControlHooks,
|
|
20
|
-
plannerFailure,
|
|
21
|
-
} from '@prisma-next/family-sql/control';
|
|
10
|
+
import { extractCodecControlHooks, plannerFailure } from '@prisma-next/family-sql/control';
|
|
22
11
|
import { verifySqlSchema } from '@prisma-next/family-sql/schema-verify';
|
|
23
12
|
import type { TargetBoundComponentDescriptor } from '@prisma-next/framework-components/components';
|
|
24
13
|
import type {
|
|
@@ -27,44 +16,9 @@ import type {
|
|
|
27
16
|
MigrationScaffoldContext,
|
|
28
17
|
SchemaIssue,
|
|
29
18
|
} from '@prisma-next/framework-components/control';
|
|
30
|
-
import
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
StorageTypeInstance,
|
|
34
|
-
} from '@prisma-next/sql-contract/types';
|
|
35
|
-
import { defaultIndexName } from '@prisma-next/sql-schema-ir/naming';
|
|
36
|
-
import type { SqlSchemaIR } from '@prisma-next/sql-schema-ir/types';
|
|
37
|
-
import { ifDefined } from '@prisma-next/utils/defined';
|
|
38
|
-
import { buildAddColumnSql, buildCreateTableSql, buildForeignKeySql } from './planner-ddl-builders';
|
|
39
|
-
import { resolveIdentityValue } from './planner-identity-values';
|
|
40
|
-
import {
|
|
41
|
-
buildAddColumnOperationIdentity,
|
|
42
|
-
buildAddNotNullColumnWithTemporaryDefaultOperation,
|
|
43
|
-
} from './planner-recipes';
|
|
44
|
-
import { buildReconciliationPlan } from './planner-reconciliation';
|
|
45
|
-
import {
|
|
46
|
-
buildSchemaLookupMap,
|
|
47
|
-
hasForeignKey,
|
|
48
|
-
hasIndex,
|
|
49
|
-
hasUniqueConstraint,
|
|
50
|
-
type SchemaTableLookup,
|
|
51
|
-
} from './planner-schema-lookup';
|
|
52
|
-
import {
|
|
53
|
-
columnExistsCheck,
|
|
54
|
-
columnNullabilityCheck,
|
|
55
|
-
constraintExistsCheck,
|
|
56
|
-
qualifyTableName,
|
|
57
|
-
tableHasPrimaryKeyCheck,
|
|
58
|
-
tableIsEmptyCheck,
|
|
59
|
-
toRegclassLiteral,
|
|
60
|
-
} from './planner-sql-checks';
|
|
61
|
-
import {
|
|
62
|
-
buildTargetDetails,
|
|
63
|
-
type OperationClass,
|
|
64
|
-
type PlanningMode,
|
|
65
|
-
type PostgresPlanTargetDetails,
|
|
66
|
-
} from './planner-target-details';
|
|
67
|
-
import { renderDescriptorTypeScript } from './scaffolding';
|
|
19
|
+
import { planIssues } from './issue-planner';
|
|
20
|
+
import { TypeScriptRenderablePostgresMigration } from './planner-produced-postgres-migration';
|
|
21
|
+
import { postgresPlannerStrategies } from './planner-strategies';
|
|
68
22
|
|
|
69
23
|
type PlannerFrameworkComponents = SqlMigrationPlannerPlanOptions extends {
|
|
70
24
|
readonly frameworkComponents: infer T;
|
|
@@ -80,12 +34,6 @@ type VerifySqlSchemaOptionsWithComponents = Parameters<typeof verifySqlSchema>[0
|
|
|
80
34
|
readonly frameworkComponents: PlannerFrameworkComponents;
|
|
81
35
|
};
|
|
82
36
|
|
|
83
|
-
type PlannerDatabaseDependency = {
|
|
84
|
-
readonly id: string;
|
|
85
|
-
readonly label: string;
|
|
86
|
-
readonly install: readonly SqlMigrationPlanOperation<PostgresPlanTargetDetails>[];
|
|
87
|
-
};
|
|
88
|
-
|
|
89
37
|
interface PlannerConfig {
|
|
90
38
|
readonly defaultSchema: string;
|
|
91
39
|
}
|
|
@@ -104,44 +52,31 @@ export function createPostgresMigrationPlanner(
|
|
|
104
52
|
}
|
|
105
53
|
|
|
106
54
|
/**
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
typeof createMigrationPlan<PostgresPlanTargetDetails>
|
|
113
|
-
> & {
|
|
114
|
-
renderTypeScript(): string;
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Result of `PostgresMigrationPlanner.plan()`. A discriminated union that
|
|
119
|
-
* satisfies both the framework's `MigrationPlannerResult` (success plan
|
|
120
|
-
* carries `renderTypeScript()`) and the SQL family's typed result shape
|
|
121
|
-
* (success plan is a `SqlMigrationPlan<PostgresPlanTargetDetails>`).
|
|
55
|
+
* Result of `PostgresMigrationPlanner.plan()`. A discriminated union whose
|
|
56
|
+
* success variant carries a `TypeScriptRenderablePostgresMigration` — a
|
|
57
|
+
* migration object that both the CLI (via `renderTypeScript()`) and the
|
|
58
|
+
* SQL-typed callers (via `operations`, `describe()`, etc.) consume
|
|
59
|
+
* uniformly.
|
|
122
60
|
*/
|
|
123
61
|
export type PostgresPlanResult =
|
|
124
|
-
| { readonly kind: 'success'; readonly plan:
|
|
62
|
+
| { readonly kind: 'success'; readonly plan: TypeScriptRenderablePostgresMigration }
|
|
125
63
|
| SqlPlannerFailureResult;
|
|
126
64
|
|
|
127
65
|
/**
|
|
128
|
-
* Postgres migration planner
|
|
129
|
-
*
|
|
130
|
-
* Implements the framework's `MigrationPlanner<'sql', 'postgres'>` directly
|
|
131
|
-
* — meaning it owns `emptyMigration()` and attaches the `renderTypeScript()`
|
|
132
|
-
* stub to success plans (Postgres uses descriptor-flow authoring, so
|
|
133
|
-
* `renderTypeScript()` throws on planner-produced plans). No external wrapper
|
|
134
|
-
* is required at the descriptor level.
|
|
66
|
+
* Postgres migration planner — a thin wrapper over `planIssues`.
|
|
135
67
|
*
|
|
136
|
-
* `plan()`
|
|
137
|
-
*
|
|
138
|
-
* `
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
* `
|
|
143
|
-
*
|
|
144
|
-
* `
|
|
68
|
+
* `plan()` verifies the live schema against the target contract (producing
|
|
69
|
+
* `SchemaIssue[]`) and delegates to `planIssues` with the unified
|
|
70
|
+
* `postgresPlannerStrategies` list: enum-change, NOT-NULL backfill,
|
|
71
|
+
* type-change, nullable-tightening, codec-hook storage types,
|
|
72
|
+
* component-declared dependency installs, and shared-temp-default /
|
|
73
|
+
* empty-table-guarded NOT-NULL add-column. The same strategy list runs for
|
|
74
|
+
* `migration plan`, `db update`, and `db init`; behavior diverges purely on
|
|
75
|
+
* `policy.allowedOperationClasses` (the data-safe strategies short-circuit
|
|
76
|
+
* when `'data'` is excluded). The issue planner applies operation-class
|
|
77
|
+
* policy gates and emits a single `PostgresOpFactoryCall[]` that drives both
|
|
78
|
+
* the runtime-ops view (via `renderOps`) and the `renderTypeScript()`
|
|
79
|
+
* authoring surface.
|
|
145
80
|
*/
|
|
146
81
|
export class PostgresMigrationPlanner implements MigrationPlanner<'sql', 'postgres'> {
|
|
147
82
|
constructor(private readonly config: PlannerConfig) {}
|
|
@@ -151,107 +86,66 @@ export class PostgresMigrationPlanner implements MigrationPlanner<'sql', 'postgr
|
|
|
151
86
|
readonly schema: unknown;
|
|
152
87
|
readonly policy: MigrationOperationPolicy;
|
|
153
88
|
readonly fromHash?: string;
|
|
89
|
+
/**
|
|
90
|
+
* The "from" contract (state the planner assumes the database starts
|
|
91
|
+
* at). Only `migration plan` supplies this; `db update` / `db init`
|
|
92
|
+
* reconcile against the live schema with no old contract. When present
|
|
93
|
+
* alongside the `'data'` operation class, strategies that need from/to
|
|
94
|
+
* column shape comparisons (unsafe type change, nullability tightening)
|
|
95
|
+
* activate.
|
|
96
|
+
*/
|
|
97
|
+
readonly fromContract?: unknown;
|
|
154
98
|
readonly schemaName?: string;
|
|
155
99
|
readonly frameworkComponents: ReadonlyArray<TargetBoundComponentDescriptor<'sql', string>>;
|
|
156
100
|
}): PostgresPlanResult {
|
|
157
|
-
return this.planSql(options as SqlMigrationPlannerPlanOptions);
|
|
101
|
+
return this.planSql(options as SqlMigrationPlannerPlanOptions, options.fromHash ?? '');
|
|
158
102
|
}
|
|
159
103
|
|
|
160
104
|
emptyMigration(context: MigrationScaffoldContext): MigrationPlanWithAuthoringSurface {
|
|
161
|
-
return {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
renderTypeScript(): string {
|
|
166
|
-
return renderDescriptorTypeScript([], context);
|
|
167
|
-
},
|
|
168
|
-
};
|
|
105
|
+
return new TypeScriptRenderablePostgresMigration([], {
|
|
106
|
+
from: context.fromHash,
|
|
107
|
+
to: context.toHash,
|
|
108
|
+
});
|
|
169
109
|
}
|
|
170
110
|
|
|
171
|
-
private planSql(options: SqlMigrationPlannerPlanOptions): PostgresPlanResult {
|
|
111
|
+
private planSql(options: SqlMigrationPlannerPlanOptions, fromHash: string): PostgresPlanResult {
|
|
172
112
|
const schemaName = options.schemaName ?? this.config.defaultSchema;
|
|
173
113
|
const policyResult = this.ensureAdditivePolicy(options.policy);
|
|
174
114
|
if (policyResult) {
|
|
175
115
|
return policyResult;
|
|
176
116
|
}
|
|
177
117
|
|
|
178
|
-
const
|
|
179
|
-
const schemaIssues = this.collectSchemaIssues(options, planningMode.includeExtraObjects);
|
|
180
|
-
|
|
181
|
-
// Extract codec control hooks once at entry point for reuse across all operations.
|
|
182
|
-
// This avoids repeated iteration over frameworkComponents for each method that needs hooks.
|
|
118
|
+
const schemaIssues = this.collectSchemaIssues(options);
|
|
183
119
|
const codecHooks = extractCodecControlHooks(options.frameworkComponents);
|
|
184
|
-
|
|
185
|
-
const operations: SqlMigrationPlanOperation<PostgresPlanTargetDetails>[] = [];
|
|
186
120
|
const storageTypes = options.contract.storage.types ?? {};
|
|
187
121
|
|
|
188
|
-
const
|
|
189
|
-
contract: options.contract,
|
|
122
|
+
const result = planIssues({
|
|
190
123
|
issues: schemaIssues,
|
|
124
|
+
toContract: options.contract,
|
|
125
|
+
// `fromContract` is only supplied by `migration plan`. It is `null` for
|
|
126
|
+
// `db update` / `db init`, which means data-safety strategies needing
|
|
127
|
+
// from/to comparisons (unsafe type change, nullable tightening) are
|
|
128
|
+
// inapplicable there — reconciliation falls through to
|
|
129
|
+
// `mapIssueToCall`'s direct destructive handlers.
|
|
130
|
+
fromContract: options.fromContract ?? null,
|
|
191
131
|
schemaName,
|
|
192
|
-
mode: planningMode,
|
|
193
|
-
policy: options.policy,
|
|
194
132
|
codecHooks,
|
|
133
|
+
storageTypes,
|
|
134
|
+
schema: options.schema,
|
|
135
|
+
policy: options.policy,
|
|
136
|
+
frameworkComponents: options.frameworkComponents,
|
|
137
|
+
strategies: postgresPlannerStrategies,
|
|
195
138
|
});
|
|
196
|
-
if (reconciliationPlan.conflicts.length > 0) {
|
|
197
|
-
return plannerFailure(reconciliationPlan.conflicts);
|
|
198
|
-
}
|
|
199
139
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
return plannerFailure(storageTypePlan.conflicts);
|
|
140
|
+
if (!result.ok) {
|
|
141
|
+
return plannerFailure(result.failure);
|
|
203
142
|
}
|
|
204
143
|
|
|
205
|
-
// Sort table entries once for reuse across all additive operation builders.
|
|
206
|
-
const sortedTables = sortedEntries(options.contract.storage.tables);
|
|
207
|
-
|
|
208
|
-
// Pre-compute constraint lookups once per schema table for O(1) checks across all builders.
|
|
209
|
-
const schemaLookups = buildSchemaLookupMap(options.schema);
|
|
210
|
-
|
|
211
|
-
// Build extension operations from component-owned database dependencies
|
|
212
|
-
operations.push(
|
|
213
|
-
...this.buildDatabaseDependencyOperations(options),
|
|
214
|
-
...storageTypePlan.operations,
|
|
215
|
-
...reconciliationPlan.operations,
|
|
216
|
-
...this.buildTableOperations(
|
|
217
|
-
sortedTables,
|
|
218
|
-
options.schema,
|
|
219
|
-
schemaName,
|
|
220
|
-
codecHooks,
|
|
221
|
-
storageTypes,
|
|
222
|
-
),
|
|
223
|
-
...this.buildColumnOperations(
|
|
224
|
-
sortedTables,
|
|
225
|
-
options.schema,
|
|
226
|
-
schemaLookups,
|
|
227
|
-
schemaName,
|
|
228
|
-
codecHooks,
|
|
229
|
-
storageTypes,
|
|
230
|
-
),
|
|
231
|
-
...this.buildPrimaryKeyOperations(sortedTables, options.schema, schemaName),
|
|
232
|
-
...this.buildUniqueOperations(sortedTables, schemaLookups, schemaName),
|
|
233
|
-
...this.buildIndexOperations(sortedTables, schemaLookups, schemaName),
|
|
234
|
-
...this.buildFkBackingIndexOperations(sortedTables, schemaLookups, schemaName),
|
|
235
|
-
...this.buildForeignKeyOperations(sortedTables, schemaLookups, schemaName),
|
|
236
|
-
);
|
|
237
|
-
|
|
238
|
-
const plan = createMigrationPlan<PostgresPlanTargetDetails>({
|
|
239
|
-
targetId: 'postgres',
|
|
240
|
-
origin: null,
|
|
241
|
-
destination: {
|
|
242
|
-
storageHash: options.contract.storage.storageHash,
|
|
243
|
-
...ifDefined('profileHash', options.contract.profileHash),
|
|
244
|
-
},
|
|
245
|
-
operations,
|
|
246
|
-
});
|
|
247
|
-
|
|
248
144
|
return Object.freeze({
|
|
249
145
|
kind: 'success' as const,
|
|
250
|
-
plan:
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
throw errorPlanDoesNotSupportAuthoringSurface({ targetId: 'postgres' });
|
|
254
|
-
},
|
|
146
|
+
plan: new TypeScriptRenderablePostgresMigration(result.value.calls, {
|
|
147
|
+
from: fromHash,
|
|
148
|
+
to: options.contract.storage.storageHash,
|
|
255
149
|
}),
|
|
256
150
|
});
|
|
257
151
|
}
|
|
@@ -269,573 +163,12 @@ export class PostgresMigrationPlanner implements MigrationPlanner<'sql', 'postgr
|
|
|
269
163
|
return null;
|
|
270
164
|
}
|
|
271
165
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
): readonly SqlMigrationPlanOperation<PostgresPlanTargetDetails>[] {
|
|
279
|
-
const dependencies = this.collectDependencies(options);
|
|
280
|
-
const operations: SqlMigrationPlanOperation<PostgresPlanTargetDetails>[] = [];
|
|
281
|
-
const seenDependencyIds = new Set<string>();
|
|
282
|
-
const seenOperationIds = new Set<string>();
|
|
283
|
-
|
|
284
|
-
const installedIds = new Set(options.schema.dependencies.map((d) => d.id));
|
|
285
|
-
|
|
286
|
-
for (const dependency of dependencies) {
|
|
287
|
-
if (seenDependencyIds.has(dependency.id)) {
|
|
288
|
-
continue;
|
|
289
|
-
}
|
|
290
|
-
seenDependencyIds.add(dependency.id);
|
|
291
|
-
|
|
292
|
-
if (installedIds.has(dependency.id)) {
|
|
293
|
-
continue;
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
for (const installOp of dependency.install) {
|
|
297
|
-
if (seenOperationIds.has(installOp.id)) {
|
|
298
|
-
continue;
|
|
299
|
-
}
|
|
300
|
-
seenOperationIds.add(installOp.id);
|
|
301
|
-
operations.push(installOp as SqlMigrationPlanOperation<PostgresPlanTargetDetails>);
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
return operations;
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
private buildStorageTypeOperations(
|
|
309
|
-
options: PlannerOptionsWithComponents,
|
|
310
|
-
schemaName: string,
|
|
311
|
-
codecHooks: Map<string, CodecControlHooks>,
|
|
312
|
-
): {
|
|
313
|
-
readonly operations: readonly SqlMigrationPlanOperation<PostgresPlanTargetDetails>[];
|
|
314
|
-
readonly conflicts: readonly SqlPlannerConflict[];
|
|
315
|
-
} {
|
|
316
|
-
const operations: SqlMigrationPlanOperation<PostgresPlanTargetDetails>[] = [];
|
|
317
|
-
const conflicts: SqlPlannerConflict[] = [];
|
|
318
|
-
const storageTypes = options.contract.storage.types ?? {};
|
|
319
|
-
|
|
320
|
-
for (const [typeName, typeInstance] of sortedEntries(storageTypes)) {
|
|
321
|
-
const hook = codecHooks.get(typeInstance.codecId);
|
|
322
|
-
const planResult = hook?.planTypeOperations?.({
|
|
323
|
-
typeName,
|
|
324
|
-
typeInstance,
|
|
325
|
-
contract: options.contract,
|
|
326
|
-
schema: options.schema,
|
|
327
|
-
schemaName,
|
|
328
|
-
policy: options.policy,
|
|
329
|
-
});
|
|
330
|
-
if (!planResult) {
|
|
331
|
-
continue;
|
|
332
|
-
}
|
|
333
|
-
for (const operation of planResult.operations) {
|
|
334
|
-
if (!options.policy.allowedOperationClasses.includes(operation.operationClass)) {
|
|
335
|
-
conflicts.push({
|
|
336
|
-
kind: 'missingButNonAdditive',
|
|
337
|
-
summary: `Storage type "${typeName}" requires "${operation.operationClass}" operation "${operation.id}"`,
|
|
338
|
-
location: {
|
|
339
|
-
type: typeName,
|
|
340
|
-
},
|
|
341
|
-
});
|
|
342
|
-
continue;
|
|
343
|
-
}
|
|
344
|
-
operations.push({
|
|
345
|
-
...operation,
|
|
346
|
-
target: {
|
|
347
|
-
id: operation.target.id,
|
|
348
|
-
details: this.buildTargetDetails('type', typeName, schemaName),
|
|
349
|
-
},
|
|
350
|
-
});
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
return { operations, conflicts };
|
|
355
|
-
}
|
|
356
|
-
private collectDependencies(
|
|
357
|
-
options: PlannerOptionsWithComponents,
|
|
358
|
-
): ReadonlyArray<PlannerDatabaseDependency> {
|
|
359
|
-
const dependencies = collectInitDependencies(options.frameworkComponents);
|
|
360
|
-
return sortDependencies(dependencies.filter(isPostgresPlannerDependency));
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
private buildTableOperations(
|
|
364
|
-
tables: ReadonlyArray<[string, StorageTable]>,
|
|
365
|
-
schema: SqlSchemaIR,
|
|
366
|
-
schemaName: string,
|
|
367
|
-
codecHooks: Map<string, CodecControlHooks>,
|
|
368
|
-
storageTypes: Record<string, StorageTypeInstance>,
|
|
369
|
-
): readonly SqlMigrationPlanOperation<PostgresPlanTargetDetails>[] {
|
|
370
|
-
const operations: SqlMigrationPlanOperation<PostgresPlanTargetDetails>[] = [];
|
|
371
|
-
for (const [tableName, table] of tables) {
|
|
372
|
-
if (schema.tables[tableName]) {
|
|
373
|
-
continue;
|
|
374
|
-
}
|
|
375
|
-
const qualified = qualifyTableName(schemaName, tableName);
|
|
376
|
-
operations.push({
|
|
377
|
-
id: `table.${tableName}`,
|
|
378
|
-
label: `Create table ${tableName}`,
|
|
379
|
-
summary: `Creates table ${tableName} with required columns`,
|
|
380
|
-
operationClass: 'additive',
|
|
381
|
-
target: {
|
|
382
|
-
id: 'postgres',
|
|
383
|
-
details: this.buildTargetDetails('table', tableName, schemaName),
|
|
384
|
-
},
|
|
385
|
-
precheck: [
|
|
386
|
-
{
|
|
387
|
-
description: `ensure table "${tableName}" does not exist`,
|
|
388
|
-
sql: `SELECT to_regclass(${toRegclassLiteral(schemaName, tableName)}) IS NULL`,
|
|
389
|
-
},
|
|
390
|
-
],
|
|
391
|
-
execute: [
|
|
392
|
-
{
|
|
393
|
-
description: `create table "${tableName}"`,
|
|
394
|
-
sql: buildCreateTableSql(qualified, table, codecHooks, storageTypes),
|
|
395
|
-
},
|
|
396
|
-
],
|
|
397
|
-
postcheck: [
|
|
398
|
-
{
|
|
399
|
-
description: `verify table "${tableName}" exists`,
|
|
400
|
-
sql: `SELECT to_regclass(${toRegclassLiteral(schemaName, tableName)}) IS NOT NULL`,
|
|
401
|
-
},
|
|
402
|
-
],
|
|
403
|
-
});
|
|
404
|
-
}
|
|
405
|
-
return operations;
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
private buildColumnOperations(
|
|
409
|
-
tables: ReadonlyArray<[string, StorageTable]>,
|
|
410
|
-
schema: SqlSchemaIR,
|
|
411
|
-
schemaLookups: ReadonlyMap<string, SchemaTableLookup>,
|
|
412
|
-
schemaName: string,
|
|
413
|
-
codecHooks: Map<string, CodecControlHooks>,
|
|
414
|
-
storageTypes: Record<string, StorageTypeInstance>,
|
|
415
|
-
): readonly SqlMigrationPlanOperation<PostgresPlanTargetDetails>[] {
|
|
416
|
-
const operations: SqlMigrationPlanOperation<PostgresPlanTargetDetails>[] = [];
|
|
417
|
-
for (const [tableName, table] of tables) {
|
|
418
|
-
const schemaTable = schema.tables[tableName];
|
|
419
|
-
if (!schemaTable) {
|
|
420
|
-
continue;
|
|
421
|
-
}
|
|
422
|
-
const schemaLookup = schemaLookups.get(tableName);
|
|
423
|
-
for (const [columnName, column] of sortedEntries(table.columns)) {
|
|
424
|
-
if (schemaTable.columns[columnName]) {
|
|
425
|
-
continue;
|
|
426
|
-
}
|
|
427
|
-
operations.push(
|
|
428
|
-
this.buildAddColumnOperation({
|
|
429
|
-
schema: schemaName,
|
|
430
|
-
tableName,
|
|
431
|
-
table,
|
|
432
|
-
schemaTable,
|
|
433
|
-
schemaLookup,
|
|
434
|
-
columnName,
|
|
435
|
-
column,
|
|
436
|
-
codecHooks,
|
|
437
|
-
storageTypes,
|
|
438
|
-
}),
|
|
439
|
-
);
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
return operations;
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
private buildAddColumnOperation(options: {
|
|
446
|
-
readonly schema: string;
|
|
447
|
-
readonly tableName: string;
|
|
448
|
-
readonly table: StorageTable;
|
|
449
|
-
readonly schemaTable: SqlSchemaIR['tables'][string];
|
|
450
|
-
readonly schemaLookup: SchemaTableLookup | undefined;
|
|
451
|
-
readonly columnName: string;
|
|
452
|
-
readonly column: StorageColumn;
|
|
453
|
-
readonly codecHooks: Map<string, CodecControlHooks>;
|
|
454
|
-
readonly storageTypes: Record<string, StorageTypeInstance>;
|
|
455
|
-
}): SqlMigrationPlanOperation<PostgresPlanTargetDetails> {
|
|
456
|
-
const {
|
|
457
|
-
schema,
|
|
458
|
-
tableName,
|
|
459
|
-
table,
|
|
460
|
-
schemaTable,
|
|
461
|
-
schemaLookup,
|
|
462
|
-
columnName,
|
|
463
|
-
column,
|
|
464
|
-
codecHooks,
|
|
465
|
-
storageTypes,
|
|
466
|
-
} = options;
|
|
467
|
-
const notNull = !column.nullable;
|
|
468
|
-
const hasDefault = column.default !== undefined;
|
|
469
|
-
// Planner logic decides whether this column needs the coordinated multi-step
|
|
470
|
-
// strategy. The strategy recipe itself is built by a dedicated helper.
|
|
471
|
-
const needsTemporaryDefault = notNull && !hasDefault;
|
|
472
|
-
const temporaryDefault = needsTemporaryDefault
|
|
473
|
-
? resolveIdentityValue(column, codecHooks, storageTypes)
|
|
474
|
-
: null;
|
|
475
|
-
const canUseSharedTemporaryDefault =
|
|
476
|
-
needsTemporaryDefault &&
|
|
477
|
-
temporaryDefault !== null &&
|
|
478
|
-
canUseSharedTemporaryDefaultStrategy({
|
|
479
|
-
table,
|
|
480
|
-
schemaTable,
|
|
481
|
-
schemaLookup,
|
|
482
|
-
columnName,
|
|
483
|
-
});
|
|
484
|
-
|
|
485
|
-
if (canUseSharedTemporaryDefault) {
|
|
486
|
-
return buildAddNotNullColumnWithTemporaryDefaultOperation({
|
|
487
|
-
schema,
|
|
488
|
-
tableName,
|
|
489
|
-
columnName,
|
|
490
|
-
column,
|
|
491
|
-
codecHooks,
|
|
492
|
-
storageTypes,
|
|
493
|
-
temporaryDefault,
|
|
494
|
-
});
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
const qualified = qualifyTableName(schema, tableName);
|
|
498
|
-
const requiresEmptyTableCheck = needsTemporaryDefault && !canUseSharedTemporaryDefault;
|
|
499
|
-
return {
|
|
500
|
-
...buildAddColumnOperationIdentity(schema, tableName, columnName),
|
|
501
|
-
operationClass: 'additive',
|
|
502
|
-
precheck: [
|
|
503
|
-
{
|
|
504
|
-
description: `ensure column "${columnName}" is missing`,
|
|
505
|
-
sql: columnExistsCheck({ schema, table: tableName, column: columnName, exists: false }),
|
|
506
|
-
},
|
|
507
|
-
...(requiresEmptyTableCheck
|
|
508
|
-
? [
|
|
509
|
-
{
|
|
510
|
-
description: `ensure table "${tableName}" is empty before adding NOT NULL column without default`,
|
|
511
|
-
sql: tableIsEmptyCheck(qualified),
|
|
512
|
-
},
|
|
513
|
-
]
|
|
514
|
-
: []),
|
|
515
|
-
],
|
|
516
|
-
execute: [
|
|
517
|
-
{
|
|
518
|
-
description: `add column "${columnName}"`,
|
|
519
|
-
sql: buildAddColumnSql(
|
|
520
|
-
qualified,
|
|
521
|
-
columnName,
|
|
522
|
-
column,
|
|
523
|
-
codecHooks,
|
|
524
|
-
undefined,
|
|
525
|
-
storageTypes,
|
|
526
|
-
),
|
|
527
|
-
},
|
|
528
|
-
],
|
|
529
|
-
postcheck: [
|
|
530
|
-
{
|
|
531
|
-
description: `verify column "${columnName}" exists`,
|
|
532
|
-
sql: columnExistsCheck({ schema, table: tableName, column: columnName }),
|
|
533
|
-
},
|
|
534
|
-
...(notNull
|
|
535
|
-
? [
|
|
536
|
-
{
|
|
537
|
-
description: `verify column "${columnName}" is NOT NULL`,
|
|
538
|
-
sql: columnNullabilityCheck({
|
|
539
|
-
schema,
|
|
540
|
-
table: tableName,
|
|
541
|
-
column: columnName,
|
|
542
|
-
nullable: false,
|
|
543
|
-
}),
|
|
544
|
-
},
|
|
545
|
-
]
|
|
546
|
-
: []),
|
|
547
|
-
],
|
|
548
|
-
};
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
private buildPrimaryKeyOperations(
|
|
552
|
-
tables: ReadonlyArray<[string, StorageTable]>,
|
|
553
|
-
schema: SqlSchemaIR,
|
|
554
|
-
schemaName: string,
|
|
555
|
-
): readonly SqlMigrationPlanOperation<PostgresPlanTargetDetails>[] {
|
|
556
|
-
const operations: SqlMigrationPlanOperation<PostgresPlanTargetDetails>[] = [];
|
|
557
|
-
for (const [tableName, table] of tables) {
|
|
558
|
-
if (!table.primaryKey) {
|
|
559
|
-
continue;
|
|
560
|
-
}
|
|
561
|
-
const schemaTable = schema.tables[tableName];
|
|
562
|
-
if (!schemaTable || schemaTable.primaryKey) {
|
|
563
|
-
continue;
|
|
564
|
-
}
|
|
565
|
-
const constraintName = table.primaryKey.name ?? `${tableName}_pkey`;
|
|
566
|
-
operations.push({
|
|
567
|
-
id: `primaryKey.${tableName}.${constraintName}`,
|
|
568
|
-
label: `Add primary key ${constraintName} on ${tableName}`,
|
|
569
|
-
summary: `Adds primary key ${constraintName} on ${tableName}`,
|
|
570
|
-
operationClass: 'additive',
|
|
571
|
-
target: {
|
|
572
|
-
id: 'postgres',
|
|
573
|
-
details: this.buildTargetDetails('table', tableName, schemaName),
|
|
574
|
-
},
|
|
575
|
-
precheck: [
|
|
576
|
-
{
|
|
577
|
-
description: `ensure primary key does not exist on "${tableName}"`,
|
|
578
|
-
sql: tableHasPrimaryKeyCheck(schemaName, tableName, false),
|
|
579
|
-
},
|
|
580
|
-
],
|
|
581
|
-
execute: [
|
|
582
|
-
{
|
|
583
|
-
description: `add primary key "${constraintName}"`,
|
|
584
|
-
sql: `ALTER TABLE ${qualifyTableName(schemaName, tableName)}
|
|
585
|
-
ADD CONSTRAINT ${quoteIdentifier(constraintName)}
|
|
586
|
-
PRIMARY KEY (${table.primaryKey.columns.map(quoteIdentifier).join(', ')})`,
|
|
587
|
-
},
|
|
588
|
-
],
|
|
589
|
-
postcheck: [
|
|
590
|
-
{
|
|
591
|
-
description: `verify primary key "${constraintName}" exists`,
|
|
592
|
-
sql: tableHasPrimaryKeyCheck(schemaName, tableName, true, constraintName),
|
|
593
|
-
},
|
|
594
|
-
],
|
|
595
|
-
});
|
|
596
|
-
}
|
|
597
|
-
return operations;
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
private buildUniqueOperations(
|
|
601
|
-
tables: ReadonlyArray<[string, StorageTable]>,
|
|
602
|
-
schemaLookups: ReadonlyMap<string, SchemaTableLookup>,
|
|
603
|
-
schemaName: string,
|
|
604
|
-
): readonly SqlMigrationPlanOperation<PostgresPlanTargetDetails>[] {
|
|
605
|
-
const operations: SqlMigrationPlanOperation<PostgresPlanTargetDetails>[] = [];
|
|
606
|
-
for (const [tableName, table] of tables) {
|
|
607
|
-
const lookup = schemaLookups.get(tableName);
|
|
608
|
-
for (const unique of table.uniques) {
|
|
609
|
-
if (lookup && hasUniqueConstraint(lookup, unique.columns)) {
|
|
610
|
-
continue;
|
|
611
|
-
}
|
|
612
|
-
const constraintName = unique.name ?? `${tableName}_${unique.columns.join('_')}_key`;
|
|
613
|
-
operations.push({
|
|
614
|
-
id: `unique.${tableName}.${constraintName}`,
|
|
615
|
-
label: `Add unique constraint ${constraintName} on ${tableName}`,
|
|
616
|
-
summary: `Adds unique constraint ${constraintName} on ${tableName}`,
|
|
617
|
-
operationClass: 'additive',
|
|
618
|
-
target: {
|
|
619
|
-
id: 'postgres',
|
|
620
|
-
details: this.buildTargetDetails('unique', constraintName, schemaName, tableName),
|
|
621
|
-
},
|
|
622
|
-
precheck: [
|
|
623
|
-
{
|
|
624
|
-
description: `ensure unique constraint "${constraintName}" is missing`,
|
|
625
|
-
sql: constraintExistsCheck({
|
|
626
|
-
constraintName,
|
|
627
|
-
schema: schemaName,
|
|
628
|
-
table: tableName,
|
|
629
|
-
exists: false,
|
|
630
|
-
}),
|
|
631
|
-
},
|
|
632
|
-
],
|
|
633
|
-
execute: [
|
|
634
|
-
{
|
|
635
|
-
description: `add unique constraint "${constraintName}"`,
|
|
636
|
-
sql: `ALTER TABLE ${qualifyTableName(schemaName, tableName)}
|
|
637
|
-
ADD CONSTRAINT ${quoteIdentifier(constraintName)}
|
|
638
|
-
UNIQUE (${unique.columns.map(quoteIdentifier).join(', ')})`,
|
|
639
|
-
},
|
|
640
|
-
],
|
|
641
|
-
postcheck: [
|
|
642
|
-
{
|
|
643
|
-
description: `verify unique constraint "${constraintName}" exists`,
|
|
644
|
-
sql: constraintExistsCheck({ constraintName, schema: schemaName, table: tableName }),
|
|
645
|
-
},
|
|
646
|
-
],
|
|
647
|
-
});
|
|
648
|
-
}
|
|
649
|
-
}
|
|
650
|
-
return operations;
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
private buildIndexOperations(
|
|
654
|
-
tables: ReadonlyArray<[string, StorageTable]>,
|
|
655
|
-
schemaLookups: ReadonlyMap<string, SchemaTableLookup>,
|
|
656
|
-
schemaName: string,
|
|
657
|
-
): readonly SqlMigrationPlanOperation<PostgresPlanTargetDetails>[] {
|
|
658
|
-
const operations: SqlMigrationPlanOperation<PostgresPlanTargetDetails>[] = [];
|
|
659
|
-
for (const [tableName, table] of tables) {
|
|
660
|
-
const lookup = schemaLookups.get(tableName);
|
|
661
|
-
for (const index of table.indexes) {
|
|
662
|
-
if (lookup && hasIndex(lookup, index.columns)) {
|
|
663
|
-
continue;
|
|
664
|
-
}
|
|
665
|
-
const indexName = index.name ?? defaultIndexName(tableName, index.columns);
|
|
666
|
-
operations.push({
|
|
667
|
-
id: `index.${tableName}.${indexName}`,
|
|
668
|
-
label: `Create index ${indexName} on ${tableName}`,
|
|
669
|
-
summary: `Creates index ${indexName} on ${tableName}`,
|
|
670
|
-
operationClass: 'additive',
|
|
671
|
-
target: {
|
|
672
|
-
id: 'postgres',
|
|
673
|
-
details: this.buildTargetDetails('index', indexName, schemaName, tableName),
|
|
674
|
-
},
|
|
675
|
-
precheck: [
|
|
676
|
-
{
|
|
677
|
-
description: `ensure index "${indexName}" is missing`,
|
|
678
|
-
sql: `SELECT to_regclass(${toRegclassLiteral(schemaName, indexName)}) IS NULL`,
|
|
679
|
-
},
|
|
680
|
-
],
|
|
681
|
-
execute: [
|
|
682
|
-
{
|
|
683
|
-
description: `create index "${indexName}"`,
|
|
684
|
-
sql: `CREATE INDEX ${quoteIdentifier(indexName)} ON ${qualifyTableName(
|
|
685
|
-
schemaName,
|
|
686
|
-
tableName,
|
|
687
|
-
)} (${index.columns.map(quoteIdentifier).join(', ')})`,
|
|
688
|
-
},
|
|
689
|
-
],
|
|
690
|
-
postcheck: [
|
|
691
|
-
{
|
|
692
|
-
description: `verify index "${indexName}" exists`,
|
|
693
|
-
sql: `SELECT to_regclass(${toRegclassLiteral(schemaName, indexName)}) IS NOT NULL`,
|
|
694
|
-
},
|
|
695
|
-
],
|
|
696
|
-
});
|
|
697
|
-
}
|
|
698
|
-
}
|
|
699
|
-
return operations;
|
|
700
|
-
}
|
|
701
|
-
|
|
702
|
-
/**
|
|
703
|
-
* Generates FK-backing index operations for FKs with `index: true`,
|
|
704
|
-
* but only when no matching user-declared index exists in `contractTable.indexes`.
|
|
705
|
-
*/
|
|
706
|
-
private buildFkBackingIndexOperations(
|
|
707
|
-
tables: ReadonlyArray<[string, StorageTable]>,
|
|
708
|
-
schemaLookups: ReadonlyMap<string, SchemaTableLookup>,
|
|
709
|
-
schemaName: string,
|
|
710
|
-
): readonly SqlMigrationPlanOperation<PostgresPlanTargetDetails>[] {
|
|
711
|
-
const operations: SqlMigrationPlanOperation<PostgresPlanTargetDetails>[] = [];
|
|
712
|
-
for (const [tableName, table] of tables) {
|
|
713
|
-
const lookup = schemaLookups.get(tableName);
|
|
714
|
-
// Collect column sets of user-declared indexes to avoid duplicates
|
|
715
|
-
const declaredIndexColumns = new Set(table.indexes.map((idx) => idx.columns.join(',')));
|
|
716
|
-
|
|
717
|
-
for (const fk of table.foreignKeys) {
|
|
718
|
-
if (fk.index === false) continue;
|
|
719
|
-
// Skip if user already declared an index with these columns
|
|
720
|
-
if (declaredIndexColumns.has(fk.columns.join(','))) continue;
|
|
721
|
-
// Skip if the index already exists in the database
|
|
722
|
-
if (lookup && hasIndex(lookup, fk.columns)) continue;
|
|
723
|
-
|
|
724
|
-
const indexName = defaultIndexName(tableName, fk.columns);
|
|
725
|
-
operations.push({
|
|
726
|
-
id: `index.${tableName}.${indexName}`,
|
|
727
|
-
label: `Create FK-backing index ${indexName} on ${tableName}`,
|
|
728
|
-
summary: `Creates FK-backing index ${indexName} on ${tableName}`,
|
|
729
|
-
operationClass: 'additive',
|
|
730
|
-
target: {
|
|
731
|
-
id: 'postgres',
|
|
732
|
-
details: this.buildTargetDetails('index', indexName, schemaName, tableName),
|
|
733
|
-
},
|
|
734
|
-
precheck: [
|
|
735
|
-
{
|
|
736
|
-
description: `ensure index "${indexName}" is missing`,
|
|
737
|
-
sql: `SELECT to_regclass(${toRegclassLiteral(schemaName, indexName)}) IS NULL`,
|
|
738
|
-
},
|
|
739
|
-
],
|
|
740
|
-
execute: [
|
|
741
|
-
{
|
|
742
|
-
description: `create FK-backing index "${indexName}"`,
|
|
743
|
-
sql: `CREATE INDEX ${quoteIdentifier(indexName)} ON ${qualifyTableName(
|
|
744
|
-
schemaName,
|
|
745
|
-
tableName,
|
|
746
|
-
)} (${fk.columns.map(quoteIdentifier).join(', ')})`,
|
|
747
|
-
},
|
|
748
|
-
],
|
|
749
|
-
postcheck: [
|
|
750
|
-
{
|
|
751
|
-
description: `verify index "${indexName}" exists`,
|
|
752
|
-
sql: `SELECT to_regclass(${toRegclassLiteral(schemaName, indexName)}) IS NOT NULL`,
|
|
753
|
-
},
|
|
754
|
-
],
|
|
755
|
-
});
|
|
756
|
-
}
|
|
757
|
-
}
|
|
758
|
-
return operations;
|
|
759
|
-
}
|
|
760
|
-
|
|
761
|
-
private buildForeignKeyOperations(
|
|
762
|
-
tables: ReadonlyArray<[string, StorageTable]>,
|
|
763
|
-
schemaLookups: ReadonlyMap<string, SchemaTableLookup>,
|
|
764
|
-
schemaName: string,
|
|
765
|
-
): readonly SqlMigrationPlanOperation<PostgresPlanTargetDetails>[] {
|
|
766
|
-
const operations: SqlMigrationPlanOperation<PostgresPlanTargetDetails>[] = [];
|
|
767
|
-
for (const [tableName, table] of tables) {
|
|
768
|
-
const lookup = schemaLookups.get(tableName);
|
|
769
|
-
for (const foreignKey of table.foreignKeys) {
|
|
770
|
-
if (foreignKey.constraint === false) continue;
|
|
771
|
-
if (lookup && hasForeignKey(lookup, foreignKey)) {
|
|
772
|
-
continue;
|
|
773
|
-
}
|
|
774
|
-
const fkName = foreignKey.name ?? `${tableName}_${foreignKey.columns.join('_')}_fkey`;
|
|
775
|
-
operations.push({
|
|
776
|
-
id: `foreignKey.${tableName}.${fkName}`,
|
|
777
|
-
label: `Add foreign key ${fkName} on ${tableName}`,
|
|
778
|
-
summary: `Adds foreign key ${fkName} referencing ${foreignKey.references.table}`,
|
|
779
|
-
operationClass: 'additive',
|
|
780
|
-
target: {
|
|
781
|
-
id: 'postgres',
|
|
782
|
-
details: this.buildTargetDetails('foreignKey', fkName, schemaName, tableName),
|
|
783
|
-
},
|
|
784
|
-
precheck: [
|
|
785
|
-
{
|
|
786
|
-
description: `ensure foreign key "${fkName}" is missing`,
|
|
787
|
-
sql: constraintExistsCheck({
|
|
788
|
-
constraintName: fkName,
|
|
789
|
-
schema: schemaName,
|
|
790
|
-
table: tableName,
|
|
791
|
-
exists: false,
|
|
792
|
-
}),
|
|
793
|
-
},
|
|
794
|
-
],
|
|
795
|
-
execute: [
|
|
796
|
-
{
|
|
797
|
-
description: `add foreign key "${fkName}"`,
|
|
798
|
-
sql: buildForeignKeySql(schemaName, tableName, fkName, foreignKey),
|
|
799
|
-
},
|
|
800
|
-
],
|
|
801
|
-
postcheck: [
|
|
802
|
-
{
|
|
803
|
-
description: `verify foreign key "${fkName}" exists`,
|
|
804
|
-
sql: constraintExistsCheck({
|
|
805
|
-
constraintName: fkName,
|
|
806
|
-
schema: schemaName,
|
|
807
|
-
table: tableName,
|
|
808
|
-
}),
|
|
809
|
-
},
|
|
810
|
-
],
|
|
811
|
-
});
|
|
812
|
-
}
|
|
813
|
-
}
|
|
814
|
-
return operations;
|
|
815
|
-
}
|
|
816
|
-
|
|
817
|
-
private buildTargetDetails(
|
|
818
|
-
objectType: OperationClass,
|
|
819
|
-
name: string,
|
|
820
|
-
schema: string,
|
|
821
|
-
table?: string,
|
|
822
|
-
): PostgresPlanTargetDetails {
|
|
823
|
-
return buildTargetDetails(objectType, name, schema, table);
|
|
824
|
-
}
|
|
825
|
-
|
|
826
|
-
private resolvePlanningMode(policy: MigrationOperationPolicy): PlanningMode {
|
|
827
|
-
const allowWidening = policy.allowedOperationClasses.includes('widening');
|
|
828
|
-
const allowDestructive = policy.allowedOperationClasses.includes('destructive');
|
|
829
|
-
// `db init` uses additive-only policy and intentionally ignores extras.
|
|
830
|
-
// Any reconciliation-capable policy should inspect extras to reconcile strict equality.
|
|
831
|
-
const includeExtraObjects = allowWidening || allowDestructive;
|
|
832
|
-
return { includeExtraObjects, allowWidening, allowDestructive };
|
|
833
|
-
}
|
|
834
|
-
|
|
835
|
-
private collectSchemaIssues(
|
|
836
|
-
options: PlannerOptionsWithComponents,
|
|
837
|
-
strict: boolean,
|
|
838
|
-
): readonly SchemaIssue[] {
|
|
166
|
+
private collectSchemaIssues(options: PlannerOptionsWithComponents): readonly SchemaIssue[] {
|
|
167
|
+
// `db init` uses additive-only policy and intentionally ignores extra
|
|
168
|
+
// schema objects. Any reconciliation-capable policy (widening or
|
|
169
|
+
// destructive) must inspect extras to reconcile strict equality.
|
|
170
|
+
const allowed = options.policy.allowedOperationClasses;
|
|
171
|
+
const strict = allowed.includes('widening') || allowed.includes('destructive');
|
|
839
172
|
const verifyOptions: VerifySqlSchemaOptionsWithComponents = {
|
|
840
173
|
contract: options.contract,
|
|
841
174
|
schema: options.schema,
|
|
@@ -849,54 +182,3 @@ UNIQUE (${unique.columns.map(quoteIdentifier).join(', ')})`,
|
|
|
849
182
|
return verifyResult.schema.issues;
|
|
850
183
|
}
|
|
851
184
|
}
|
|
852
|
-
|
|
853
|
-
function canUseSharedTemporaryDefaultStrategy(options: {
|
|
854
|
-
readonly table: StorageTable;
|
|
855
|
-
readonly schemaTable: SqlSchemaIR['tables'][string];
|
|
856
|
-
readonly schemaLookup: SchemaTableLookup | undefined;
|
|
857
|
-
readonly columnName: string;
|
|
858
|
-
}): boolean {
|
|
859
|
-
const { table, schemaTable, schemaLookup, columnName } = options;
|
|
860
|
-
|
|
861
|
-
// Shared placeholders are only safe when later plan steps do not require
|
|
862
|
-
// row-specific values for this newly added column.
|
|
863
|
-
if (table.primaryKey?.columns.includes(columnName) && !schemaTable.primaryKey) {
|
|
864
|
-
return false;
|
|
865
|
-
}
|
|
866
|
-
|
|
867
|
-
for (const unique of table.uniques) {
|
|
868
|
-
if (!unique.columns.includes(columnName)) {
|
|
869
|
-
continue;
|
|
870
|
-
}
|
|
871
|
-
if (!schemaLookup || !hasUniqueConstraint(schemaLookup, unique.columns)) {
|
|
872
|
-
return false;
|
|
873
|
-
}
|
|
874
|
-
}
|
|
875
|
-
|
|
876
|
-
for (const foreignKey of table.foreignKeys) {
|
|
877
|
-
if (foreignKey.constraint === false || !foreignKey.columns.includes(columnName)) {
|
|
878
|
-
continue;
|
|
879
|
-
}
|
|
880
|
-
if (!schemaLookup || !hasForeignKey(schemaLookup, foreignKey)) {
|
|
881
|
-
return false;
|
|
882
|
-
}
|
|
883
|
-
}
|
|
884
|
-
|
|
885
|
-
return true;
|
|
886
|
-
}
|
|
887
|
-
|
|
888
|
-
function sortDependencies(
|
|
889
|
-
dependencies: ReadonlyArray<PlannerDatabaseDependency>,
|
|
890
|
-
): ReadonlyArray<PlannerDatabaseDependency> {
|
|
891
|
-
return [...dependencies].sort((a, b) => a.id.localeCompare(b.id));
|
|
892
|
-
}
|
|
893
|
-
|
|
894
|
-
function isPostgresPlannerDependency(
|
|
895
|
-
dependency: ComponentDatabaseDependency<unknown>,
|
|
896
|
-
): dependency is PlannerDatabaseDependency {
|
|
897
|
-
return dependency.install.every((operation) => operation.target.id === 'postgres');
|
|
898
|
-
}
|
|
899
|
-
|
|
900
|
-
function sortedEntries<V>(record: Readonly<Record<string, V>>): Array<[string, V]> {
|
|
901
|
-
return Object.entries(record).sort(([a], [b]) => a.localeCompare(b)) as Array<[string, V]>;
|
|
902
|
-
}
|