@prisma-next/target-postgres 0.14.0 → 0.15.0-dev.10

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 (269) hide show
  1. package/dist/authoring-BtsOWgI3.mjs +674 -0
  2. package/dist/authoring-BtsOWgI3.mjs.map +1 -0
  3. package/dist/canonicalize-D1F4vUnP.d.mts +48 -0
  4. package/dist/canonicalize-D1F4vUnP.d.mts.map +1 -0
  5. package/dist/{codec-ids-BvytN2P8.mjs → codec-ids-B10ZZHzw.mjs} +4 -2
  6. package/dist/codec-ids-B10ZZHzw.mjs.map +1 -0
  7. package/dist/{codec-ids-CnXu9Qy3.d.mts → codec-ids-CI144z7g.d.mts} +4 -2
  8. package/dist/codec-ids-CI144z7g.d.mts.map +1 -0
  9. package/dist/codec-ids.d.mts +2 -2
  10. package/dist/codec-ids.mjs +2 -2
  11. package/dist/{codec-types-DHCkwPKE.d.mts → codec-types-CTfT4U4O.d.mts} +4 -2
  12. package/dist/codec-types-CTfT4U4O.d.mts.map +1 -0
  13. package/dist/codec-types.d.mts +1 -1
  14. package/dist/{codecs--0A5_4Bq.d.mts → codecs-D7W9O0Xx.d.mts} +134 -4
  15. package/dist/codecs-D7W9O0Xx.d.mts.map +1 -0
  16. package/dist/codecs-DRbb26Or.mjs +955 -0
  17. package/dist/codecs-DRbb26Or.mjs.map +1 -0
  18. package/dist/codecs.d.mts +2 -2
  19. package/dist/codecs.mjs +3 -757
  20. package/dist/codecs.mjs.map +1 -1
  21. package/dist/contract-free.d.mts +33 -3
  22. package/dist/contract-free.d.mts.map +1 -1
  23. package/dist/contract-free.mjs +23 -7
  24. package/dist/contract-free.mjs.map +1 -1
  25. package/dist/control-policy-DaLdbcYE.mjs +1254 -0
  26. package/dist/control-policy-DaLdbcYE.mjs.map +1 -0
  27. package/dist/control.d.mts +2 -2
  28. package/dist/control.d.mts.map +1 -1
  29. package/dist/control.mjs +880 -36
  30. package/dist/control.mjs.map +1 -1
  31. package/dist/data-transform-BOWpliq8.mjs.map +1 -1
  32. package/dist/{data-transform-DDgWdB5o.d.mts → data-transform-DPzwBMhW.d.mts} +3 -3
  33. package/dist/{data-transform-DDgWdB5o.d.mts.map → data-transform-DPzwBMhW.d.mts.map} +1 -1
  34. package/dist/data-transform.d.mts +1 -1
  35. package/dist/{ddl-QDyOSeLc.mjs → ddl-GTTOvjx_.mjs} +128 -6
  36. package/dist/ddl-GTTOvjx_.mjs.map +1 -0
  37. package/dist/ddl.d.mts +3 -2
  38. package/dist/ddl.mjs +2 -2
  39. package/dist/default-normalizer.d.mts +1 -1
  40. package/dist/default-normalizer.d.mts.map +1 -1
  41. package/dist/default-normalizer.mjs +247 -1
  42. package/dist/default-normalizer.mjs.map +1 -0
  43. package/dist/descriptor-meta-CFQKLXl_.mjs +23 -0
  44. package/dist/descriptor-meta-CFQKLXl_.mjs.map +1 -0
  45. package/dist/descriptor-meta-runtime-UwYdV8fD.mjs +12 -0
  46. package/dist/descriptor-meta-runtime-UwYdV8fD.mjs.map +1 -0
  47. package/dist/diff-database-schema-oY3brcLj.mjs +368 -0
  48. package/dist/diff-database-schema-oY3brcLj.mjs.map +1 -0
  49. package/dist/diff-database-schema.d.mts +37 -0
  50. package/dist/diff-database-schema.d.mts.map +1 -0
  51. package/dist/diff-database-schema.mjs +2 -0
  52. package/dist/errors-DO-w0EUS.mjs +35 -0
  53. package/dist/errors-DO-w0EUS.mjs.map +1 -0
  54. package/dist/errors.d.mts +11 -9
  55. package/dist/errors.d.mts.map +1 -1
  56. package/dist/errors.mjs +1 -1
  57. package/dist/issue-planner.d.mts +35 -11
  58. package/dist/issue-planner.d.mts.map +1 -1
  59. package/dist/issue-planner.mjs +2 -2
  60. package/dist/migration.d.mts +5 -5
  61. package/dist/migration.d.mts.map +1 -1
  62. package/dist/migration.mjs +4 -4
  63. package/dist/namespace-ids-xtp_ZY86.mjs +7 -0
  64. package/dist/namespace-ids-xtp_ZY86.mjs.map +1 -0
  65. package/dist/native-type-normalizer.mjs +37 -1
  66. package/dist/native-type-normalizer.mjs.map +1 -0
  67. package/dist/nodes-DJC5B8Id.mjs +192 -0
  68. package/dist/nodes-DJC5B8Id.mjs.map +1 -0
  69. package/dist/nodes-Dv5eCSq9.d.mts +170 -0
  70. package/dist/nodes-Dv5eCSq9.d.mts.map +1 -0
  71. package/dist/{op-factory-call-DmQEc3XV.d.mts → op-factory-call-CvH0JeBW.d.mts} +119 -17
  72. package/dist/op-factory-call-CvH0JeBW.d.mts.map +1 -0
  73. package/dist/{op-factory-call-D_p5vxwt.mjs → op-factory-call-tBcLo_B8.mjs} +496 -38
  74. package/dist/op-factory-call-tBcLo_B8.mjs.map +1 -0
  75. package/dist/op-factory-call.d.mts +2 -2
  76. package/dist/op-factory-call.mjs +2 -2
  77. package/dist/pack.d.mts +321 -3
  78. package/dist/pack.d.mts.map +1 -1
  79. package/dist/pack.mjs +1 -1
  80. package/dist/{planner-ddl-builders-B2wOwLqI.mjs → planner-ddl-builders-BKlmGDJl.mjs} +17 -28
  81. package/dist/planner-ddl-builders-BKlmGDJl.mjs.map +1 -0
  82. package/dist/planner-ddl-builders.d.mts +4 -6
  83. package/dist/planner-ddl-builders.d.mts.map +1 -1
  84. package/dist/planner-ddl-builders.mjs +2 -2
  85. package/dist/planner-identity-values-CJPha2Sz.mjs.map +1 -1
  86. package/dist/planner-identity-values.d.mts +8 -2
  87. package/dist/planner-identity-values.d.mts.map +1 -1
  88. package/dist/planner-identity-values.mjs +2 -2
  89. package/dist/planner-ootbxGty.mjs +390 -0
  90. package/dist/planner-ootbxGty.mjs.map +1 -0
  91. package/dist/{planner-produced-postgres-migration-Cji5vxUf.mjs → planner-produced-postgres-migration-DVsaobdV.mjs} +3 -3
  92. package/dist/{planner-produced-postgres-migration-Cji5vxUf.mjs.map → planner-produced-postgres-migration-DVsaobdV.mjs.map} +1 -1
  93. package/dist/{planner-produced-postgres-migration-QqHa2C2l.d.mts → planner-produced-postgres-migration-k33bb9Sm.d.mts} +4 -4
  94. package/dist/{planner-produced-postgres-migration-QqHa2C2l.d.mts.map → planner-produced-postgres-migration-k33bb9Sm.d.mts.map} +1 -1
  95. package/dist/planner-produced-postgres-migration.d.mts +1 -1
  96. package/dist/planner-produced-postgres-migration.mjs +1 -1
  97. package/dist/{planner-sql-checks-jqUUGyQR.mjs → planner-sql-checks-zqNfs2XZ.mjs} +5 -5
  98. package/dist/{planner-sql-checks-jqUUGyQR.mjs.map → planner-sql-checks-zqNfs2XZ.mjs.map} +1 -1
  99. package/dist/planner-sql-checks.d.mts +2 -2
  100. package/dist/planner-sql-checks.d.mts.map +1 -1
  101. package/dist/planner-sql-checks.mjs +1 -1
  102. package/dist/{planner-target-details-CIY6tLeo.d.mts → planner-target-details-HkP4RrRO.d.mts} +2 -2
  103. package/dist/planner-target-details-HkP4RrRO.d.mts.map +1 -0
  104. package/dist/planner-target-details.d.mts +1 -1
  105. package/dist/planner.d.mts +72 -17
  106. package/dist/planner.d.mts.map +1 -1
  107. package/dist/planner.mjs +3 -2
  108. package/dist/postgres-contract-view-C9j5cqP0.d.mts +34 -0
  109. package/dist/postgres-contract-view-C9j5cqP0.d.mts.map +1 -0
  110. package/dist/{postgres-contract-serializer-k3TAcPMY.mjs → postgres-contract-view-DM8sN1Ot.mjs} +45 -41
  111. package/dist/postgres-contract-view-DM8sN1Ot.mjs.map +1 -0
  112. package/dist/postgres-database-schema-node-DJOTywwQ.d.mts +271 -0
  113. package/dist/postgres-database-schema-node-DJOTywwQ.d.mts.map +1 -0
  114. package/dist/postgres-migration-Bt3sXoID.mjs +194 -0
  115. package/dist/postgres-migration-Bt3sXoID.mjs.map +1 -0
  116. package/dist/{postgres-migration-Y4YBJqkS.d.mts → postgres-migration-ulfqRGA1.d.mts} +83 -9
  117. package/dist/postgres-migration-ulfqRGA1.d.mts.map +1 -0
  118. package/dist/postgres-native-enum-BwVNf0Ku.mjs +45 -0
  119. package/dist/postgres-native-enum-BwVNf0Ku.mjs.map +1 -0
  120. package/dist/postgres-rls-policy--CSddvMR.d.mts +52 -0
  121. package/dist/postgres-rls-policy--CSddvMR.d.mts.map +1 -0
  122. package/dist/postgres-role-schema-node-bg32e7I-.mjs +191 -0
  123. package/dist/postgres-role-schema-node-bg32e7I-.mjs.map +1 -0
  124. package/dist/{postgres-schema-COGZ1ark.mjs → postgres-schema-Cm4DtiGA.mjs} +200 -11
  125. package/dist/postgres-schema-Cm4DtiGA.mjs.map +1 -0
  126. package/dist/postgres-schema-DKq6s1P3.d.mts +269 -0
  127. package/dist/postgres-schema-DKq6s1P3.d.mts.map +1 -0
  128. package/dist/postgres-table-schema-node-D6LvInCe.mjs +216 -0
  129. package/dist/postgres-table-schema-node-D6LvInCe.mjs.map +1 -0
  130. package/dist/render-ops.d.mts +1 -1
  131. package/dist/{render-typescript-KMgosran.mjs → render-typescript-7yqILcwr.mjs} +45 -16
  132. package/dist/render-typescript-7yqILcwr.mjs.map +1 -0
  133. package/dist/render-typescript.mjs +1 -1
  134. package/dist/resolve-ddl-schema-XyBaaenM.mjs +22 -0
  135. package/dist/resolve-ddl-schema-XyBaaenM.mjs.map +1 -0
  136. package/dist/rls-canonicalize.d.mts +27 -0
  137. package/dist/rls-canonicalize.d.mts.map +1 -0
  138. package/dist/rls-canonicalize.mjs +2 -0
  139. package/dist/runtime.d.mts +6 -8
  140. package/dist/runtime.d.mts.map +1 -1
  141. package/dist/runtime.mjs +3 -3
  142. package/dist/runtime.mjs.map +1 -1
  143. package/dist/schema-ir-annotations.d.mts +15 -0
  144. package/dist/schema-ir-annotations.d.mts.map +1 -0
  145. package/dist/schema-ir-annotations.mjs +2 -0
  146. package/dist/schema-node-kinds-CrYzCR1i.d.mts +30 -0
  147. package/dist/schema-node-kinds-CrYzCR1i.d.mts.map +1 -0
  148. package/dist/{sql-utils-DcfMz4MQ.mjs → sql-utils-SU4FDvIV.mjs} +12 -3
  149. package/dist/{sql-utils-DcfMz4MQ.mjs.map → sql-utils-SU4FDvIV.mjs.map} +1 -1
  150. package/dist/sql-utils.d.mts +8 -1
  151. package/dist/sql-utils.d.mts.map +1 -1
  152. package/dist/sql-utils.mjs +2 -2
  153. package/dist/types.d.mts +5 -140
  154. package/dist/types.mjs +5 -2
  155. package/dist/wire-name-DbQ9-2RC.mjs +104 -0
  156. package/dist/wire-name-DbQ9-2RC.mjs.map +1 -0
  157. package/package.json +26 -20
  158. package/src/contract-free/checks.ts +153 -0
  159. package/src/contract-free/control-bootstrap.ts +20 -3
  160. package/src/contract-free/ddl.ts +98 -1
  161. package/src/core/authoring.ts +726 -2
  162. package/src/core/codec-helpers.ts +33 -7
  163. package/src/core/codec-ids.ts +2 -0
  164. package/src/core/codec-type-map.ts +4 -0
  165. package/src/core/codecs.ts +236 -12
  166. package/src/core/ddl/nodes.ts +185 -2
  167. package/src/core/default-normalizer.ts +122 -2
  168. package/src/core/descriptor-meta.ts +12 -1
  169. package/src/core/entity-kinds.ts +41 -0
  170. package/src/core/errors.ts +14 -12
  171. package/src/core/migrations/bound-schema.ts +10 -0
  172. package/src/core/migrations/column-ddl-rendering.ts +117 -0
  173. package/src/core/migrations/contract-to-postgres-database-schema-node.ts +269 -0
  174. package/src/core/migrations/control-policy.ts +218 -65
  175. package/src/core/migrations/diff-database-schema.ts +252 -0
  176. package/src/core/migrations/issue-planner.ts +782 -660
  177. package/src/core/migrations/op-factory-call.ts +415 -31
  178. package/src/core/migrations/operations/columns.ts +24 -12
  179. package/src/core/migrations/operations/constraints.ts +1 -1
  180. package/src/core/migrations/operations/data-transform.ts +6 -0
  181. package/src/core/migrations/operations/dependencies.ts +2 -1
  182. package/src/core/migrations/operations/native-enum-types.ts +62 -0
  183. package/src/core/migrations/operations/rls.ts +187 -0
  184. package/src/core/migrations/operations/shared.ts +0 -30
  185. package/src/core/migrations/planner-ddl-builders.ts +31 -59
  186. package/src/core/migrations/planner-identity-values.ts +1 -1
  187. package/src/core/migrations/planner-recipes.ts +31 -13
  188. package/src/core/migrations/planner-sql-checks.ts +1 -1
  189. package/src/core/migrations/planner-strategies.ts +250 -218
  190. package/src/core/migrations/planner-target-details.ts +3 -1
  191. package/src/core/migrations/planner.ts +358 -67
  192. package/src/core/migrations/postgres-migration.ts +178 -86
  193. package/src/core/migrations/render-typescript.ts +42 -15
  194. package/src/core/migrations/resolve-ddl-schema.ts +26 -0
  195. package/src/core/migrations/runner.ts +11 -16
  196. package/src/core/migrations/verify-postgres-namespaces.ts +45 -31
  197. package/src/core/namespace-ids.ts +3 -0
  198. package/src/core/postgres-contract-serializer.ts +46 -71
  199. package/src/core/postgres-contract-view.ts +48 -0
  200. package/src/core/postgres-native-enum.ts +61 -0
  201. package/src/core/postgres-rls-enablement.ts +34 -0
  202. package/src/core/postgres-rls-policy.ts +64 -0
  203. package/src/core/postgres-role.ts +59 -0
  204. package/src/core/postgres-schema-verifier.ts +10 -7
  205. package/src/core/postgres-schema.ts +85 -15
  206. package/src/core/postgres-validators.ts +34 -0
  207. package/src/core/psl-infer/infer-psl-contract.ts +1290 -0
  208. package/src/core/psl-infer/postgres-default-mapping.ts +16 -0
  209. package/src/core/psl-infer/postgres-type-map.ts +129 -0
  210. package/src/core/rls/canonicalize.ts +65 -0
  211. package/src/core/rls/wire-name.ts +33 -0
  212. package/src/core/schema-ir/node-storage-coordinate.ts +26 -0
  213. package/src/core/schema-ir/postgres-database-schema-node.ts +61 -0
  214. package/src/core/schema-ir/postgres-namespace-schema-node.ts +70 -0
  215. package/src/core/schema-ir/postgres-native-enum-schema-node.ts +83 -0
  216. package/src/core/schema-ir/postgres-policy-schema-node.ts +100 -0
  217. package/src/core/schema-ir/postgres-role-schema-node.ts +65 -0
  218. package/src/core/schema-ir/postgres-table-schema-node.ts +140 -0
  219. package/src/core/schema-ir/schema-node-kinds.ts +122 -0
  220. package/src/core/sql-utils.ts +15 -2
  221. package/src/exports/codecs.ts +5 -0
  222. package/src/exports/contract-free.ts +11 -1
  223. package/src/exports/control.ts +27 -33
  224. package/src/exports/ddl.ts +8 -0
  225. package/src/exports/diff-database-schema.ts +7 -0
  226. package/src/exports/issue-planner.ts +1 -1
  227. package/src/exports/migration.ts +1 -0
  228. package/src/exports/op-factory-call.ts +9 -0
  229. package/src/exports/planner-ddl-builders.ts +0 -2
  230. package/src/exports/planner-identity-values.ts +4 -1
  231. package/src/exports/planner.ts +1 -0
  232. package/src/exports/rls-canonicalize.ts +12 -0
  233. package/src/exports/runtime.ts +1 -0
  234. package/src/exports/schema-ir-annotations.ts +1 -0
  235. package/src/exports/sql-utils.ts +1 -0
  236. package/src/exports/types.ts +38 -0
  237. package/dist/codec-ids-BvytN2P8.mjs.map +0 -1
  238. package/dist/codec-ids-CnXu9Qy3.d.mts.map +0 -1
  239. package/dist/codec-types-DHCkwPKE.d.mts.map +0 -1
  240. package/dist/codecs--0A5_4Bq.d.mts.map +0 -1
  241. package/dist/ddl-QDyOSeLc.mjs.map +0 -1
  242. package/dist/default-normalizer-DyyCHQWs.mjs +0 -131
  243. package/dist/default-normalizer-DyyCHQWs.mjs.map +0 -1
  244. package/dist/descriptor-meta-CpGygXpI.mjs +0 -140
  245. package/dist/descriptor-meta-CpGygXpI.mjs.map +0 -1
  246. package/dist/errors-CUk87ByX.mjs +0 -33
  247. package/dist/errors-CUk87ByX.mjs.map +0 -1
  248. package/dist/issue-planner-DL6g3CmE.mjs +0 -824
  249. package/dist/issue-planner-DL6g3CmE.mjs.map +0 -1
  250. package/dist/native-type-normalizer-Bc9XJzWC.mjs +0 -38
  251. package/dist/native-type-normalizer-Bc9XJzWC.mjs.map +0 -1
  252. package/dist/nodes-Bbhs2rwj.mjs +0 -76
  253. package/dist/nodes-Bbhs2rwj.mjs.map +0 -1
  254. package/dist/nodes-pLeLgdis.d.mts +0 -67
  255. package/dist/nodes-pLeLgdis.d.mts.map +0 -1
  256. package/dist/op-factory-call-D_p5vxwt.mjs.map +0 -1
  257. package/dist/op-factory-call-DmQEc3XV.d.mts.map +0 -1
  258. package/dist/planner-Bs_baQax.mjs +0 -324
  259. package/dist/planner-Bs_baQax.mjs.map +0 -1
  260. package/dist/planner-ddl-builders-B2wOwLqI.mjs.map +0 -1
  261. package/dist/planner-target-details-CIY6tLeo.d.mts.map +0 -1
  262. package/dist/postgres-contract-serializer-k3TAcPMY.mjs.map +0 -1
  263. package/dist/postgres-migration-B5jKrXv3.mjs +0 -145
  264. package/dist/postgres-migration-B5jKrXv3.mjs.map +0 -1
  265. package/dist/postgres-migration-Y4YBJqkS.d.mts.map +0 -1
  266. package/dist/postgres-schema-COGZ1ark.mjs.map +0 -1
  267. package/dist/render-typescript-KMgosran.mjs.map +0 -1
  268. package/dist/types.d.mts.map +0 -1
  269. package/src/core/postgres-enum-type-schema.ts +0 -17
