@prisma-next/target-postgres 0.4.0-dev.9 → 0.4.2

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.
Files changed (195) hide show
  1. package/dist/codec-ids-CojIXVf9.mjs +29 -0
  2. package/dist/codec-ids-CojIXVf9.mjs.map +1 -0
  3. package/dist/codec-ids.d.mts +28 -0
  4. package/dist/codec-ids.d.mts.map +1 -0
  5. package/dist/codec-ids.mjs +3 -0
  6. package/dist/codec-types.d.mts +42 -0
  7. package/dist/codec-types.d.mts.map +1 -0
  8. package/dist/codec-types.mjs +3 -0
  9. package/dist/codecs-BoahtY_Q.mjs +385 -0
  10. package/dist/codecs-BoahtY_Q.mjs.map +1 -0
  11. package/dist/codecs-D-F2KJqt.d.mts +299 -0
  12. package/dist/codecs-D-F2KJqt.d.mts.map +1 -0
  13. package/dist/codecs.d.mts +2 -0
  14. package/dist/codecs.mjs +3 -0
  15. package/dist/control.d.mts +1 -9
  16. package/dist/control.d.mts.map +1 -1
  17. package/dist/control.mjs +23 -5101
  18. package/dist/control.mjs.map +1 -1
  19. package/dist/data-transform-CxFRBIUp.d.mts +32 -0
  20. package/dist/data-transform-CxFRBIUp.d.mts.map +1 -0
  21. package/dist/data-transform-VfEGzXWt.mjs +39 -0
  22. package/dist/data-transform-VfEGzXWt.mjs.map +1 -0
  23. package/dist/data-transform.d.mts +2 -0
  24. package/dist/data-transform.mjs +3 -0
  25. package/dist/default-normalizer-DNOpRoOF.mjs +131 -0
  26. package/dist/default-normalizer-DNOpRoOF.mjs.map +1 -0
  27. package/dist/default-normalizer.d.mts +19 -0
  28. package/dist/default-normalizer.d.mts.map +1 -0
  29. package/dist/default-normalizer.mjs +3 -0
  30. package/dist/{descriptor-meta-DkvCmY98.mjs → descriptor-meta-BVoVtyp-.mjs} +1 -1
  31. package/dist/{descriptor-meta-DkvCmY98.mjs.map → descriptor-meta-BVoVtyp-.mjs.map} +1 -1
  32. package/dist/errors-AFvEPZ1R.mjs +34 -0
  33. package/dist/errors-AFvEPZ1R.mjs.map +1 -0
  34. package/dist/errors.d.mts +27 -0
  35. package/dist/errors.d.mts.map +1 -0
  36. package/dist/errors.mjs +3 -0
  37. package/dist/issue-planner-CFjB0_oO.mjs +879 -0
  38. package/dist/issue-planner-CFjB0_oO.mjs.map +1 -0
  39. package/dist/issue-planner.d.mts +85 -0
  40. package/dist/issue-planner.d.mts.map +1 -0
  41. package/dist/issue-planner.mjs +3 -0
  42. package/dist/migration.d.mts +90 -0
  43. package/dist/migration.d.mts.map +1 -0
  44. package/dist/migration.mjs +24 -0
  45. package/dist/migration.mjs.map +1 -0
  46. package/dist/native-type-normalizer-CInai_oY.mjs +38 -0
  47. package/dist/native-type-normalizer-CInai_oY.mjs.map +1 -0
  48. package/dist/native-type-normalizer.d.mts +18 -0
  49. package/dist/native-type-normalizer.d.mts.map +1 -0
  50. package/dist/native-type-normalizer.mjs +3 -0
  51. package/dist/op-factory-call-BKlruaiC.mjs +605 -0
  52. package/dist/op-factory-call-BKlruaiC.mjs.map +1 -0
  53. package/dist/op-factory-call-C3bWXKSP.d.mts +304 -0
  54. package/dist/op-factory-call-C3bWXKSP.d.mts.map +1 -0
  55. package/dist/op-factory-call.d.mts +3 -0
  56. package/dist/op-factory-call.mjs +3 -0
  57. package/dist/pack.d.mts +1 -1
  58. package/dist/pack.mjs +1 -1
  59. package/dist/planner-CLUvVhUN.mjs +98 -0
  60. package/dist/planner-CLUvVhUN.mjs.map +1 -0
  61. package/dist/planner-ddl-builders-Dxvw1LHw.mjs +132 -0
  62. package/dist/planner-ddl-builders-Dxvw1LHw.mjs.map +1 -0
  63. package/dist/planner-ddl-builders.d.mts +22 -0
  64. package/dist/planner-ddl-builders.d.mts.map +1 -0
  65. package/dist/planner-ddl-builders.mjs +3 -0
  66. package/dist/planner-identity-values-Dju-o5GF.mjs +91 -0
  67. package/dist/planner-identity-values-Dju-o5GF.mjs.map +1 -0
  68. package/dist/planner-identity-values.d.mts +20 -0
  69. package/dist/planner-identity-values.d.mts.map +1 -0
  70. package/dist/planner-identity-values.mjs +3 -0
  71. package/dist/planner-produced-postgres-migration-CRRTno6Z.d.mts +20 -0
  72. package/dist/planner-produced-postgres-migration-CRRTno6Z.d.mts.map +1 -0
  73. package/dist/planner-produced-postgres-migration-DSSPq8QS.mjs +33 -0
  74. package/dist/planner-produced-postgres-migration-DSSPq8QS.mjs.map +1 -0
  75. package/dist/planner-produced-postgres-migration.d.mts +5 -0
  76. package/dist/planner-produced-postgres-migration.mjs +3 -0
  77. package/dist/planner-schema-lookup-B7lkypwn.mjs +29 -0
  78. package/dist/planner-schema-lookup-B7lkypwn.mjs.map +1 -0
  79. package/dist/planner-schema-lookup.d.mts +22 -0
  80. package/dist/planner-schema-lookup.d.mts.map +1 -0
  81. package/dist/planner-schema-lookup.mjs +3 -0
  82. package/dist/planner-sql-checks-7jkgm9TX.mjs +241 -0
  83. package/dist/planner-sql-checks-7jkgm9TX.mjs.map +1 -0
  84. package/dist/planner-sql-checks.d.mts +55 -0
  85. package/dist/planner-sql-checks.d.mts.map +1 -0
  86. package/dist/planner-sql-checks.mjs +3 -0
  87. package/dist/planner-target-details-DH-azLu-.d.mts +11 -0
  88. package/dist/planner-target-details-DH-azLu-.d.mts.map +1 -0
  89. package/dist/planner-target-details.d.mts +2 -0
  90. package/dist/planner-target-details.mjs +1 -0
  91. package/dist/planner.d.mts +68 -0
  92. package/dist/planner.d.mts.map +1 -0
  93. package/dist/planner.mjs +4 -0
  94. package/dist/postgres-migration-BjA3Zmts.d.mts +50 -0
  95. package/dist/postgres-migration-BjA3Zmts.d.mts.map +1 -0
  96. package/dist/postgres-migration-qtmtbONe.mjs +52 -0
  97. package/dist/postgres-migration-qtmtbONe.mjs.map +1 -0
  98. package/dist/render-ops-D6_DHdOK.mjs +8 -0
  99. package/dist/render-ops-D6_DHdOK.mjs.map +1 -0
  100. package/dist/render-ops.d.mts +11 -0
  101. package/dist/render-ops.d.mts.map +1 -0
  102. package/dist/render-ops.mjs +3 -0
  103. package/dist/render-typescript-1rF_SB4g.mjs +85 -0
  104. package/dist/render-typescript-1rF_SB4g.mjs.map +1 -0
  105. package/dist/render-typescript.d.mts +15 -0
  106. package/dist/render-typescript.d.mts.map +1 -0
  107. package/dist/render-typescript.mjs +3 -0
  108. package/dist/runtime.d.mts +15 -3
  109. package/dist/runtime.d.mts.map +1 -1
  110. package/dist/runtime.mjs +10 -1
  111. package/dist/runtime.mjs.map +1 -1
  112. package/dist/shared-Bxkt8pNO.d.mts +41 -0
  113. package/dist/shared-Bxkt8pNO.d.mts.map +1 -0
  114. package/dist/sql-utils-r-Lw535w.mjs +76 -0
  115. package/dist/sql-utils-r-Lw535w.mjs.map +1 -0
  116. package/dist/sql-utils.d.mts +59 -0
  117. package/dist/sql-utils.d.mts.map +1 -0
  118. package/dist/sql-utils.mjs +3 -0
  119. package/dist/statement-builders-BPnmt6wx.mjs +116 -0
  120. package/dist/statement-builders-BPnmt6wx.mjs.map +1 -0
  121. package/dist/statement-builders.d.mts +23 -0
  122. package/dist/statement-builders.d.mts.map +1 -0
  123. package/dist/statement-builders.mjs +3 -0
  124. package/dist/tables-BmdW_FWO.mjs +477 -0
  125. package/dist/tables-BmdW_FWO.mjs.map +1 -0
  126. package/dist/types-ClK03Ojd.d.mts +10 -0
  127. package/dist/types-ClK03Ojd.d.mts.map +1 -0
  128. package/dist/types.d.mts +2 -0
  129. package/dist/types.mjs +1 -0
  130. package/package.json +40 -18
  131. package/src/core/codec-ids.ts +30 -0
  132. package/src/core/codecs.ts +645 -0
  133. package/src/core/default-normalizer.ts +131 -0
  134. package/src/core/descriptor-meta.ts +1 -1
  135. package/src/core/errors.ts +33 -0
  136. package/src/core/json-schema-type-expression.ts +131 -0
  137. package/src/core/migrations/issue-planner.ts +832 -0
  138. package/src/core/migrations/op-factory-call.ts +858 -0
  139. package/src/core/migrations/operations/columns.ts +285 -0
  140. package/src/core/migrations/operations/constraints.ts +191 -0
  141. package/src/core/migrations/operations/data-transform.ts +119 -0
  142. package/src/core/migrations/operations/dependencies.ts +36 -0
  143. package/src/core/migrations/operations/enums.ts +113 -0
  144. package/src/core/migrations/operations/indexes.ts +61 -0
  145. package/src/core/migrations/operations/raw.ts +15 -0
  146. package/src/core/migrations/operations/shared.ts +67 -0
  147. package/src/core/migrations/operations/tables.ts +63 -0
  148. package/src/core/migrations/planner-ddl-builders.ts +1 -1
  149. package/src/core/migrations/planner-produced-postgres-migration.ts +67 -0
  150. package/src/core/migrations/planner-recipes.ts +1 -1
  151. package/src/core/migrations/planner-sql-checks.ts +1 -1
  152. package/src/core/migrations/planner-strategies.ts +592 -151
  153. package/src/core/migrations/planner-target-details.ts +0 -6
  154. package/src/core/migrations/planner.ts +65 -785
  155. package/src/core/migrations/postgres-migration.ts +73 -0
  156. package/src/core/migrations/render-ops.ts +9 -0
  157. package/src/core/migrations/render-typescript.ts +105 -0
  158. package/src/core/migrations/runner.ts +2 -4
  159. package/src/core/native-type-normalizer.ts +49 -0
  160. package/src/core/sql-utils.ts +104 -0
  161. package/src/exports/codec-ids.ts +1 -0
  162. package/src/exports/codec-types.ts +51 -0
  163. package/src/exports/codecs.ts +2 -0
  164. package/src/exports/control.ts +9 -142
  165. package/src/exports/data-transform.ts +1 -0
  166. package/src/exports/default-normalizer.ts +1 -0
  167. package/src/exports/errors.ts +1 -0
  168. package/src/exports/issue-planner.ts +1 -0
  169. package/src/exports/migration.ts +46 -0
  170. package/src/exports/native-type-normalizer.ts +1 -0
  171. package/src/exports/op-factory-call.ts +25 -0
  172. package/src/exports/planner-ddl-builders.ts +8 -0
  173. package/src/exports/planner-identity-values.ts +1 -0
  174. package/src/exports/planner-produced-postgres-migration.ts +1 -0
  175. package/src/exports/planner-schema-lookup.ts +6 -0
  176. package/src/exports/planner-sql-checks.ts +11 -0
  177. package/src/exports/planner-target-details.ts +1 -0
  178. package/src/exports/planner.ts +1 -0
  179. package/src/exports/render-ops.ts +1 -0
  180. package/src/exports/render-typescript.ts +1 -0
  181. package/src/exports/runtime.ts +19 -4
  182. package/src/exports/sql-utils.ts +7 -0
  183. package/src/exports/statement-builders.ts +7 -0
  184. package/src/exports/types.ts +1 -0
  185. package/dist/migration-builders.d.mts +0 -88
  186. package/dist/migration-builders.d.mts.map +0 -1
  187. package/dist/migration-builders.mjs +0 -3
  188. package/dist/operation-descriptors-CxymFSgK.mjs +0 -52
  189. package/dist/operation-descriptors-CxymFSgK.mjs.map +0 -1
  190. package/src/core/migrations/descriptor-planner.ts +0 -464
  191. package/src/core/migrations/operation-descriptors.ts +0 -166
  192. package/src/core/migrations/operation-resolver.ts +0 -929
  193. package/src/core/migrations/planner-reconciliation.ts +0 -798
  194. package/src/core/migrations/scaffolding.ts +0 -140
  195. package/src/exports/migration-builders.ts +0 -56
