@prisma-next/target-postgres 0.14.0-dev.5 → 0.14.0-dev.50

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 (238) hide show
  1. package/dist/authoring-DzHOdWTl.mjs +364 -0
  2. package/dist/authoring-DzHOdWTl.mjs.map +1 -0
  3. package/dist/canonicalize-Bn3BM1Cn.mjs +46 -0
  4. package/dist/canonicalize-Bn3BM1Cn.mjs.map +1 -0
  5. package/dist/canonicalize-CAbXtVtB.d.mts +37 -0
  6. package/dist/canonicalize-CAbXtVtB.d.mts.map +1 -0
  7. package/dist/{codec-ids-CnXu9Qy3.d.mts → codec-ids-BzrFF-I4.d.mts} +3 -2
  8. package/dist/codec-ids-BzrFF-I4.d.mts.map +1 -0
  9. package/dist/{codec-ids-BvytN2P8.mjs → codec-ids-C_-Hj6bL.mjs} +3 -2
  10. package/dist/codec-ids-C_-Hj6bL.mjs.map +1 -0
  11. package/dist/codec-ids.d.mts +2 -2
  12. package/dist/codec-ids.mjs +2 -2
  13. package/dist/{codec-types-DHCkwPKE.d.mts → codec-types-BDVmvKAW.d.mts} +3 -2
  14. package/dist/codec-types-BDVmvKAW.d.mts.map +1 -0
  15. package/dist/codec-types.d.mts +1 -1
  16. package/dist/{codecs--0A5_4Bq.d.mts → codecs-Dk2sxGLv.d.mts} +77 -4
  17. package/dist/codecs-Dk2sxGLv.d.mts.map +1 -0
  18. package/dist/codecs.d.mts +1 -1
  19. package/dist/codecs.mjs +117 -2
  20. package/dist/codecs.mjs.map +1 -1
  21. package/dist/contract-free.d.mts +8 -3
  22. package/dist/contract-free.d.mts.map +1 -1
  23. package/dist/contract-free.mjs +4 -4
  24. package/dist/control-policy-DUFgy9jO.mjs +1103 -0
  25. package/dist/control-policy-DUFgy9jO.mjs.map +1 -0
  26. package/dist/control.d.mts +2 -2
  27. package/dist/control.d.mts.map +1 -1
  28. package/dist/control.mjs +757 -35
  29. package/dist/control.mjs.map +1 -1
  30. package/dist/data-transform-BOWpliq8.mjs.map +1 -1
  31. package/dist/{data-transform-DDgWdB5o.d.mts → data-transform-DPzwBMhW.d.mts} +3 -3
  32. package/dist/{data-transform-DDgWdB5o.d.mts.map → data-transform-DPzwBMhW.d.mts.map} +1 -1
  33. package/dist/data-transform.d.mts +1 -1
  34. package/dist/{ddl-QDyOSeLc.mjs → ddl-ujxH2A7r.mjs} +63 -6
  35. package/dist/ddl-ujxH2A7r.mjs.map +1 -0
  36. package/dist/ddl.d.mts +3 -2
  37. package/dist/ddl.mjs +2 -2
  38. package/dist/default-normalizer.d.mts +1 -1
  39. package/dist/default-normalizer.d.mts.map +1 -1
  40. package/dist/default-normalizer.mjs +247 -1
  41. package/dist/default-normalizer.mjs.map +1 -0
  42. package/dist/descriptor-meta-CuGDsrG4.mjs +19 -0
  43. package/dist/descriptor-meta-CuGDsrG4.mjs.map +1 -0
  44. package/dist/descriptor-meta-runtime-UwYdV8fD.mjs +12 -0
  45. package/dist/descriptor-meta-runtime-UwYdV8fD.mjs.map +1 -0
  46. package/dist/diff-database-schema-BmSmWcaY.mjs +313 -0
  47. package/dist/diff-database-schema-BmSmWcaY.mjs.map +1 -0
  48. package/dist/diff-database-schema.d.mts +46 -0
  49. package/dist/diff-database-schema.d.mts.map +1 -0
  50. package/dist/diff-database-schema.mjs +2 -0
  51. package/dist/issue-planner.d.mts +35 -11
  52. package/dist/issue-planner.d.mts.map +1 -1
  53. package/dist/issue-planner.mjs +2 -2
  54. package/dist/migration.d.mts +5 -5
  55. package/dist/migration.d.mts.map +1 -1
  56. package/dist/migration.mjs +4 -4
  57. package/dist/namespace-ids-xtp_ZY86.mjs +7 -0
  58. package/dist/namespace-ids-xtp_ZY86.mjs.map +1 -0
  59. package/dist/native-type-normalizer.mjs +37 -1
  60. package/dist/native-type-normalizer.mjs.map +1 -0
  61. package/dist/nodes-CSkPX3rI.d.mts +120 -0
  62. package/dist/nodes-CSkPX3rI.d.mts.map +1 -0
  63. package/dist/{nodes-Bbhs2rwj.mjs → nodes-Dh__NS58.mjs} +56 -2
  64. package/dist/nodes-Dh__NS58.mjs.map +1 -0
  65. package/dist/{op-factory-call-D_p5vxwt.mjs → op-factory-call-BKRnMgrB.mjs} +205 -31
  66. package/dist/op-factory-call-BKRnMgrB.mjs.map +1 -0
  67. package/dist/{op-factory-call-DmQEc3XV.d.mts → op-factory-call-Dt-Jx5JS.d.mts} +46 -15
  68. package/dist/op-factory-call-Dt-Jx5JS.d.mts.map +1 -0
  69. package/dist/op-factory-call.d.mts +2 -2
  70. package/dist/op-factory-call.mjs +2 -2
  71. package/dist/pack.d.mts +115 -3
  72. package/dist/pack.d.mts.map +1 -1
  73. package/dist/pack.mjs +1 -1
  74. package/dist/planner-BMBRPzsv.mjs +367 -0
  75. package/dist/planner-BMBRPzsv.mjs.map +1 -0
  76. package/dist/{planner-ddl-builders-B2wOwLqI.mjs → planner-ddl-builders-B8Nn6nHN.mjs} +12 -27
  77. package/dist/planner-ddl-builders-B8Nn6nHN.mjs.map +1 -0
  78. package/dist/planner-ddl-builders.d.mts +4 -6
  79. package/dist/planner-ddl-builders.d.mts.map +1 -1
  80. package/dist/planner-ddl-builders.mjs +2 -2
  81. package/dist/planner-identity-values-CJPha2Sz.mjs.map +1 -1
  82. package/dist/planner-identity-values.d.mts +8 -2
  83. package/dist/planner-identity-values.d.mts.map +1 -1
  84. package/dist/planner-identity-values.mjs +2 -2
  85. package/dist/{planner-produced-postgres-migration-QqHa2C2l.d.mts → planner-produced-postgres-migration-C0a7I1-e.d.mts} +4 -4
  86. package/dist/{planner-produced-postgres-migration-QqHa2C2l.d.mts.map → planner-produced-postgres-migration-C0a7I1-e.d.mts.map} +1 -1
  87. package/dist/{planner-produced-postgres-migration-Cji5vxUf.mjs → planner-produced-postgres-migration-Dp6IO_gj.mjs} +3 -3
  88. package/dist/{planner-produced-postgres-migration-Cji5vxUf.mjs.map → planner-produced-postgres-migration-Dp6IO_gj.mjs.map} +1 -1
  89. package/dist/planner-produced-postgres-migration.d.mts +1 -1
  90. package/dist/planner-produced-postgres-migration.mjs +1 -1
  91. package/dist/{planner-sql-checks-jqUUGyQR.mjs → planner-sql-checks-Cze3vMfe.mjs} +2 -2
  92. package/dist/{planner-sql-checks-jqUUGyQR.mjs.map → planner-sql-checks-Cze3vMfe.mjs.map} +1 -1
  93. package/dist/planner-sql-checks.d.mts +2 -2
  94. package/dist/planner-sql-checks.d.mts.map +1 -1
  95. package/dist/planner-sql-checks.mjs +1 -1
  96. package/dist/{planner-target-details-CIY6tLeo.d.mts → planner-target-details-HkP4RrRO.d.mts} +2 -2
  97. package/dist/planner-target-details-HkP4RrRO.d.mts.map +1 -0
  98. package/dist/planner-target-details.d.mts +1 -1
  99. package/dist/planner.d.mts +55 -17
  100. package/dist/planner.d.mts.map +1 -1
  101. package/dist/planner.mjs +3 -2
  102. package/dist/{postgres-contract-serializer-k3TAcPMY.mjs → postgres-contract-view-3xfUgjnC.mjs} +44 -41
  103. package/dist/postgres-contract-view-3xfUgjnC.mjs.map +1 -0
  104. package/dist/postgres-contract-view-C9j5cqP0.d.mts +34 -0
  105. package/dist/postgres-contract-view-C9j5cqP0.d.mts.map +1 -0
  106. package/dist/postgres-database-schema-node-C9bTuMrd.d.mts +190 -0
  107. package/dist/postgres-database-schema-node-C9bTuMrd.d.mts.map +1 -0
  108. package/dist/{postgres-migration-B5jKrXv3.mjs → postgres-migration-D0jwzYDT.mjs} +21 -2
  109. package/dist/postgres-migration-D0jwzYDT.mjs.map +1 -0
  110. package/dist/{postgres-migration-Y4YBJqkS.d.mts → postgres-migration-DFaxuRyI.d.mts} +20 -7
  111. package/dist/postgres-migration-DFaxuRyI.d.mts.map +1 -0
  112. package/dist/postgres-native-enum-xdiqmBf_.mjs +41 -0
  113. package/dist/postgres-native-enum-xdiqmBf_.mjs.map +1 -0
  114. package/dist/postgres-rls-policy--CSddvMR.d.mts +52 -0
  115. package/dist/postgres-rls-policy--CSddvMR.d.mts.map +1 -0
  116. package/dist/{postgres-schema-COGZ1ark.mjs → postgres-schema-CajYAwny.mjs} +135 -7
  117. package/dist/postgres-schema-CajYAwny.mjs.map +1 -0
  118. package/dist/postgres-schema-DqnhQRvR.d.mts +226 -0
  119. package/dist/postgres-schema-DqnhQRvR.d.mts.map +1 -0
  120. package/dist/postgres-table-schema-node-BgaSrxYN.mjs +249 -0
  121. package/dist/postgres-table-schema-node-BgaSrxYN.mjs.map +1 -0
  122. package/dist/render-ops.d.mts +1 -1
  123. package/dist/{render-typescript-KMgosran.mjs → render-typescript-7yqILcwr.mjs} +45 -16
  124. package/dist/render-typescript-7yqILcwr.mjs.map +1 -0
  125. package/dist/render-typescript.mjs +1 -1
  126. package/dist/resolve-ddl-schema-Bv1ZvjX8.mjs +22 -0
  127. package/dist/resolve-ddl-schema-Bv1ZvjX8.mjs.map +1 -0
  128. package/dist/rls-canonicalize.d.mts +2 -0
  129. package/dist/rls-canonicalize.mjs +2 -0
  130. package/dist/runtime.d.mts +6 -8
  131. package/dist/runtime.d.mts.map +1 -1
  132. package/dist/runtime.mjs +3 -3
  133. package/dist/runtime.mjs.map +1 -1
  134. package/dist/schema-ir-annotations.d.mts +15 -0
  135. package/dist/schema-ir-annotations.d.mts.map +1 -0
  136. package/dist/schema-ir-annotations.mjs +2 -0
  137. package/dist/schema-node-kinds-ClScchhi.mjs +76 -0
  138. package/dist/schema-node-kinds-ClScchhi.mjs.map +1 -0
  139. package/dist/types.d.mts +4 -140
  140. package/dist/types.mjs +3 -2
  141. package/package.json +23 -17
  142. package/src/contract-free/checks.ts +73 -0
  143. package/src/contract-free/ddl.ts +45 -1
  144. package/src/core/authoring.ts +287 -2
  145. package/src/core/codec-ids.ts +1 -0
  146. package/src/core/codec-type-map.ts +2 -0
  147. package/src/core/codecs.ts +141 -1
  148. package/src/core/ddl/nodes.ts +94 -2
  149. package/src/core/default-normalizer.ts +122 -2
  150. package/src/core/descriptor-meta.ts +6 -1
  151. package/src/core/entity-kinds.ts +30 -0
  152. package/src/core/migrations/bound-schema.ts +10 -0
  153. package/src/core/migrations/column-ddl-rendering.ts +117 -0
  154. package/src/core/migrations/contract-to-postgres-database-schema-node.ts +149 -0
  155. package/src/core/migrations/control-policy.ts +70 -47
  156. package/src/core/migrations/diff-database-schema.ts +280 -0
  157. package/src/core/migrations/issue-planner.ts +647 -652
  158. package/src/core/migrations/op-factory-call.ts +109 -21
  159. package/src/core/migrations/operations/columns.ts +24 -12
  160. package/src/core/migrations/operations/constraints.ts +1 -1
  161. package/src/core/migrations/operations/data-transform.ts +6 -0
  162. package/src/core/migrations/operations/dependencies.ts +2 -1
  163. package/src/core/migrations/operations/rls.ts +106 -0
  164. package/src/core/migrations/operations/shared.ts +0 -30
  165. package/src/core/migrations/planner-ddl-builders.ts +20 -58
  166. package/src/core/migrations/planner-identity-values.ts +1 -1
  167. package/src/core/migrations/planner-recipes.ts +31 -13
  168. package/src/core/migrations/planner-sql-checks.ts +1 -1
  169. package/src/core/migrations/planner-strategies.ts +248 -216
  170. package/src/core/migrations/planner-target-details.ts +3 -1
  171. package/src/core/migrations/planner.ts +297 -59
  172. package/src/core/migrations/postgres-migration.ts +34 -4
  173. package/src/core/migrations/render-typescript.ts +42 -15
  174. package/src/core/migrations/resolve-ddl-schema.ts +26 -0
  175. package/src/core/migrations/runner.ts +10 -16
  176. package/src/core/migrations/verify-postgres-namespaces.ts +47 -30
  177. package/src/core/namespace-ids.ts +3 -0
  178. package/src/core/postgres-contract-serializer.ts +41 -71
  179. package/src/core/postgres-contract-view.ts +48 -0
  180. package/src/core/postgres-native-enum.ts +51 -0
  181. package/src/core/postgres-rls-policy.ts +64 -0
  182. package/src/core/postgres-role.ts +37 -0
  183. package/src/core/postgres-schema-verifier.ts +10 -7
  184. package/src/core/postgres-schema.ts +62 -14
  185. package/src/core/postgres-validators.ts +27 -0
  186. package/src/core/psl-infer/infer-psl-contract.ts +1068 -0
  187. package/src/core/psl-infer/postgres-default-mapping.ts +16 -0
  188. package/src/core/psl-infer/postgres-type-map.ts +157 -0
  189. package/src/core/rls/canonicalize.ts +48 -0
  190. package/src/core/schema-ir/postgres-database-schema-node.ts +60 -0
  191. package/src/core/schema-ir/postgres-namespace-schema-node.ts +60 -0
  192. package/src/core/schema-ir/postgres-policy-schema-node.ts +90 -0
  193. package/src/core/schema-ir/postgres-role-schema-node.ts +63 -0
  194. package/src/core/schema-ir/postgres-table-schema-node.ts +118 -0
  195. package/src/core/schema-ir/schema-node-kinds.ts +106 -0
  196. package/src/exports/contract-free.ts +7 -1
  197. package/src/exports/control.ts +27 -33
  198. package/src/exports/ddl.ts +4 -0
  199. package/src/exports/diff-database-schema.ts +7 -0
  200. package/src/exports/issue-planner.ts +1 -1
  201. package/src/exports/migration.ts +1 -0
  202. package/src/exports/op-factory-call.ts +1 -0
  203. package/src/exports/planner-ddl-builders.ts +0 -2
  204. package/src/exports/planner-identity-values.ts +4 -1
  205. package/src/exports/planner.ts +1 -0
  206. package/src/exports/rls-canonicalize.ts +6 -0
  207. package/src/exports/runtime.ts +1 -0
  208. package/src/exports/schema-ir-annotations.ts +1 -0
  209. package/src/exports/types.ts +28 -0
  210. package/dist/codec-ids-BvytN2P8.mjs.map +0 -1
  211. package/dist/codec-ids-CnXu9Qy3.d.mts.map +0 -1
  212. package/dist/codec-types-DHCkwPKE.d.mts.map +0 -1
  213. package/dist/codecs--0A5_4Bq.d.mts.map +0 -1
  214. package/dist/ddl-QDyOSeLc.mjs.map +0 -1
  215. package/dist/default-normalizer-DyyCHQWs.mjs +0 -131
  216. package/dist/default-normalizer-DyyCHQWs.mjs.map +0 -1
  217. package/dist/descriptor-meta-CpGygXpI.mjs +0 -140
  218. package/dist/descriptor-meta-CpGygXpI.mjs.map +0 -1
  219. package/dist/issue-planner-DL6g3CmE.mjs +0 -824
  220. package/dist/issue-planner-DL6g3CmE.mjs.map +0 -1
  221. package/dist/native-type-normalizer-Bc9XJzWC.mjs +0 -38
  222. package/dist/native-type-normalizer-Bc9XJzWC.mjs.map +0 -1
  223. package/dist/nodes-Bbhs2rwj.mjs.map +0 -1
  224. package/dist/nodes-pLeLgdis.d.mts +0 -67
  225. package/dist/nodes-pLeLgdis.d.mts.map +0 -1
  226. package/dist/op-factory-call-D_p5vxwt.mjs.map +0 -1
  227. package/dist/op-factory-call-DmQEc3XV.d.mts.map +0 -1
  228. package/dist/planner-Bs_baQax.mjs +0 -324
  229. package/dist/planner-Bs_baQax.mjs.map +0 -1
  230. package/dist/planner-ddl-builders-B2wOwLqI.mjs.map +0 -1
  231. package/dist/planner-target-details-CIY6tLeo.d.mts.map +0 -1
  232. package/dist/postgres-contract-serializer-k3TAcPMY.mjs.map +0 -1
  233. package/dist/postgres-migration-B5jKrXv3.mjs.map +0 -1
  234. package/dist/postgres-migration-Y4YBJqkS.d.mts.map +0 -1
  235. package/dist/postgres-schema-COGZ1ark.mjs.map +0 -1
  236. package/dist/render-typescript-KMgosran.mjs.map +0 -1
  237. package/dist/types.d.mts.map +0 -1
  238. package/src/core/postgres-enum-type-schema.ts +0 -17