@@ -1,10 +1,10 @@
1
1
  /**
2
2
  * Migration strategies.
3
3
  *
4
- * Each strategy examines the issue list, consumes issues it handles, and
5
- * returns the `PostgresOpFactoryCall[]` to address them. The issue planner
6
- * runs each strategy in order and routes whatever's left through
7
- * `mapIssueToCall`.
4
+ * Each strategy examines the node-typed diff-issue list, consumes issues it
5
+ * handles, and returns the `PostgresOpFactoryCall[]` to address them. The
6
+ * issue planner runs each strategy in order and routes whatever's left
7
+ * through `mapNodeIssueToCall`.
8
8
  *
9
9
  * The full ordered list is exported as `postgresPlannerStrategies` and is
10
10
  * used unchanged by both `migration plan` and `db update` / `db init`. The
@@ -14,8 +14,16 @@
14
14
  * nullability tightening, unsafe type changes) emit
15
15
  * `DataTransformCall` placeholders that the user fills in.
16
16
  * - When `'data'` is excluded, those strategies short-circuit so the
17
- * downstream walk-schema strategies (codec-hook type ops and temp-default
18
- * backfill) and `mapIssueToCall` defaults emit direct DDL instead.
17
+ * downstream strategies (codec-hook type ops and temp-default backfill)
18
+ * and `mapNodeIssueToCall` defaults emit direct DDL instead.
19
+ *
20
+ * Structural decisions (type/nullability drift, DDL fragments) read the diff
21
+ * node pair and resolve column DDL from the node's `codecRef` (`column-ddl-
22
+ * rendering.ts`). The retained subsystems — codec type-operations
23
+ * (`storageTypePlanCallStrategy`) and the NOT-NULL temp-default deferred DDL
24
+ * (`notNullAddColumnCallStrategy`) — keep reading the contract + codec hooks
25
+ * via `StrategyContext`, per the slice's scope (see `diff-database-schema.ts`
26
+ * / the slice spec).
19
27
  */