@@ -1,24 +1,9 @@
1
- import {
2
- normalizeSchemaNativeType,
3
- parsePostgresDefault,
4
- quoteIdentifier,
5
- } from '@prisma-next/adapter-postgres/control';
6
- import { errorPlanDoesNotSupportAuthoringSurface } from '@prisma-next/errors/migration';
7
1
  import type {
8
- CodecControlHooks,
9
- ComponentDatabaseDependency,
10
2
  MigrationOperationPolicy,
11
3
  SqlMigrationPlannerPlanOptions,
12
- SqlMigrationPlanOperation,
13
- SqlPlannerConflict,
14
4
  SqlPlannerFailureResult,
15
5
  } from '@prisma-next/family-sql/control';
16
- import {
17
- collectInitDependencies,
18
- createMigrationPlan,
19
- extractCodecControlHooks,
20
- plannerFailure,
21
- } from '@prisma-next/family-sql/control';
6
+ import { extractCodecControlHooks, plannerFailure } from '@prisma-next/family-sql/control';
22
7
  import { verifySqlSchema } from '@prisma-next/family-sql/schema-verify';
23
8
  import type { TargetBoundComponentDescriptor } from '@prisma-next/framework-components/components';
24
9
  import type {
@@ -27,44 +12,11 @@ import type {
27
12
  MigrationScaffoldContext,
28
13
  SchemaIssue,
29
14
  } from '@prisma-next/framework-components/control';
30
- import type {
31
- StorageColumn,
32
- StorageTable,
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';
15
+ import { parsePostgresDefault } from '../default-normalizer';
16
+ import { normalizeSchemaNativeType } from '../native-type-normalizer';
17
+ import { planIssues } from './issue-planner';
18
+ import { TypeScriptRenderablePostgresMigration } from './planner-produced-postgres-migration';
19
+ import { postgresPlannerStrategies } from './planner-strategies';
68
20
 
69
21
  type PlannerFrameworkComponents = SqlMigrationPlannerPlanOptions extends {
70
22
  readonly frameworkComponents: infer T;
@@ -80,12 +32,6 @@ type VerifySqlSchemaOptionsWithComponents = Parameters<typeof verifySqlSchema>[0
80
32
  readonly frameworkComponents: PlannerFrameworkComponents;
81
33
  };
82
34
 
83
- type PlannerDatabaseDependency = {
84
- readonly id: string;
85
- readonly label: string;
86
- readonly install: readonly SqlMigrationPlanOperation<PostgresPlanTargetDetails>[];
87
- };
88
-
89
35
  interface PlannerConfig {
90
36
  readonly defaultSchema: string;
91
37
  }
@@ -104,44 +50,31 @@ export function createPostgresMigrationPlanner(
104
50
  }
105
51
 
106
52
  /**
107
- * Postgres planner success plan: the SQL-typed plan (so target-detail
108
- * typing is preserved for direct SQL callers, including this package's unit
109
- * tests) plus the framework-required `renderTypeScript()` stub.
110
- */
111
- export type PostgresPlanWithRender = ReturnType<
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>`).
53
+ * Result of `PostgresMigrationPlanner.plan()`. A discriminated union whose
54
+ * success variant carries a `TypeScriptRenderablePostgresMigration` a
55
+ * migration object that both the CLI (via `renderTypeScript()`) and the
56
+ * SQL-typed callers (via `operations`, `describe()`, etc.) consume
57
+ * uniformly.
122
58
  */
123
59
  export type PostgresPlanResult =
124
- | { readonly kind: 'success'; readonly plan: PostgresPlanWithRender }
60
+ | { readonly kind: 'success'; readonly plan: TypeScriptRenderablePostgresMigration }
125
61
  | SqlPlannerFailureResult;
126
62
 
127
63
  /**
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.
135
- *
136
- * `plan()` accepts the framework's option shape (with `contract`/`schema`
137
- * typed as `unknown`); SQL-typed callers may pass the more specific
138
- * `SqlMigrationPlannerPlanOptions`, since those structurally satisfy the
139
- * looser framework contract. Internally we treat options as the SQL-typed
140
- * superset so the existing planner logic stays identical.
64
+ * Postgres migration planner — a thin wrapper over `planIssues`.
141
65
  *
142
- * `fromHash` is accepted but ignored: Postgres is descriptor-flow and never
143
- * needs it (only class-flow planners like Mongo populate `describe()` from
144
- * `fromHash`).
66
+ * `plan()` verifies the live schema against the target contract (producing
67
+ * `SchemaIssue[]`) and delegates to `planIssues` with the unified
68
+ * `postgresPlannerStrategies` list: enum-change, NOT-NULL backfill,
69
+ * type-change, nullable-tightening, codec-hook storage types,
70
+ * component-declared dependency installs, and shared-temp-default /
71
+ * empty-table-guarded NOT-NULL add-column. The same strategy list runs for
72
+ * `migration plan`, `db update`, and `db init`; behavior diverges purely on
73
+ * `policy.allowedOperationClasses` (the data-safe strategies short-circuit
74
+ * when `'data'` is excluded). The issue planner applies operation-class
75
+ * policy gates and emits a single `PostgresOpFactoryCall[]` that drives both
76
+ * the runtime-ops view (via `renderOps`) and the `renderTypeScript()`
77
+ * authoring surface.
145
78
  */
146
79
  export class PostgresMigrationPlanner implements MigrationPlanner<'sql', 'postgres'> {
147
80
  constructor(private readonly config: PlannerConfig) {}
@@ -151,107 +84,66 @@ export class PostgresMigrationPlanner implements MigrationPlanner<'sql', 'postgr
151
84
  readonly schema: unknown;
152
85
  readonly policy: MigrationOperationPolicy;
153
86
  readonly fromHash?: string;
87
+ /**
88
+ * The "from" contract (state the planner assumes the database starts
89
+ * at). Only `migration plan` supplies this; `db update` / `db init`
90
+ * reconcile against the live schema with no old contract. When present
91
+ * alongside the `'data'` operation class, strategies that need from/to
92
+ * column shape comparisons (unsafe type change, nullability tightening)
93
+ * activate.
94
+ */
95
+ readonly fromContract?: unknown;
154
96
  readonly schemaName?: string;
155
97
  readonly frameworkComponents: ReadonlyArray<TargetBoundComponentDescriptor<'sql', string>>;
156
98
  }): PostgresPlanResult {
157
- return this.planSql(options as SqlMigrationPlannerPlanOptions);
99
+ return this.planSql(options as SqlMigrationPlannerPlanOptions, options.fromHash ?? '');
158
100
  }
159
101
 
160
102
  emptyMigration(context: MigrationScaffoldContext): MigrationPlanWithAuthoringSurface {
161
- return {
162
- targetId: 'postgres',
163
- destination: { storageHash: context.toHash },
164
- operations: [],
165
- renderTypeScript(): string {
166
- return renderDescriptorTypeScript([], context);
167
- },
168
- };
103
+ return new TypeScriptRenderablePostgresMigration([], {
104
+ from: context.fromHash,
105
+ to: context.toHash,
106
+ });
169
107
  }
170
108
 
171
- private planSql(options: SqlMigrationPlannerPlanOptions): PostgresPlanResult {
109
+ private planSql(options: SqlMigrationPlannerPlanOptions, fromHash: string): PostgresPlanResult {
172
110
  const schemaName = options.schemaName ?? this.config.defaultSchema;
173
111
  const policyResult = this.ensureAdditivePolicy(options.policy);
174
112
  if (policyResult) {
175
113
  return policyResult;
176
114
  }
177
115
 
178
- const planningMode = this.resolvePlanningMode(options.policy);
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.
116
+ const schemaIssues = this.collectSchemaIssues(options);
183
117
  const codecHooks = extractCodecControlHooks(options.frameworkComponents);
184
-
185
- const operations: SqlMigrationPlanOperation<PostgresPlanTargetDetails>[] = [];
186
118
  const storageTypes = options.contract.storage.types ?? {};
187
119
 
188
- const reconciliationPlan = buildReconciliationPlan({
189
- contract: options.contract,
120
+ const result = planIssues({
190
121
  issues: schemaIssues,
122
+ toContract: options.contract,
123
+ // `fromContract` is only supplied by `migration plan`. It is `null` for
124
+ // `db update` / `db init`, which means data-safety strategies needing
125
+ // from/to comparisons (unsafe type change, nullable tightening) are
126
+ // inapplicable there — reconciliation falls through to
127
+ // `mapIssueToCall`'s direct destructive handlers.
128
+ fromContract: options.fromContract ?? null,
191
129
  schemaName,
192
- mode: planningMode,
193
- policy: options.policy,
194
130
  codecHooks,
131
+ storageTypes,
132
+ schema: options.schema,
133
+ policy: options.policy,
134
+ frameworkComponents: options.frameworkComponents,
135
+ strategies: postgresPlannerStrategies,
195
136
  });
196
- if (reconciliationPlan.conflicts.length > 0) {
197
- return plannerFailure(reconciliationPlan.conflicts);
198
- }
199
137
 
200
- const storageTypePlan = this.buildStorageTypeOperations(options, schemaName, codecHooks);
201
- if (storageTypePlan.conflicts.length > 0) {
202
- return plannerFailure(storageTypePlan.conflicts);
138
+ if (!result.ok) {
139
+ return plannerFailure(result.failure);
203
140
  }
204
141
 
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
142
  return Object.freeze({
249
143
  kind: 'success' as const,
250
- plan: Object.freeze({
251
- ...plan,
252
- renderTypeScript(): string {
253
- throw errorPlanDoesNotSupportAuthoringSurface({ targetId: 'postgres' });
254
- },
144
+ plan: new TypeScriptRenderablePostgresMigration(result.value.calls, {
145
+ from: fromHash,
146
+ to: options.contract.storage.storageHash,
255
147
  }),
256
148
  });
257
149
  }
@@ -269,573 +161,12 @@ export class PostgresMigrationPlanner implements MigrationPlanner<'sql', 'postgr
269
161
  return null;
270
162
  }
271
163
 
272
- /**
273
- * Builds migration operations from component-owned database dependencies.
274
- * These operations install database-side persistence structures declared by components.
275
- */
276
- private buildDatabaseDependencyOperations(
277
- options: PlannerOptionsWithComponents,
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[] {
164
+ private collectSchemaIssues(options: PlannerOptionsWithComponents): readonly SchemaIssue[] {
165
+ // `db init` uses additive-only policy and intentionally ignores extra
166
+ // schema objects. Any reconciliation-capable policy (widening or
167
+ // destructive) must inspect extras to reconcile strict equality.
168
+ const allowed = options.policy.allowedOperationClasses;
169
+ const strict = allowed.includes('widening') || allowed.includes('destructive');
839
170
  const verifyOptions: VerifySqlSchemaOptionsWithComponents = {
840
171
  contract: options.contract,
841
172
  schema: options.schema,
@@ -849,54 +180,3 @@ UNIQUE (${unique.columns.map(quoteIdentifier).join(', ')})`,
849
180
  return verifyResult.schema.issues;
850
181
  }
851
182
  }
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
- }