@@ -1,10 +1,15 @@
1
1
  import type { Contract } from '@prisma-next/contract/types';
2
2
  import type { ControlPolicySubject } from '@prisma-next/family-sql/control';
3
- import type { SchemaIssue } from '@prisma-next/framework-components/control';
3
+ import type { SchemaDiffIssue } from '@prisma-next/framework-components/control';
4
4
  import { entityAt, UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir';
5
5
  import type { SqlStorage, StorageTable } from '@prisma-next/sql-contract/types';
6
+ import { blindCast } from '@prisma-next/utils/casts';
6
7
  import { ifDefined } from '@prisma-next/utils/defined';
7
8
  import { isPostgresSchema } from '../postgres-schema';
9
+ import type { PostgresNamespaceSchemaNode } from '../schema-ir/postgres-namespace-schema-node';
10
+ import type { SqlSchemaDiffNode } from '../schema-ir/schema-node-kinds';
11
+ import { PostgresSchemaNodeKind } from '../schema-ir/schema-node-kinds';
12
+ import { issueColumnName } from './issue-planner';
8
13
  import type { PostgresOpFactoryCall } from './op-factory-call';
9
14
 
10
15
  /**
@@ -21,6 +26,8 @@ import type { PostgresOpFactoryCall } from './op-factory-call';
21
26
  const OBJECT_CREATION_FACTORIES: ReadonlySet<string> = new Set<string>([
22
27
  'createTable',
23
28
  'createSchema',
29
+ 'createRlsPolicy',
30
+ 'enableRowLevelSecurity',
24
31
  ]);
25
32
 
26
33
  function createsNewTopLevelObject(call: PostgresOpFactoryCall): boolean {
@@ -54,7 +61,7 @@ function resolveNamespaceIdForTable(
54
61
  return UNBOUND_NAMESPACE_ID;
55
62
  }
56
63
 
57
- function resolveNamespaceIdForDdlSchema(
64
+ export function resolveNamespaceIdForDdlSchema(
58
65
  contract: Contract<SqlStorage>,
59
66
  ddlSchemaName: string,
60
67
  ): string {
@@ -141,64 +148,80 @@ export function resolvePostgresCallControlPolicySubject(
141
148
  }
142
149
 
143
150
  /**
144
- * Issue kinds that describe the absence of a whole, top-level Postgres
145
- * object — the same kinds `createsNewTopLevelObject` recognises for calls.
146
- * Used by {@link resolvePostgresIssueCreationFactoryName} to decide whether
147
- * a `tolerated` subject permits the issue to flow into the planner
151
+ * Node kinds that describe the absence of a whole, top-level Postgres
152
+ * object — the same objects `createsNewTopLevelObject` recognises for calls.
153
+ * Used by {@link resolvePostgresNodeIssueCreationFactoryName} to decide
154
+ * whether a `tolerated` subject permits the issue to flow into the planner
148
155
  * (create-if-absent) and to seed the suppressed-subject warning's
149
- * `factoryName` when the planner is skipped.
156
+ * `factoryName` when the planner is skipped. RLS policy creation is not
157
+ * listed here — policy issues never reach this issue-based partition (they
158
+ * are routed to `planPostgresSchemaDiff` and gated via the call-based
159
+ * {@link resolvePostgresCallControlPolicySubject} instead).
150
160
  */
151
- const POSTGRES_ISSUE_CREATION_FACTORY: Readonly<Record<string, string>> = Object.freeze({
152
- missing_schema: 'createSchema',
153
- missing_table: 'createTable',
161
+ const POSTGRES_NODE_CREATION_FACTORY: Readonly<Record<string, string>> = Object.freeze({
162
+ [PostgresSchemaNodeKind.namespace]: 'createSchema',
163
+ [PostgresSchemaNodeKind.table]: 'createTable',
154
164
  });
155
165
 
156
- export function resolvePostgresIssueCreationFactoryName(issue: SchemaIssue): string | undefined {
157
- return POSTGRES_ISSUE_CREATION_FACTORY[issue.kind];
166
+ export function resolvePostgresNodeIssueCreationFactoryName(
167
+ issue: SchemaDiffIssue<SqlSchemaDiffNode>,
168
+ ): string | undefined {
169
+ if (issue.reason !== 'not-found') return undefined;
170
+ const node = issue.expected ?? issue.actual;
171
+ if (node === undefined) return undefined;
172
+ return POSTGRES_NODE_CREATION_FACTORY[node.nodeKind];
158
173
  }
159
174
 
160
175
  /**
161
- * Resolve the control-policy subject coordinate for a single
162
- * {@link SchemaIssue}. Mirrors the resolution `resolvePostgresCallControlPolicySubject`
163
- * performs for a generated DDL call, but works *off the issue* — so the
164
- * planner can partition issues by effective policy before the diff engine
165
- * runs. `createsNewObject` is derived from the issue's kind: schema/table/
166
- * type-missing issues describe a brand-new top-level object; everything else
167
- * touches an existing object.
176
+ * Resolve the control-policy subject coordinate for a single node-typed
177
+ * {@link SchemaDiffIssue}. Mirrors the resolution
178
+ * `resolvePostgresCallControlPolicySubject` performs for a generated DDL
179
+ * call, but works *off the issue* so the planner can partition issues by
180
+ * effective policy before the diff engine runs. `createsNewObject` is
181
+ * derived from the node kind + reason: a `not-found` schema/table describes
182
+ * a brand-new top-level object; everything else touches an existing object.
168
183
  *
169
- * An `extra_table` issue carries no contract namespace coordinate (the table
170
- * isn't in any contract namespace), so the subject's `namespaceId` falls
171
- * back to {@link UNBOUND_NAMESPACE_ID}; the call-side resolver does the same
172
- * for the `DropTableCall` it produces.
184
+ * A `not-expected` table carries no contract namespace coordinate (the live
185
+ * table isn't claimed by any contract namespace), so the subject's
186
+ * `namespaceId` falls back to {@link UNBOUND_NAMESPACE_ID} via
187
+ * `resolveNamespaceIdForTable`'s own fallback — the call-side resolver does
188
+ * the same for the `DropTableCall` it produces.
173
189
  */
174
- export function resolvePostgresIssueControlPolicySubject(
175
- issue: SchemaIssue,
190
+ export function resolvePostgresNodeIssueControlPolicySubject(
191
+ issue: SchemaDiffIssue<SqlSchemaDiffNode>,
176
192
  contract: Contract<SqlStorage>,
177
193
  ): ControlPolicySubject | undefined {
178
- const createsNewObject = POSTGRES_ISSUE_CREATION_FACTORY[issue.kind] !== undefined;
179
-
180
- if (issue.kind === 'missing_schema' && issue.namespaceId) {
181
- return { namespaceId: issue.namespaceId, createsNewObject };
182
- }
183
-
184
- if ('table' in issue && issue.table) {
185
- const namespaceId =
186
- 'namespaceId' in issue && issue.namespaceId
187
- ? issue.namespaceId
188
- : resolveNamespaceIdForTable(contract, issue.table, undefined);
189
- const table = entityAt<StorageTable>(contract.storage, {
190
- namespaceId,
191
- entityKind: 'table',
192
- entityName: issue.table,
193
- });
194
+ const node = issue.expected ?? issue.actual;
195
+ if (node === undefined) return undefined;
196
+
197
+ if (node.nodeKind === PostgresSchemaNodeKind.namespace) {
198
+ const namespaceNode = blindCast<
199
+ PostgresNamespaceSchemaNode,
200
+ 'a postgres-namespace diff node is always a PostgresNamespaceSchemaNode'
201
+ >(node);
194
202
  return {
195
- namespaceId,
196
- ...ifDefined('explicitNodeControlPolicy', table?.control),
197
- table: issue.table,
198
- ...ifDefined('column', 'column' in issue ? issue.column : undefined),
199
- createsNewObject,
203
+ namespaceId: resolveNamespaceIdForDdlSchema(contract, namespaceNode.schemaName),
204
+ createsNewObject: issue.reason === 'not-found',
200
205
  };
201
206
  }
202
207
 
203
- return undefined;
208
+ const tableName = issue.path[2];
209
+ if (tableName === undefined) return undefined;
210
+ const ddlSchemaName = issue.path[1];
211
+ const namespaceId = resolveNamespaceIdForTable(contract, tableName, ddlSchemaName);
212
+ const table = entityAt<StorageTable>(contract.storage, {
213
+ namespaceId,
214
+ entityKind: 'table',
215
+ entityName: tableName,
216
+ });
217
+ const createsNewObject =
218
+ node.nodeKind === PostgresSchemaNodeKind.table && issue.reason === 'not-found';
219
+
220
+ return {
221
+ namespaceId,
222
+ ...ifDefined('explicitNodeControlPolicy', table?.control),
223
+ table: tableName,
224
+ ...ifDefined('column', issueColumnName(issue)),
225
+ createsNewObject,
226
+ };
204
227
  }
@@ -0,0 +1,280 @@
1
+ import type { Contract, ControlPolicy } from '@prisma-next/contract/types';
2
+ import type { SqlSchemaDiffResult } from '@prisma-next/family-sql/control';
3
+ import { buildNativeTypeExpander } from '@prisma-next/family-sql/control';
4
+ import {
5
+ classifyDiffSubjectGranularity,
6
+ resolveSemanticSatisfaction,
7
+ } from '@prisma-next/family-sql/diff';
8
+ import type { TargetBoundComponentDescriptor } from '@prisma-next/framework-components/components';
9
+ import type { SchemaDiffIssue } from '@prisma-next/framework-components/control';
10
+ import { diffSchemas } from '@prisma-next/framework-components/control';
11
+ import type { SqlStorage } from '@prisma-next/sql-contract/types';
12
+ import type { SqlSchemaIRNode } from '@prisma-next/sql-schema-ir/types';
13
+ import { blindCast } from '@prisma-next/utils/casts';
14
+ import { ifDefined } from '@prisma-next/utils/defined';
15
+ import type { PostgresContract } from '../postgres-schema';
16
+ import { PostgresDatabaseSchemaNode } from '../schema-ir/postgres-database-schema-node';
17
+ import { PostgresNamespaceSchemaNode } from '../schema-ir/postgres-namespace-schema-node';
18
+ import { PostgresTableSchemaNode } from '../schema-ir/postgres-table-schema-node';
19
+ import {
20
+ postgresDiffSubjectGranularity,
21
+ type SqlSchemaDiffNode,
22
+ } from '../schema-ir/schema-node-kinds';
23
+ import { contractToPostgresDatabaseSchemaNode } from './contract-to-postgres-database-schema-node';
24
+ import { resolvePostgresNodeIssueControlPolicySubject } from './control-policy';
25
+
26
+ function ownedSchemaNames(expected: PostgresDatabaseSchemaNode): ReadonlySet<string> {
27
+ const policyNamespaces = Object.values(expected.namespaces).flatMap((ns) =>
28
+ Object.values(ns.tables).flatMap((t) => t.policies.map((p) => p.namespaceId)),
29
+ );
30
+ return new Set([...policyNamespaces, ...expected.existingSchemas]);
31
+ }
32
+
33
+ /**
34
+ * Applies the family's semantic-satisfaction normalization across a Postgres
35
+ * tree pair: every actual table with an expected counterpart (paired by
36
+ * namespace id, then table id) gets its unique/index child lists adjusted;
37
+ * everything else passes through untouched.
38
+ */
39
+ export function normalizePostgresActualForDiff(
40
+ expected: PostgresDatabaseSchemaNode,
41
+ actual: PostgresDatabaseSchemaNode,
42
+ ): PostgresDatabaseSchemaNode {
43
+ const namespaces: Record<string, PostgresNamespaceSchemaNode> = {};
44
+ for (const [nsId, actualNs] of Object.entries(actual.namespaces)) {
45
+ const expectedNs = expected.namespaces[nsId];
46
+ if (expectedNs === undefined) {
47
+ namespaces[nsId] = actualNs;
48
+ continue;
49
+ }
50
+ const tables: Record<string, PostgresTableSchemaNode> = {};
51
+ for (const [tableName, actualTable] of Object.entries(actualNs.tables)) {
52
+ const expectedTable = expectedNs.tables[tableName];
53
+ if (expectedTable === undefined) {
54
+ tables[tableName] = actualTable;
55
+ continue;
56
+ }
57
+ const adjusted = resolveSemanticSatisfaction({
58
+ expectedUniques: expectedTable.uniques,
59
+ expectedIndexes: expectedTable.indexes,
60
+ actualUniques: actualTable.uniques,
61
+ actualIndexes: actualTable.indexes,
62
+ });
63
+ tables[tableName] = new PostgresTableSchemaNode({
64
+ name: actualTable.name,
65
+ columns: actualTable.columns,
66
+ foreignKeys: actualTable.foreignKeys,
67
+ uniques: adjusted.actualUniques,
68
+ indexes: adjusted.actualIndexes,
69
+ ...ifDefined('primaryKey', actualTable.primaryKey),
70
+ ...ifDefined('annotations', actualTable.annotations),
71
+ ...ifDefined('checks', actualTable.checks),
72
+ policies: [...actualTable.policies],
73
+ });
74
+ }
75
+ namespaces[nsId] = new PostgresNamespaceSchemaNode({
76
+ schemaName: actualNs.schemaName,
77
+ tables,
78
+ nativeEnumTypeNames: actualNs.nativeEnumTypeNames,
79
+ });
80
+ }
81
+ return new PostgresDatabaseSchemaNode({
82
+ namespaces,
83
+ roles: [...actual.roles],
84
+ existingSchemas: [...actual.existingSchemas],
85
+ pgVersion: actual.pgVersion,
86
+ });
87
+ }
88
+
89
+ /**
90
+ * Drops contract namespaces that declare no tables from the verdict-diff
91
+ * expected tree and the relational owned-schema set. The legacy relational
92
+ * walk skipped a table-less namespace (e.g. an enums-only schema) before
93
+ * pairing, so neither its DDL schema's absence nor that schema's live
94
+ * relational contents ever reached the verdict — the pruned tree
95
+ * reproduces that. The prune loses no expected policies: policies attach
96
+ * to tables, so a table-less namespace carries none (the projection
97
+ * throws on a policy referencing an absent table). Live policies in a
98
+ * pruned schema remain governed via the full owned set (see
99
+ * {@link diffPostgresSchema}).
100
+ */
101
+ function pruneTableLessNamespaces(
102
+ expected: PostgresDatabaseSchemaNode,
103
+ ): PostgresDatabaseSchemaNode {
104
+ const namespaces = Object.fromEntries(
105
+ Object.entries(expected.namespaces).filter(([, ns]) => Object.keys(ns.tables).length > 0),
106
+ );
107
+ return new PostgresDatabaseSchemaNode({
108
+ namespaces,
109
+ roles: [...expected.roles],
110
+ existingSchemas: expected.existingSchemas.filter((s) => namespaces[s] !== undefined),
111
+ pgVersion: expected.pgVersion,
112
+ });
113
+ }
114
+
115
+ /**
116
+ * Resolves a verdict-diff issue's subject table's declared control policy
117
+ * directly from the contract, by delegating to the same node-typed resolver
118
+ * ({@link resolvePostgresNodeIssueControlPolicySubject}) the planner uses to
119
+ * gate DDL calls. `undefined` when the issue resolves to no contract table.
120
+ */
121
+ function resolveControlPolicy(
122
+ issue: SchemaDiffIssue,
123
+ contract: Contract<SqlStorage>,
124
+ ): ControlPolicy | undefined {
125
+ const nodeIssue = blindCast<
126
+ SchemaDiffIssue<SqlSchemaDiffNode>,
127
+ 'every node in a Postgres schema diff tree is a SqlSchemaDiffNode'
128
+ >(issue);
129
+ return resolvePostgresNodeIssueControlPolicySubject(nodeIssue, contract)
130
+ ?.explicitNodeControlPolicy;
131
+ }
132
+
133
+ /**
134
+ * The Postgres full-tree node diff for the family verify verdict: derive
135
+ * the expected tree (resolved leaf values, expander threaded, FK schemas
136
+ * resolved, table-less namespaces pruned), normalize the actual tree for
137
+ * semantic satisfaction, run the generic
138
+ * differ, and scope out `not-expected` findings under namespaces the
139
+ * contract does not own. Ownership is role-aware, mirroring the legacy
140
+ * decomposition: relational extras check the PRUNED owned set (the legacy
141
+ * per-namespace walk never visited a table-less namespace, so its live
142
+ * relational contents are invisible), while `structural` extras (RLS
143
+ * policies) check the FULL owned set (the legacy policy diff governed
144
+ * every contract schema regardless of tables — RLS governance does not
145
+ * shrink because a namespace declares no tables). The codec `verifyType`
146
+ * hooks run once per contract namespace with tables against that
147
+ * namespace's paired actual node (the hooks read namespace-scoped state
148
+ * such as `nativeEnumTypeNames`).
149
+ */
150
+ export function diffPostgresSchema(input: {
151
+ readonly contract: Contract<SqlStorage>;
152
+ readonly schema: SqlSchemaIRNode;
153
+ readonly frameworkComponents: ReadonlyArray<TargetBoundComponentDescriptor<'sql', string>>;
154
+ }): SqlSchemaDiffResult {
155
+ const postgresContract = blindCast<
156
+ PostgresContract,
157
+ 'diffPostgresSchema is only called with a postgres contract'
158
+ >(input.contract);
159
+ PostgresDatabaseSchemaNode.assert(input.schema);
160
+ const actual = input.schema;
161
+ const expandNativeType = buildNativeTypeExpander(input.frameworkComponents);
162
+ const fullExpected = contractToPostgresDatabaseSchemaNode(postgresContract, {
163
+ annotationNamespace: 'pg',
164
+ ...ifDefined('expandNativeType', expandNativeType),
165
+ });
166
+ const expected = pruneTableLessNamespaces(fullExpected);
167
+ const normalizedActual = normalizePostgresActualForDiff(expected, actual);
168
+ const relationalOwned = ownedSchemaNames(expected);
169
+ const structuralOwned = ownedSchemaNames(fullExpected);
170
+ const issues = diffSchemas(expected, normalizedActual).filter((issue) => {
171
+ if (issue.reason !== 'not-expected') return true;
172
+ const namespaceSegment = issue.path[1];
173
+ if (namespaceSegment === undefined) return true;
174
+ const granularity = classifyDiffSubjectGranularity(issue, postgresDiffSubjectGranularity);
175
+ const owned = granularity === 'structural' ? structuralOwned : relationalOwned;
176
+ return owned.has(namespaceSegment);
177
+ });
178
+ const namespacePairs = Object.values(expected.namespaces).map((ns) => ({
179
+ actual: actual.namespaces[ns.schemaName],
180
+ }));
181
+ return {
182
+ issues,
183
+ resolveControlPolicy: (issue) => resolveControlPolicy(issue, postgresContract),
184
+ namespacePairs,
185
+ };
186
+ }
187
+
188
+ /**
189
+ * Adds an empty namespace node to the actual tree for every expected namespace
190
+ * absent from it. The relational plan diff pairs on namespace: a contract
191
+ * namespace whose live schema does not exist yet must surface each of its
192
+ * tables as `not-found` (→ `CREATE TABLE`), NOT as a single namespace
193
+ * `not-found` that subtree-coalescing would collapse (leaving `CREATE SCHEMA`
194
+ * with no tables). Padding makes the namespaces pair, so only table/column/
195
+ * policy drift surfaces; `CREATE SCHEMA` comes separately from the synthesized
196
+ * namespace-presence stitch (`verifyPostgresNamespacePresence`), never from the
197
+ * tree diff — matching the retired per-namespace-paired relational walk, which
198
+ * paired a missing schema against an empty namespace node.
199
+ */
200
+ function padActualNamespaces(
201
+ expected: PostgresDatabaseSchemaNode,
202
+ actual: PostgresDatabaseSchemaNode,
203
+ ): PostgresDatabaseSchemaNode {
204
+ const namespaces: Record<string, PostgresNamespaceSchemaNode> = { ...actual.namespaces };
205
+ let padded = false;
206
+ for (const schemaName of Object.keys(expected.namespaces)) {
207
+ if (namespaces[schemaName] === undefined) {
208
+ namespaces[schemaName] = new PostgresNamespaceSchemaNode({
209
+ schemaName,
210
+ tables: {},
211
+ nativeEnumTypeNames: [],
212
+ });
213
+ padded = true;
214
+ }
215
+ }
216
+ if (!padded) return actual;
217
+ return new PostgresDatabaseSchemaNode({
218
+ namespaces,
219
+ roles: [...actual.roles],
220
+ existingSchemas: [...actual.existingSchemas],
221
+ pgVersion: actual.pgVersion,
222
+ });
223
+ }
224
+
225
+ export interface PostgresPlanDiff {
226
+ /** The desired ("end") tree — resolved leaf values (incl. `codecRef`) on every column, table-less namespaces pruned. */
227
+ readonly expected: PostgresDatabaseSchemaNode;
228
+ /** The live ("start") tree, padded with empty namespaces and normalized for semantic satisfaction against `expected`. */
229
+ readonly actual: PostgresDatabaseSchemaNode;
230
+ /** The one node diff over the two trees: relational + policy drift, role-aware ownership filtered. */
231
+ readonly issues: readonly SchemaDiffIssue<SqlSchemaDiffNode>[];
232
+ }
233
+
234
+ /**
235
+ * The Postgres planner's diff input: the SAME tree-building
236
+ * `diffPostgresSchema` uses (expander threaded, FK schemas resolved,
237
+ * table-less namespaces pruned, actual normalized for semantic satisfaction,
238
+ * role-aware ownership filter) plus actual namespace padding (so a missing
239
+ * schema's tables surface as `not-found` instead of a swallowed namespace
240
+ * `not-found`). One differ drives both verify and plan; this is the
241
+ * plan-side derivation. The single issue list covers tables / columns /
242
+ * constraints / indexes / defaults AND policies — the caller splits it
243
+ * (relational → `mapNodeIssueToCall`; policy → RLS ops) and stitches in
244
+ * `CREATE SCHEMA` separately.
245
+ */
246
+ export function buildPostgresPlanDiff(input: {
247
+ readonly contract: Contract<SqlStorage>;
248
+ readonly actualSchema: SqlSchemaIRNode;
249
+ readonly frameworkComponents: ReadonlyArray<TargetBoundComponentDescriptor<'sql', string>>;
250
+ }): PostgresPlanDiff {
251
+ const postgresContract = blindCast<
252
+ PostgresContract,
253
+ 'buildPostgresPlanDiff is only called with a postgres contract'
254
+ >(input.contract);
255
+ PostgresDatabaseSchemaNode.assert(input.actualSchema);
256
+ const actual = input.actualSchema;
257
+ const expandNativeType = buildNativeTypeExpander(input.frameworkComponents);
258
+ const projectionOptions = {
259
+ annotationNamespace: 'pg',
260
+ ...ifDefined('expandNativeType', expandNativeType),
261
+ };
262
+ const fullExpected = contractToPostgresDatabaseSchemaNode(postgresContract, projectionOptions);
263
+ const expected = pruneTableLessNamespaces(fullExpected);
264
+ const paddedActual = padActualNamespaces(expected, actual);
265
+ const normalizedActual = normalizePostgresActualForDiff(expected, paddedActual);
266
+ const relationalOwned = ownedSchemaNames(expected);
267
+ const structuralOwned = ownedSchemaNames(fullExpected);
268
+ const issues = blindCast<
269
+ readonly SchemaDiffIssue<SqlSchemaDiffNode>[],
270
+ 'both trees are PostgresDatabaseSchemaNodes, so every diff-issue node is a SqlSchemaDiffNode'
271
+ >(diffSchemas(expected, normalizedActual)).filter((issue) => {
272
+ if (issue.reason !== 'not-expected') return true;
273
+ const namespaceSegment = issue.path[1];
274
+ if (namespaceSegment === undefined) return true;
275
+ const granularity = classifyDiffSubjectGranularity(issue, postgresDiffSubjectGranularity);
276
+ const owned = granularity === 'structural' ? structuralOwned : relationalOwned;
277
+ return owned.has(namespaceSegment);
278
+ });
279
+ return { expected, actual: normalizedActual, issues };
280
+ }