20
28
 
21
29
  import type { Contract } from '@prisma-next/contract/types';
@@ -26,20 +34,29 @@ import type {
26
34
  } from '@prisma-next/family-sql/control';
27
35
  import { resolveValueSetValues } from '@prisma-next/family-sql/control';
28
36
  import type { TargetBoundComponentDescriptor } from '@prisma-next/framework-components/components';
29
- import type { SchemaIssue } from '@prisma-next/framework-components/control';
37
+ import type { SchemaDiffIssue } from '@prisma-next/framework-components/control';
38
+ import { issueOutcome } from '@prisma-next/framework-components/control';
30
39
  import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir';
31
40
  import {
32
41
  type SqlStorage,
33
42
  StorageTable,
34
43
  type StorageTypeInstance,
35
44
  } from '@prisma-next/sql-contract/types';
36
- import type { CodecRef, DdlColumn } from '@prisma-next/sql-relational-core/ast';
37
- import { col } from '@prisma-next/sql-relational-core/contract-free';
38
- import type { SqlSchemaIR } from '@prisma-next/sql-schema-ir/types';
45
+ import * as contractFree from '@prisma-next/sql-relational-core/contract-free';
46
+ import {
47
+ RelationalSchemaNodeKind,
48
+ type SqlColumnIR,
49
+ type SqlSchemaIR,
50
+ } from '@prisma-next/sql-schema-ir/types';
39
51
  import { blindCast } from '@prisma-next/utils/casts';
40
- import { ifDefined } from '@prisma-next/utils/defined';
41
- import type { JsonValue } from '@prisma-next/utils/json';
42
52
  import { isPostgresSchema } from '../postgres-schema';
53
+ import {
54
+ renderColumnAlterType,
55
+ renderColumnDdl,
56
+ resolveColumnTemporaryDefault,
57
+ } from './column-ddl-rendering';
58
+ import { resolveNamespaceIdForDdlSchema } from './control-policy';
59
+ import { emissionSchemaName, issueNode, issueSchemaName, issueTableName } from './issue-planner';
43
60
  import {
44
61
  AddCheckConstraintCall,
45
62
  AddColumnCall,
@@ -48,17 +65,13 @@ import {
48
65
  AlterColumnTypeCall,
49
66
  DataTransformCall,
50
67
  DropCheckConstraintCall,
68
+ DropNotNullCall,
51
69
  type PostgresOpFactoryCall,
52
- postgresDefaultToDdlColumnDefault,
53
70
  RawSqlCall,
54
71
  SetNotNullCall,
55
72
  } from './op-factory-call';
56
- import { buildAddColumnSql, buildColumnTypeSql } from './planner-ddl-builders';
57
- import { resolveIdentityValue } from './planner-identity-values';
58
73
  import { buildSchemaLookupMap, hasForeignKey, hasUniqueConstraint } from './planner-schema-lookup';
59
- import { buildExpectedFormatType, qualifyTableName } from './planner-sql-checks';
60
74
  import { buildTargetDetails, type PostgresPlanTargetDetails } from './planner-target-details';
61
- import { resolveColumnTypeMetadata } from './planner-type-resolution';
62
75
 
63
76
  /**
64
77
  * Look up a storage table by its explicit namespace coordinate. Returns
@@ -80,19 +93,6 @@ export function tableAt(
80
93
  return ns.entries.table?.[tableName];
81
94
  }
82
95
 
83
- /**
84
- * Default namespace coordinate for an issue that does not carry one
85
- * explicitly. Hand-crafted unit-test issues and `extra_table` issues
86
- * fall back to `__unbound__`, the only namespace any single-namespace
87
- * contract carries — verifier-emitted issues for legacy
88
- * single-namespace contracts already stamp this id explicitly. Typed
89
- * structurally so issue variants without a `namespaceId` slot
90
- * (e.g. `EnumValuesChangedIssue`) flow through to the same fallback.
91
- */
92
- export function resolveNamespaceIdForIssue(issue: { readonly namespaceId?: string }): string {
93
- return issue.namespaceId ?? UNBOUND_NAMESPACE_ID;
94
- }
95
-
96
96
  /**
97
97
  * Resolve the DDL schema name for a namespace coordinate. Postgres-aware
98
98
  * namespaces dispatch to their polymorphic `ddlSchemaName` override —
@@ -111,6 +111,17 @@ export function resolveDdlSchemaForNamespace(ctx: StrategyContext, namespaceId:
111
111
  return namespaceId;
112
112
  }
113
113
 
114
+ /**
115
+ * Recovers the contract namespace id for a DDL schema name embedded in a
116
+ * diff-issue path (`path[1]`). The strategies need the CONTRACT column/table
117
+ * (for codec-hook reads and eligibility probes the retained subsystems still
118
+ * run) even though the issue itself only carries the resolved DDL schema —
119
+ * this is the reverse of `resolveDdlSchemaForNamespace`.
120
+ */
121
+ function namespaceIdForDdlSchema(ctx: StrategyContext, ddlSchemaName: string): string {
122
+ return resolveNamespaceIdForDdlSchema(ctx.toContract, ddlSchemaName);
123
+ }
124
+
114
125
  // ============================================================================
115
126
  // Strategy types
116
127
  // ============================================================================
@@ -139,13 +150,20 @@ export interface StrategyContext {
139
150
  // Call strategies (for issue planner)
140
151
  // ============================================================================
141
152
 
153
+ /**
154
+ * Consumes node-typed diff issues (`SchemaDiffIssue`, from the one differ —
155
+ * `buildPostgresPlanDiff`), reading the issue's node pair for structural
156
+ * decisions and resolving column DDL from the node's `codecRef`. The
157
+ * retained subsystems (codec type-operations, the NOT-NULL temp-default
158
+ * backfill) keep reading the contract via `StrategyContext`.
159
+ */
142
160
  export type CallMigrationStrategy = (
143
- issues: readonly SchemaIssue[],
161
+ issues: readonly SchemaDiffIssue[],
144
162
  context: StrategyContext,
145
163
  ) =>
146
164
  | {
147
165
  kind: 'match';
148
- issues: readonly SchemaIssue[];
166
+ issues: readonly SchemaDiffIssue[];
149
167
  calls: readonly PostgresOpFactoryCall[];
150
168
  /**
151
169
  * `true` for strategies that emit cohesive sequential recipes whose
@@ -158,91 +176,56 @@ export type CallMigrationStrategy = (
158
176
  }
159
177
  | { kind: 'no_match' };
160
178
 
161
- function buildColumnSpec(
162
- namespaceId: string,
163
- table: string,
164
- column: string,
165
- ctx: StrategyContext,
166
- overrides?: { nullable?: boolean },
167
- ): DdlColumn {
168
- const storageCol = tableAt(ctx.toContract.storage, namespaceId, table)?.columns[column];
169
- if (!storageCol)
170
- throw new Error(`Column "${table}"."${column}" not found in destination contract`);
171
- const mutableHooks = ctx.codecHooks as Map<string, CodecControlHooks>;
172
- const mutableTypes = ctx.storageTypes as Record<string, StorageTypeInstance>;
173
- const typeSql = buildColumnTypeSql(storageCol, mutableHooks, mutableTypes);
174
- const ddlDefault = postgresDefaultToDdlColumnDefault(storageCol.default);
175
- const resolved = resolveColumnTypeMetadata(storageCol, mutableTypes);
176
- const typeParams =
177
- resolved.typeParams === undefined
178
- ? undefined
179
- : blindCast<
180
- JsonValue,
181
- 'resolved.typeParams is JsonValue-shaped storage metadata; the narrowed value lands in CodecRef.typeParams which is JsonValue'
182
- >(resolved.typeParams);
183
- const codecRef: CodecRef | undefined = resolved.codecId
184
- ? {
185
- codecId: resolved.codecId,
186
- ...ifDefined('typeParams', typeParams),
187
- }
188
- : undefined;
189
- const nullable = overrides?.nullable ?? storageCol.nullable;
190
- return col(column, typeSql, {
191
- ...(!nullable ? { notNull: true } : {}),
192
- ...ifDefined('default', ddlDefault),
193
- ...ifDefined('codecRef', codecRef),
194
- });
195
- }
196
-
197
- function buildAlterTypeOptions(
198
- namespaceId: string,
199
- table: string,
200
- column: string,
201
- ctx: StrategyContext,
202
- using?: string,
203
- ) {
204
- const col = tableAt(ctx.toContract.storage, namespaceId, table)?.columns[column];
205
- if (!col) throw new Error(`Column "${table}"."${column}" not found in destination contract`);
206
- const mutableHooks = ctx.codecHooks as Map<string, CodecControlHooks>;
207
- const mutableTypes = ctx.storageTypes as Record<string, StorageTypeInstance>;
208
- const qualifiedTargetType = buildColumnTypeSql(col, mutableHooks, mutableTypes, false);
209
- const formatTypeExpected = buildExpectedFormatType(col, mutableHooks, mutableTypes);
179
+ /** A `not-equal` column issue whose node pair is narrowed to `SqlColumnIR`. */
180
+ function columnNodePair(
181
+ issue: SchemaDiffIssue,
182
+ ): { readonly expected: SqlColumnIR; readonly actual: SqlColumnIR } | undefined {
183
+ const node = issueNode(issue);
184
+ if (node === undefined || node.nodeKind !== RelationalSchemaNodeKind.column) return undefined;
185
+ if (issue.expected === undefined || issue.actual === undefined) return undefined;
210
186
  return {
211
- qualifiedTargetType,
212
- formatTypeExpected,
213
- rawTargetTypeForLabel: qualifiedTargetType,
214
- ...(using !== undefined ? { using } : {}),
187
+ expected: blindCast<SqlColumnIR, 'a column diff node is always a SqlColumnIR'>(issue.expected),
188
+ actual: blindCast<SqlColumnIR, 'a column diff node is always a SqlColumnIR'>(issue.actual),
215
189
  };
216
190
  }
217
191
 
218
192
  export const notNullBackfillCallStrategy: CallMigrationStrategy = (issues, ctx) => {
219
193
  // `DataTransformCall` is operation class `'data'`. When the policy excludes
220
194
  // it (`db update` / `db init`), skip so `notNullAddColumnCallStrategy`
221
- // (temp-default backfill) or `mapIssueToCall` can take the issue.
195
+ // (temp-default backfill) or `mapNodeIssueToCall` can take the issue.
222
196
  if (!ctx.policy.allowedOperationClasses.includes('data')) return { kind: 'no_match' };
223
197
 
224
- const matched: SchemaIssue[] = [];
198
+ const matched: SchemaDiffIssue[] = [];
225
199
  const calls: PostgresOpFactoryCall[] = [];
226
200
 
227
201
  for (const issue of issues) {
228
- if (issue.kind !== 'missing_column' || !issue.table || !issue.column) continue;
229
-
230
- const namespaceId = resolveNamespaceIdForIssue(issue);
231
- const column = tableAt(ctx.toContract.storage, namespaceId, issue.table)?.columns[issue.column];
232
- if (!column) continue;
233
- if (column.nullable === true || column.default !== undefined) continue;
202
+ if (issueOutcome(issue) !== 'not-found') continue;
203
+ const node = issueNode(issue);
204
+ if (node === undefined || node.nodeKind !== RelationalSchemaNodeKind.column) continue;
205
+ const expected = blindCast<
206
+ SqlColumnIR,
207
+ 'a not-found column issue always carries the expected column node'
208
+ >(issue.expected);
209
+ if (expected.nullable !== false || expected.resolvedDefault !== undefined) continue;
210
+
211
+ const ddlSchemaName = issueSchemaName(issue);
212
+ const tableName = issueTableName(issue);
213
+ if (ddlSchemaName === undefined || tableName === undefined) continue;
214
+ const schemaName = emissionSchemaName(ctx, ddlSchemaName);
234
215
 
235
216
  matched.push(issue);
236
- const spec = buildColumnSpec(namespaceId, issue.table, issue.column, ctx, { nullable: true });
237
- const schemaForTable = resolveDdlSchemaForNamespace(ctx, namespaceId);
217
+ const ddl = renderColumnDdl(expected.name, expected, ctx.codecHooks);
218
+ const nullableSpec = contractFree.col(ddl.name, ddl.type, {
219
+ ...(ddl.codecRef !== undefined ? { codecRef: ddl.codecRef } : {}),
220
+ });
238
221
  calls.push(
239
- new AddColumnCall(schemaForTable, issue.table, spec),
222
+ new AddColumnCall(schemaName, tableName, nullableSpec),
240
223
  new DataTransformCall(
241
- `backfill-${issue.table}-${issue.column}`,
242
- `backfill-${issue.table}-${issue.column}:check`,
243
- `backfill-${issue.table}-${issue.column}:run`,
224
+ `backfill-${tableName}-${expected.name}`,
225
+ `backfill-${tableName}-${expected.name}:check`,
226
+ `backfill-${tableName}-${expected.name}:run`,
244
227
  ),
245
- new SetNotNullCall(schemaForTable, issue.table, issue.column),
228
+ new SetNotNullCall(schemaName, tableName, expected.name),
246
229
  );
247
230
  }
248
231
 
@@ -257,49 +240,87 @@ export const notNullBackfillCallStrategy: CallMigrationStrategy = (issues, ctx)
257
240
 
258
241
  const SAFE_WIDENINGS = new Set(['int2→int4', 'int2→int8', 'int4→int8', 'float4→float8']);
259
242
 
243
+ /**
244
+ * Handles `not-equal` column issues whose TYPE differs. `fromContract` is
245
+ * only supplied by `migration plan` — for reconciliation (`db update` /
246
+ * `db init`, `fromContract === null`) this strategy never fires, mirroring
247
+ * the legacy `typeChangeCallStrategy`'s requirement of a prior contract:
248
+ * `mapNodeIssueToCall`'s in-place ALTER covers reconciliation directly.
249
+ *
250
+ * A single node issue can carry BOTH type and nullability drift (Postgres
251
+ * alters in place, so the differ emits one `not-equal` column issue where
252
+ * the legacy coordinate walk emitted two: `type_mismatch` +
253
+ * `nullability_mismatch`). When this strategy consumes the issue for its
254
+ * type portion, it also emits whatever nullability delta the same node pair
255
+ * carries — using the same construction `nullableTighteningCallStrategy` /
256
+ * the mapper's direct dispatch would have used — so the issue is never
257
+ * partially handled.
258
+ */
260
259
  export const typeChangeCallStrategy: CallMigrationStrategy = (issues, ctx) => {
261
- // For unsafe widenings this strategy emits a `DataTransformCall` placeholder
262
- // (operation class `'data'`); when the policy excludes `'data'`
263
- // (`db update` / `db init`), skip those issues so `mapIssueToCall` can
264
- // emit a direct `ALTER COLUMN TYPE`. Safe widenings still flow through
265
- // here because the resulting `AlterColumnTypeCall` is `widening`-class.
260
+ if (ctx.fromContract === null) return { kind: 'no_match' };
266
261
  const dataAllowed = ctx.policy.allowedOperationClasses.includes('data');
267
262
 
268
- const matched: SchemaIssue[] = [];
263
+ const matched: SchemaDiffIssue[] = [];
269
264
  const calls: PostgresOpFactoryCall[] = [];
270
265
 
271
266
  for (const issue of issues) {
272
- if (issue.kind !== 'type_mismatch') continue;
273
- if (!issue.table || !issue.column) continue;
274
- const namespaceId = resolveNamespaceIdForIssue(issue);
275
- const fromColumn = ctx.fromContract
276
- ? tableAt(ctx.fromContract.storage, namespaceId, issue.table)?.columns[issue.column]
277
- : undefined;
278
- const toColumn = tableAt(ctx.toContract.storage, namespaceId, issue.table)?.columns[
279
- issue.column
280
- ];
281
- if (!fromColumn || !toColumn) continue;
282
- const fromType = fromColumn.nativeType;
283
- const toType = toColumn.nativeType;
284
- if (fromType === toType) continue;
267
+ if (issueOutcome(issue) !== 'not-equal') continue;
268
+ const pair = columnNodePair(issue);
269
+ if (pair === undefined) continue;
270
+ const { expected, actual } = pair;
271
+ if (!columnTypeChangedNativeOnly(expected, actual)) continue;
272
+
273
+ const fromType = actual.nativeType;
274
+ const toType = expected.nativeType;
285
275
  const isSafeWidening = SAFE_WIDENINGS.has(`${fromType}→${toType}`);
286
276
  if (!isSafeWidening && !dataAllowed) continue;
277
+
278
+ const ddlSchemaName = issueSchemaName(issue);
279
+ const tableName = issueTableName(issue);
280
+ if (ddlSchemaName === undefined || tableName === undefined) continue;
281
+ const schemaName = emissionSchemaName(ctx, ddlSchemaName);
282
+
287
283
  matched.push(issue);
288
- const alterOpts = buildAlterTypeOptions(namespaceId, issue.table, issue.column, ctx);
289
- const schemaForTable = resolveDdlSchemaForNamespace(ctx, namespaceId);
284
+ const { qualifiedTargetType, formatTypeExpected } = renderColumnAlterType(
285
+ expected,
286
+ ctx.codecHooks,
287
+ );
288
+ const alterOpts = {
289
+ qualifiedTargetType,
290
+ formatTypeExpected,
291
+ rawTargetTypeForLabel: qualifiedTargetType,
292
+ };
290
293
  if (isSafeWidening) {
291
- calls.push(new AlterColumnTypeCall(schemaForTable, issue.table, issue.column, alterOpts));
294
+ calls.push(new AlterColumnTypeCall(schemaName, tableName, expected.name, alterOpts));
292
295
  } else {
293
296
  calls.push(
294
297
  new DataTransformCall(
295
- `typechange-${issue.table}-${issue.column}`,
296
- `typechange-${issue.table}-${issue.column}:check`,
297
- `typechange-${issue.table}-${issue.column}:run`,
298
+ `typechange-${tableName}-${expected.name}`,
299
+ `typechange-${tableName}-${expected.name}:check`,
300
+ `typechange-${tableName}-${expected.name}:run`,
298
301
  ),
299
- new AlterColumnTypeCall(schemaForTable, issue.table, issue.column, alterOpts),
302
+ new AlterColumnTypeCall(schemaName, tableName, expected.name, alterOpts),
300
303
  );
301
304
  }
305
+
306
+ if (expected.nullable !== actual.nullable) {
307
+ if (expected.nullable) {
308
+ calls.push(new DropNotNullCall(schemaName, tableName, expected.name));
309
+ } else if (dataAllowed) {
310
+ calls.push(
311
+ new DataTransformCall(
312
+ `handle-nulls-${tableName}-${expected.name}`,
313
+ `handle-nulls-${tableName}-${expected.name}:check`,
314
+ `handle-nulls-${tableName}-${expected.name}:run`,
315
+ ),
316
+ new SetNotNullCall(schemaName, tableName, expected.name),
317
+ );
318
+ } else {
319
+ calls.push(new SetNotNullCall(schemaName, tableName, expected.name));
320
+ }
321
+ }
302
322
  }
323
+
303
324
  if (matched.length === 0) return { kind: 'no_match' };
304
325
  return {
305
326
  kind: 'match',
@@ -309,32 +330,54 @@ export const typeChangeCallStrategy: CallMigrationStrategy = (issues, ctx) => {
309
330
  };
310
331
  };
311
332
 
333
+ /**
334
+ * Whether the raw (unresolved) native type differs — the SAME comparison
335
+ * `typeChangeCallStrategy` always used (`fromColumn.nativeType !==
336
+ * toColumn.nativeType`), which for the widenable numeric/float types
337
+ * `SAFE_WIDENINGS` lists is identical to the resolved comparison
338
+ * `columnTypeChanged` (in `issue-planner.ts`) performs.
339
+ */
340
+ function columnTypeChangedNativeOnly(expected: SqlColumnIR, actual: SqlColumnIR): boolean {
341
+ return expected.nativeType !== actual.nativeType;
342
+ }
343
+
344
+ /**
345
+ * Handles `not-equal` column issues whose type did NOT change but
346
+ * nullability tightened (contract requires NOT NULL, live column is
347
+ * nullable). A type-changed issue's nullability delta (if any) is already
348
+ * handled by `typeChangeCallStrategy`, which runs first — this strategy
349
+ * only ever sees issues that strategy left behind.
350
+ */
312
351
  export const nullableTighteningCallStrategy: CallMigrationStrategy = (issues, ctx) => {
313
352
  // `DataTransformCall` is operation class `'data'`. When the policy excludes
314
- // it (`db update` / `db init`), skip so `mapIssueToCall` emits a direct
353
+ // it (`db update` / `db init`), skip so `mapNodeIssueToCall` emits a direct
315
354
  // `SET NOT NULL` instead.
316
355
  if (!ctx.policy.allowedOperationClasses.includes('data')) return { kind: 'no_match' };
317
356
 
318
- const matched: SchemaIssue[] = [];
357
+ const matched: SchemaDiffIssue[] = [];
319
358
  const calls: PostgresOpFactoryCall[] = [];
320
359
 
321
360
  for (const issue of issues) {
322
- if (issue.kind !== 'nullability_mismatch' || !issue.table || !issue.column) continue;
323
-
324
- const namespaceId = resolveNamespaceIdForIssue(issue);
325
- const column = tableAt(ctx.toContract.storage, namespaceId, issue.table)?.columns[issue.column];
326
- if (!column) continue;
327
- if (column.nullable === true) continue;
361
+ if (issueOutcome(issue) !== 'not-equal') continue;
362
+ const pair = columnNodePair(issue);
363
+ if (pair === undefined) continue;
364
+ const { expected, actual } = pair;
365
+ if (columnTypeChangedNativeOnly(expected, actual)) continue; // typeChangeCallStrategy's concern
366
+ if (expected.nullable !== false || actual.nullable !== true) continue;
367
+
368
+ const ddlSchemaName = issueSchemaName(issue);
369
+ const tableName = issueTableName(issue);
370
+ if (ddlSchemaName === undefined || tableName === undefined) continue;
371
+ const schemaName = emissionSchemaName(ctx, ddlSchemaName);
328
372
 
329
373
  matched.push(issue);
330
- const schemaForTable = resolveDdlSchemaForNamespace(ctx, namespaceId);
331
374
  calls.push(
332
375
  new DataTransformCall(
333
- `handle-nulls-${issue.table}-${issue.column}`,
334
- `handle-nulls-${issue.table}-${issue.column}:check`,
335
- `handle-nulls-${issue.table}-${issue.column}:run`,
376
+ `handle-nulls-${tableName}-${expected.name}`,
377
+ `handle-nulls-${tableName}-${expected.name}:check`,
378
+ `handle-nulls-${tableName}-${expected.name}:run`,
336
379
  ),
337
- new SetNotNullCall(schemaForTable, issue.table, issue.column),
380
+ new SetNotNullCall(schemaName, tableName, expected.name),
338
381
  );
339
382
  }
340
383
 
@@ -384,9 +427,13 @@ function checkValueSetsEqual(a: readonly string[], b: readonly string[]): boolea
384
427
  /**
385
428
  * Plans check-constraint migrations for `enumType`-authored columns.
386
429
  *
387
- * Walks every namespace's tables in the target contract. For each table that
388
- * carries `checks`, diffs the contract-expected checks against the live
389
- * schema's checks:
430
+ * Walks every namespace's tables in the target contract (the check nodes'
431
+ * resolved `permittedValues` are ultimately sourced from the same
432
+ * contract-declared value sets, so walking the contract directly is the
433
+ * simplest faithful port — the strategy's decisions never depend on which
434
+ * ISSUES happen to be in the input list, only on the contract + live schema
435
+ * shapes). For each table that carries `checks`, diffs the contract-expected
436
+ * checks against the live schema's checks:
390
437
  *
391
438
  * - Check in contract, absent from live DB → `AddCheckConstraintCall`.
392
439
  * - Check in live DB, absent from contract → `DropCheckConstraintCall`.
@@ -394,7 +441,9 @@ function checkValueSetsEqual(a: readonly string[], b: readonly string[]): boolea
394
441
  * then `AddCheckConstraintCall` (drop + recreate; a check predicate cannot
395
442
  * be altered in place).
396
443
  *
397
- * Consumes `check_missing`, `check_removed`, and `check_mismatch` issues.
444
+ * Consumes every `sql-check-constraint` issue on a table this walk handles
445
+ * (not-found/not-expected/not-equal), leaving check issues on tables with
446
+ * NO contract checks to `mapNodeIssueToCall`'s `not-expected` fallback.
398
447
  */
399
448
  export const checkConstraintPlanCallStrategy: CallMigrationStrategy = (issues, ctx) => {
400
449
  const calls: PostgresOpFactoryCall[] = [];
@@ -411,7 +460,7 @@ export const checkConstraintPlanCallStrategy: CallMigrationStrategy = (issues, c
411
460
 
412
461
  for (const contractCheck of contractChecks) {
413
462
  const liveCheck = liveChecks.find((c) => c.name === contractCheck.name);
414
- const issueKey = `${tableName}${contractCheck.name}`;
463
+ const issueKey = `${tableName} ${contractCheck.name}`;
415
464
  if (!liveCheck) {
416
465
  calls.push(
417
466
  new AddCheckConstraintCall(
@@ -446,7 +495,7 @@ export const checkConstraintPlanCallStrategy: CallMigrationStrategy = (issues, c
446
495
  for (const liveCheck of liveChecks) {
447
496
  const inContract = contractChecks.some((c) => c.name === liveCheck.name);
448
497
  if (!inContract) {
449
- const issueKey = `${tableName}${liveCheck.name}`;
498
+ const issueKey = `${tableName} ${liveCheck.name}`;
450
499
  calls.push(new DropCheckConstraintCall(ddlSchema, tableName, liveCheck.name));
451
500
  handledIssueKeys.add(issueKey);
452
501
  }
@@ -457,16 +506,14 @@ export const checkConstraintPlanCallStrategy: CallMigrationStrategy = (issues, c
457
506
  if (calls.length === 0 && handledIssueKeys.size === 0) return { kind: 'no_match' };
458
507
 
459
508
  const remaining = issues.filter((issue) => {
460
- if (
461
- issue.kind !== 'check_missing' &&
462
- issue.kind !== 'check_removed' &&
463
- issue.kind !== 'check_mismatch'
464
- ) {
465
- return true;
466
- }
467
- if (!issue.table || !issue.indexOrConstraint) return true;
468
- const key = `${issue.table}${issue.indexOrConstraint}`;
469
- return !handledIssueKeys.has(key);
509
+ const node = issueNode(issue);
510
+ if (node === undefined || node.nodeKind !== RelationalSchemaNodeKind.check) return true;
511
+ const tableName = issueTableName(issue);
512
+ if (tableName === undefined) return true;
513
+ const checkName = blindCast<{ readonly name: string }, 'a check node always carries a name'>(
514
+ node,
515
+ ).name;
516
+ return !handledIssueKeys.has(`${tableName} ${checkName}`);
470
517
  });
471
518
 
472
519
  return { kind: 'match', issues: remaining, calls };
@@ -475,14 +522,16 @@ export const checkConstraintPlanCallStrategy: CallMigrationStrategy = (issues, c
475
522
  /**
476
523
  * Dispatches codec-typed storage types through their codec's
477
524
  * `planTypeOperations` hook (the authoritative source for codec-driven DDL
478
- * such as custom type creation).
525
+ * such as custom type creation). Codec extension/type ops are not modeled as
526
+ * diff nodes — this strategy drives entirely off `ctx.toContract.storage.types`
527
+ * + codec hooks, consuming nothing from the node issue list (there is no
528
+ * node-vocabulary equivalent of `type_missing` / `enum_values_changed`).
479
529
  */
480
530
  export const storageTypePlanCallStrategy: CallMigrationStrategy = (issues, ctx) => {
481
531
  const storageTypes = ctx.toContract.storage.types ?? {};
482
532
  if (Object.keys(storageTypes).length === 0) return { kind: 'no_match' };
483
533
 
484
534
  const calls: PostgresOpFactoryCall[] = [];
485
- const handledTypeNames = new Set<string>();
486
535
 
487
536
  for (const [typeName, typeInstance] of Object.entries(storageTypes).sort(([a], [b]) =>
488
537
  a.localeCompare(b),
@@ -499,11 +548,6 @@ export const storageTypePlanCallStrategy: CallMigrationStrategy = (issues, ctx)
499
548
  policy: ctx.policy,
500
549
  });
501
550
  if (!planResult) continue;
502
- if (planResult.operations.length === 0) {
503
- handledTypeNames.add(typeName);
504
- continue;
505
- }
506
- handledTypeNames.add(typeName);
507
551
  for (const op of planResult.operations) {
508
552
  calls.push(
509
553
  new RawSqlCall({
@@ -517,38 +561,28 @@ export const storageTypePlanCallStrategy: CallMigrationStrategy = (issues, ctx)
517
561
  }
518
562
  }
519
563
 
520
- const remaining = issues.filter(
521
- (issue) =>
522
- !(
523
- (issue.kind === 'type_missing' || issue.kind === 'enum_values_changed') &&
524
- issue.typeName &&
525
- handledTypeNames.has(issue.typeName)
526
- ),
527
- );
528
-
529
- if (calls.length === 0 && remaining.length === issues.length) {
530
- return { kind: 'no_match' };
531
- }
532
-
533
- return { kind: 'match', issues: remaining, calls };
564
+ if (calls.length === 0) return { kind: 'no_match' };
565
+ return { kind: 'match', issues, calls };
534
566
  };
535
567
 
536
568
  /**
537
- * Handles `missing_column` issues for NOT NULL columns without a contract
538
- * default. Replaces the walk-schema `buildAddColumnItem` non-default branches.
569
+ * Handles `not-found` column issues for NOT NULL columns without a contract
570
+ * default. Replaces the legacy `buildAddColumnItem` non-default branches.
539
571
  *
540
572
  * Two shapes:
541
573
  * - Shared-temp-default safe: emit a single atomic composite op (add
542
574
  * nullable → backfill identity value → `SET NOT NULL` → `DROP DEFAULT`).
575
+ * The temp-default value is resolved from the column node's `codecRef`
576
+ * (`resolveColumnTemporaryDefault`, wrapping `resolveIdentityValue`).
543
577
  * - Empty-table guarded: emit a hand-built op with a `tableIsEmptyCheck`
544
578
  * precheck so the failure message is "table is not empty" rather than the
545
579
  * raw PG NOT NULL violation.
546
580
  *
547
- * "Normal" missing_column cases (nullable or has a contract default) are left
548
- * for `mapIssueToCall`'s default `AddColumnCall` emission.
581
+ * "Normal" not-found column cases (nullable or has a contract default) are
582
+ * left for `mapNodeIssueToCall`'s default `AddColumnCall` emission.
549
583
  */
550
584
  export const notNullAddColumnCallStrategy: CallMigrationStrategy = (issues, ctx) => {
551
- const matched: SchemaIssue[] = [];
585
+ const matched: SchemaDiffIssue[] = [];
552
586
  const calls: PostgresOpFactoryCall[] = [];
553
587
 
554
588
  const schemaLookups = buildSchemaLookupMap(ctx.schema);
@@ -557,40 +591,47 @@ export const notNullAddColumnCallStrategy: CallMigrationStrategy = (issues, ctx)
557
591
  const mutableStorageTypes = ctx.storageTypes as Record<string, StorageTypeInstance>;
558
592
 
559
593
  for (const issue of issues) {
560
- if (issue.kind !== 'missing_column' || !issue.table || !issue.column) continue;
561
- const namespaceId = resolveNamespaceIdForIssue(issue);
562
- const contractTable = tableAt(ctx.toContract.storage, namespaceId, issue.table);
563
- const column = contractTable?.columns[issue.column];
564
- if (!column) continue;
565
-
566
- const notNull = column.nullable !== true;
567
- const hasDefault = column.default !== undefined;
568
- if (!notNull || hasDefault) continue;
569
-
570
- const schemaTable = ctx.schema.tables[issue.table];
594
+ if (issueOutcome(issue) !== 'not-found') continue;
595
+ const node = issueNode(issue);
596
+ if (node === undefined || node.nodeKind !== RelationalSchemaNodeKind.column) continue;
597
+ const expected = blindCast<
598
+ SqlColumnIR,
599
+ 'a not-found column issue always carries the expected column node'
600
+ >(issue.expected);
601
+ if (expected.nullable !== false || expected.resolvedDefault !== undefined) continue;
602
+
603
+ const ddlSchemaName = issueSchemaName(issue);
604
+ const tableName = issueTableName(issue);
605
+ if (ddlSchemaName === undefined || tableName === undefined) continue;
606
+
607
+ const namespaceId = namespaceIdForDdlSchema(ctx, ddlSchemaName);
608
+ const schemaName = namespaceId === UNBOUND_NAMESPACE_ID ? UNBOUND_NAMESPACE_ID : ddlSchemaName;
609
+ const contractTable = tableAt(ctx.toContract.storage, namespaceId, tableName);
610
+ const column = contractTable?.columns[expected.name];
611
+ if (!contractTable || !column) continue;
612
+
613
+ const schemaTable = ctx.schema.tables[tableName];
571
614
  if (!schemaTable) continue;
572
615
 
573
- const temporaryDefault = resolveIdentityValue(column, mutableCodecHooks, mutableStorageTypes);
574
- const schemaLookup = schemaLookups.get(issue.table);
616
+ const temporaryDefault = resolveColumnTemporaryDefault(expected, ctx.codecHooks);
617
+ const schemaLookup = schemaLookups.get(tableName);
575
618
  const canUseSharedTempDefault =
576
619
  temporaryDefault !== null &&
577
620
  canUseSharedTemporaryDefaultStrategy({
578
621
  table: contractTable,
579
622
  schemaTable,
580
623
  schemaLookup,
581
- columnName: issue.column,
624
+ columnName: expected.name,
582
625
  });
583
626
 
584
627
  matched.push(issue);
585
628
 
586
- const schemaForTable = resolveDdlSchemaForNamespace(ctx, namespaceId);
587
-
588
629
  if (canUseSharedTempDefault && temporaryDefault !== null) {
589
630
  calls.push(
590
631
  new AddNotNullColumnWithTempDefaultCall({
591
- schemaName: schemaForTable,
592
- tableName: issue.table,
593
- columnName: issue.column,
632
+ schemaName,
633
+ tableName,
634
+ columnName: expected.name,
594
635
  column,
595
636
  codecHooks: mutableCodecHooks,
596
637
  storageTypes: mutableStorageTypes,
@@ -600,20 +641,12 @@ export const notNullAddColumnCallStrategy: CallMigrationStrategy = (issues, ctx)
600
641
  continue;
601
642
  }
602
643
 
603
- const qualified = qualifyTableName(schemaForTable, issue.table);
604
644
  calls.push(
605
645
  new AddNotNullColumnDirectCall(
606
- schemaForTable,
607
- issue.table,
608
- issue.column,
609
- buildAddColumnSql(
610
- qualified,
611
- issue.column,
612
- column,
613
- mutableCodecHooks,
614
- undefined,
615
- mutableStorageTypes,
616
- ),
646
+ schemaName,
647
+ tableName,
648
+ expected.name,
649
+ renderColumnDdl(expected.name, expected, ctx.codecHooks),
617
650
  ),
618
651
  );
619
652
  }
@@ -653,8 +686,7 @@ function canUseSharedTemporaryDefaultStrategy(options: {
653
686
  }
654
687
 
655
688
  for (const foreignKey of table.foreignKeys) {
656
- if (foreignKey.constraint === false || !foreignKey.source.columns.includes(columnName))
657
- continue;
689
+ if (!foreignKey.source.columns.includes(columnName)) continue;
658
690
  if (!schemaLookup || !hasForeignKey(schemaLookup, foreignKey)) return false;
659
691
  }
660
692
 
@@ -665,7 +697,7 @@ function canUseSharedTemporaryDefaultStrategy(options: {
665
697
  * Ordered list of Postgres planner strategies, shared by `migration plan`
666
698
  * and `db update` / `db init`. The issue planner runs each strategy in
667
699
  * order, letting it consume any issues it handles, and routes whatever's
668
- * left through `mapIssueToCall`. Behavior diverges purely on
700
+ * left through `mapNodeIssueToCall`. Behavior diverges purely on
669
701
  * `policy.allowedOperationClasses`:
670
702
  *
671
703
  * - When `'data'` is allowed (`migration plan`), the data-safe strategies
@@ -675,9 +707,9 @@ function canUseSharedTemporaryDefaultStrategy(options: {
675
707
  *
676
708
  * - When `'data'` is not allowed (`db update` / `db init`), the
677
709
  * placeholder-emitting strategies short-circuit to `no_match`, leaving
678
- * the issue for the downstream walk-schema strategies
679
- * (`storageTypePlanCallStrategy`, `notNullAddColumnCallStrategy`) or the
680
- * `mapIssueToCall` default to handle with direct DDL.
710
+ * the issue for the downstream strategies (`storageTypePlanCallStrategy`,
711
+ * `notNullAddColumnCallStrategy`) or the `mapNodeIssueToCall` default to
712
+ * handle with direct DDL.
681
713
  *
682
714
  * Codec-typed storage type entries are dispatched through
683
715
  * `storageTypePlanCallStrategy`.