@prisma-next/target-postgres 0.14.0-dev.4 → 0.14.0-dev.41

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 (191) hide show
  1. package/dist/authoring-BX1WHIdN.mjs +220 -0
  2. package/dist/authoring-BX1WHIdN.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-types-DHCkwPKE.d.mts → codec-types-CueCoGD0.d.mts} +2 -2
  8. package/dist/{codec-types-DHCkwPKE.d.mts.map → codec-types-CueCoGD0.d.mts.map} +1 -1
  9. package/dist/codec-types.d.mts +1 -1
  10. package/dist/{codecs--0A5_4Bq.d.mts → codecs-Ca8upop6.d.mts} +12 -1
  11. package/dist/codecs-Ca8upop6.d.mts.map +1 -0
  12. package/dist/codecs.d.mts +1 -1
  13. package/dist/codecs.mjs +34 -1
  14. package/dist/codecs.mjs.map +1 -1
  15. package/dist/contract-free.d.mts +8 -3
  16. package/dist/contract-free.d.mts.map +1 -1
  17. package/dist/contract-free.mjs +3 -3
  18. package/dist/control.d.mts +1 -1
  19. package/dist/control.d.mts.map +1 -1
  20. package/dist/control.mjs +639 -21
  21. package/dist/control.mjs.map +1 -1
  22. package/dist/data-transform-BOWpliq8.mjs.map +1 -1
  23. package/dist/{data-transform-DDgWdB5o.d.mts → data-transform-qKy1U5V9.d.mts} +2 -2
  24. package/dist/{data-transform-DDgWdB5o.d.mts.map → data-transform-qKy1U5V9.d.mts.map} +1 -1
  25. package/dist/data-transform.d.mts +1 -1
  26. package/dist/{ddl-QDyOSeLc.mjs → ddl-BmLIe1z_.mjs} +62 -5
  27. package/dist/ddl-BmLIe1z_.mjs.map +1 -0
  28. package/dist/ddl.d.mts +3 -2
  29. package/dist/ddl.mjs +2 -2
  30. package/dist/{default-normalizer-DyyCHQWs.mjs → default-normalizer-Dug8Fez9.mjs} +119 -2
  31. package/dist/default-normalizer-Dug8Fez9.mjs.map +1 -0
  32. package/dist/default-normalizer.d.mts +1 -1
  33. package/dist/default-normalizer.d.mts.map +1 -1
  34. package/dist/default-normalizer.mjs +1 -1
  35. package/dist/descriptor-meta-runtime-UwYdV8fD.mjs +12 -0
  36. package/dist/descriptor-meta-runtime-UwYdV8fD.mjs.map +1 -0
  37. package/dist/descriptor-meta-tjatyhtZ.mjs +18 -0
  38. package/dist/descriptor-meta-tjatyhtZ.mjs.map +1 -0
  39. package/dist/{issue-planner-DL6g3CmE.mjs → issue-planner-DbFvaAgu.mjs} +35 -10
  40. package/dist/issue-planner-DbFvaAgu.mjs.map +1 -0
  41. package/dist/issue-planner.d.mts +3 -3
  42. package/dist/issue-planner.d.mts.map +1 -1
  43. package/dist/issue-planner.mjs +1 -1
  44. package/dist/migration.d.mts +5 -5
  45. package/dist/migration.mjs +4 -4
  46. package/dist/nodes-CSkPX3rI.d.mts +120 -0
  47. package/dist/nodes-CSkPX3rI.d.mts.map +1 -0
  48. package/dist/{nodes-Bbhs2rwj.mjs → nodes-Dh__NS58.mjs} +56 -2
  49. package/dist/nodes-Dh__NS58.mjs.map +1 -0
  50. package/dist/{op-factory-call-DmQEc3XV.d.mts → op-factory-call-CJWUxyUW.d.mts} +44 -13
  51. package/dist/op-factory-call-CJWUxyUW.d.mts.map +1 -0
  52. package/dist/{op-factory-call-D_p5vxwt.mjs → op-factory-call-t65c4jyY.mjs} +201 -28
  53. package/dist/op-factory-call-t65c4jyY.mjs.map +1 -0
  54. package/dist/op-factory-call.d.mts +1 -1
  55. package/dist/op-factory-call.mjs +1 -1
  56. package/dist/pack.d.mts +74 -2
  57. package/dist/pack.d.mts.map +1 -1
  58. package/dist/pack.mjs +1 -1
  59. package/dist/planner-CG2pLHk-.mjs +640 -0
  60. package/dist/planner-CG2pLHk-.mjs.map +1 -0
  61. package/dist/{planner-ddl-builders-B2wOwLqI.mjs → planner-ddl-builders-B8Nn6nHN.mjs} +12 -27
  62. package/dist/planner-ddl-builders-B8Nn6nHN.mjs.map +1 -0
  63. package/dist/planner-ddl-builders.d.mts +2 -4
  64. package/dist/planner-ddl-builders.d.mts.map +1 -1
  65. package/dist/planner-ddl-builders.mjs +2 -2
  66. package/dist/{planner-produced-postgres-migration-Cji5vxUf.mjs → planner-produced-postgres-migration-BolD0Mvc.mjs} +3 -3
  67. package/dist/{planner-produced-postgres-migration-Cji5vxUf.mjs.map → planner-produced-postgres-migration-BolD0Mvc.mjs.map} +1 -1
  68. package/dist/{planner-produced-postgres-migration-QqHa2C2l.d.mts → planner-produced-postgres-migration-Cz9--XOT.d.mts} +4 -4
  69. package/dist/{planner-produced-postgres-migration-QqHa2C2l.d.mts.map → planner-produced-postgres-migration-Cz9--XOT.d.mts.map} +1 -1
  70. package/dist/planner-produced-postgres-migration.d.mts +1 -1
  71. package/dist/planner-produced-postgres-migration.mjs +1 -1
  72. package/dist/{planner-sql-checks-jqUUGyQR.mjs → planner-sql-checks-DE9c4519.mjs} +2 -2
  73. package/dist/{planner-sql-checks-jqUUGyQR.mjs.map → planner-sql-checks-DE9c4519.mjs.map} +1 -1
  74. package/dist/planner-sql-checks.mjs +1 -1
  75. package/dist/{planner-target-details-CIY6tLeo.d.mts → planner-target-details-HkP4RrRO.d.mts} +2 -2
  76. package/dist/planner-target-details-HkP4RrRO.d.mts.map +1 -0
  77. package/dist/planner-target-details.d.mts +1 -1
  78. package/dist/planner.d.mts +82 -4
  79. package/dist/planner.d.mts.map +1 -1
  80. package/dist/planner.mjs +2 -2
  81. package/dist/{postgres-contract-serializer-k3TAcPMY.mjs → postgres-contract-view-BegduLr6.mjs} +50 -23
  82. package/dist/postgres-contract-view-BegduLr6.mjs.map +1 -0
  83. package/dist/postgres-contract-view-C9j5cqP0.d.mts +34 -0
  84. package/dist/postgres-contract-view-C9j5cqP0.d.mts.map +1 -0
  85. package/dist/postgres-database-schema-node-os8ogEap.d.mts +183 -0
  86. package/dist/postgres-database-schema-node-os8ogEap.d.mts.map +1 -0
  87. package/dist/{postgres-migration-B5jKrXv3.mjs → postgres-migration-BfMmK6tv.mjs} +21 -2
  88. package/dist/postgres-migration-BfMmK6tv.mjs.map +1 -0
  89. package/dist/{postgres-migration-Y4YBJqkS.d.mts → postgres-migration-D7kNVWVG.d.mts} +19 -6
  90. package/dist/postgres-migration-D7kNVWVG.d.mts.map +1 -0
  91. package/dist/postgres-rls-policy--CSddvMR.d.mts +52 -0
  92. package/dist/postgres-rls-policy--CSddvMR.d.mts.map +1 -0
  93. package/dist/{postgres-schema-COGZ1ark.mjs → postgres-schema-BLIHBEfX.mjs} +108 -5
  94. package/dist/postgres-schema-BLIHBEfX.mjs.map +1 -0
  95. package/dist/postgres-schema-BywoeKlt.d.mts +189 -0
  96. package/dist/postgres-schema-BywoeKlt.d.mts.map +1 -0
  97. package/dist/postgres-table-schema-node-1nNc8XDT.mjs +248 -0
  98. package/dist/postgres-table-schema-node-1nNc8XDT.mjs.map +1 -0
  99. package/dist/render-ops.d.mts +1 -1
  100. package/dist/{render-typescript-KMgosran.mjs → render-typescript-7yqILcwr.mjs} +45 -16
  101. package/dist/render-typescript-7yqILcwr.mjs.map +1 -0
  102. package/dist/render-typescript.mjs +1 -1
  103. package/dist/resolve-ddl-schema-CL1RR1mx.mjs +20 -0
  104. package/dist/resolve-ddl-schema-CL1RR1mx.mjs.map +1 -0
  105. package/dist/rls-canonicalize.d.mts +2 -0
  106. package/dist/rls-canonicalize.mjs +2 -0
  107. package/dist/runtime.d.mts +7 -6
  108. package/dist/runtime.d.mts.map +1 -1
  109. package/dist/runtime.mjs +3 -3
  110. package/dist/runtime.mjs.map +1 -1
  111. package/dist/schema-ir-annotations.d.mts +14 -0
  112. package/dist/schema-ir-annotations.d.mts.map +1 -0
  113. package/dist/schema-ir-annotations.mjs +2 -0
  114. package/dist/types.d.mts +4 -140
  115. package/dist/types.mjs +3 -2
  116. package/package.json +22 -17
  117. package/src/contract-free/checks.ts +73 -0
  118. package/src/contract-free/ddl.ts +45 -1
  119. package/src/core/authoring.ts +115 -1
  120. package/src/core/codecs.ts +35 -1
  121. package/src/core/ddl/nodes.ts +94 -2
  122. package/src/core/default-normalizer.ts +122 -2
  123. package/src/core/descriptor-meta.ts +4 -0
  124. package/src/core/entity-kinds.ts +16 -0
  125. package/src/core/migrations/bound-schema.ts +10 -0
  126. package/src/core/migrations/contract-to-postgres-database-schema-node.ts +125 -0
  127. package/src/core/migrations/control-policy.ts +3 -0
  128. package/src/core/migrations/diff-database-schema.ts +191 -0
  129. package/src/core/migrations/issue-planner.ts +44 -3
  130. package/src/core/migrations/op-factory-call.ts +109 -21
  131. package/src/core/migrations/operations/columns.ts +24 -12
  132. package/src/core/migrations/operations/data-transform.ts +6 -0
  133. package/src/core/migrations/operations/rls.ts +106 -0
  134. package/src/core/migrations/operations/shared.ts +0 -30
  135. package/src/core/migrations/planner-ddl-builders.ts +15 -56
  136. package/src/core/migrations/planner-recipes.ts +31 -13
  137. package/src/core/migrations/planner-strategies.ts +3 -11
  138. package/src/core/migrations/planner-target-details.ts +3 -1
  139. package/src/core/migrations/planner.ts +165 -33
  140. package/src/core/migrations/postgres-migration.ts +34 -4
  141. package/src/core/migrations/render-typescript.ts +42 -15
  142. package/src/core/migrations/resolve-ddl-schema.ts +24 -0
  143. package/src/core/migrations/runner.ts +13 -16
  144. package/src/core/migrations/verify-postgres-namespaces.ts +24 -19
  145. package/src/core/postgres-contract-serializer.ts +50 -33
  146. package/src/core/postgres-contract-view.ts +48 -0
  147. package/src/core/postgres-rls-policy.ts +64 -0
  148. package/src/core/postgres-role.ts +37 -0
  149. package/src/core/postgres-schema.ts +37 -13
  150. package/src/core/postgres-validators.ts +20 -0
  151. package/src/core/psl-infer/infer-psl-contract.ts +834 -0
  152. package/src/core/psl-infer/postgres-default-mapping.ts +16 -0
  153. package/src/core/psl-infer/postgres-type-map.ts +157 -0
  154. package/src/core/rls/canonicalize.ts +48 -0
  155. package/src/core/schema-ir/postgres-database-schema-node.ts +63 -0
  156. package/src/core/schema-ir/postgres-namespace-schema-node.ts +61 -0
  157. package/src/core/schema-ir/postgres-policy-schema-node.ts +93 -0
  158. package/src/core/schema-ir/postgres-role-schema-node.ts +66 -0
  159. package/src/core/schema-ir/postgres-table-schema-node.ts +107 -0
  160. package/src/core/schema-ir/schema-node-kinds.ts +28 -0
  161. package/src/exports/contract-free.ts +7 -1
  162. package/src/exports/control.ts +39 -12
  163. package/src/exports/ddl.ts +4 -0
  164. package/src/exports/migration.ts +1 -0
  165. package/src/exports/planner-ddl-builders.ts +0 -2
  166. package/src/exports/planner.ts +2 -0
  167. package/src/exports/rls-canonicalize.ts +6 -0
  168. package/src/exports/runtime.ts +1 -0
  169. package/src/exports/schema-ir-annotations.ts +1 -0
  170. package/src/exports/types.ts +28 -0
  171. package/dist/codecs--0A5_4Bq.d.mts.map +0 -1
  172. package/dist/ddl-QDyOSeLc.mjs.map +0 -1
  173. package/dist/default-normalizer-DyyCHQWs.mjs.map +0 -1
  174. package/dist/descriptor-meta-CpGygXpI.mjs +0 -140
  175. package/dist/descriptor-meta-CpGygXpI.mjs.map +0 -1
  176. package/dist/issue-planner-DL6g3CmE.mjs.map +0 -1
  177. package/dist/nodes-Bbhs2rwj.mjs.map +0 -1
  178. package/dist/nodes-pLeLgdis.d.mts +0 -67
  179. package/dist/nodes-pLeLgdis.d.mts.map +0 -1
  180. package/dist/op-factory-call-D_p5vxwt.mjs.map +0 -1
  181. package/dist/op-factory-call-DmQEc3XV.d.mts.map +0 -1
  182. package/dist/planner-Bs_baQax.mjs +0 -324
  183. package/dist/planner-Bs_baQax.mjs.map +0 -1
  184. package/dist/planner-ddl-builders-B2wOwLqI.mjs.map +0 -1
  185. package/dist/planner-target-details-CIY6tLeo.d.mts.map +0 -1
  186. package/dist/postgres-contract-serializer-k3TAcPMY.mjs.map +0 -1
  187. package/dist/postgres-migration-B5jKrXv3.mjs.map +0 -1
  188. package/dist/postgres-migration-Y4YBJqkS.d.mts.map +0 -1
  189. package/dist/postgres-schema-COGZ1ark.mjs.map +0 -1
  190. package/dist/render-typescript-KMgosran.mjs.map +0 -1
  191. package/dist/types.d.mts.map +0 -1
@@ -0,0 +1,125 @@
1
+ import type { ContractToSchemaIROptions } from '@prisma-next/family-sql/control';
2
+ import { contractNamespaceToSchemaIR } from '@prisma-next/family-sql/control';
3
+ import { ifDefined } from '@prisma-next/utils/defined';
4
+ import type { PostgresRlsPolicy } from '../postgres-rls-policy';
5
+ import type { PostgresContract } from '../postgres-schema';
6
+ import { isPostgresSchema } from '../postgres-schema';
7
+ import { PostgresDatabaseSchemaNode } from '../schema-ir/postgres-database-schema-node';
8
+ import { PostgresNamespaceSchemaNode } from '../schema-ir/postgres-namespace-schema-node';
9
+ import { PostgresPolicySchemaNode } from '../schema-ir/postgres-policy-schema-node';
10
+ import { PostgresRoleSchemaNode } from '../schema-ir/postgres-role-schema-node';
11
+ import { PostgresTableSchemaNode } from '../schema-ir/postgres-table-schema-node';
12
+ import { resolveDdlSchemaForNamespaceStorage } from './resolve-ddl-schema';
13
+
14
+ function toPolicyNode(policy: PostgresRlsPolicy, namespaceId: string): PostgresPolicySchemaNode {
15
+ return new PostgresPolicySchemaNode({
16
+ name: policy.name,
17
+ prefix: policy.prefix,
18
+ tableName: policy.tableName,
19
+ namespaceId,
20
+ operation: policy.operation,
21
+ roles: [...policy.roles],
22
+ ...ifDefined('using', policy.using),
23
+ ...ifDefined('withCheck', policy.withCheck),
24
+ permissive: policy.permissive,
25
+ });
26
+ }
27
+
28
+ /**
29
+ * Projects a Postgres contract into the expected schema-diff tree: a
30
+ * `PostgresDatabaseSchemaNode` root holding one `PostgresNamespaceSchemaNode`
31
+ * per Postgres namespace, each holding its `PostgresTableSchemaNode`s with
32
+ * their `PostgresPolicySchemaNode`s, plus the database roles on the root.
33
+ *
34
+ * Not a duplicate of the family's `contractToSchemaIR`: that builds a flat,
35
+ * single `{ tables }` map (and throws on cross-namespace name collisions, with
36
+ * no RLS/role concept) for SQLite's single-schema world. This is the
37
+ * Postgres-specific *tree* shape — multi-schema, RLS-policy-aware, role-aware.
38
+ * It reuses the family's per-namespace table conversion (`contractNamespaceToSchemaIR`)
39
+ * for column/FK/index building and only adds the Postgres tree/policy/role shape.
40
+ *
41
+ * Tables are grouped by their owning namespace (resolved DDL schema name) so
42
+ * the tree mirrors Postgres's object hierarchy. The DDL schema name is
43
+ * resolved once per namespace.
44
+ *
45
+ * A policy that references a table absent from its namespace is a malformed
46
+ * contract — the loop throws rather than fabricating a stub table.
47
+ */
48
+ export function contractToPostgresDatabaseSchemaNode(
49
+ contract: PostgresContract | null,
50
+ options: ContractToSchemaIROptions,
51
+ ): PostgresDatabaseSchemaNode {
52
+ if (contract === null) {
53
+ return new PostgresDatabaseSchemaNode({
54
+ namespaces: {},
55
+ roles: [],
56
+ existingSchemas: [],
57
+ pgVersion: '',
58
+ });
59
+ }
60
+
61
+ const namespaces: Record<string, PostgresNamespaceSchemaNode> = {};
62
+ const roles: PostgresRoleSchemaNode[] = [];
63
+ const ownedSchemas: string[] = [];
64
+
65
+ for (const ns of Object.values(contract.storage.namespaces)) {
66
+ if (!isPostgresSchema(ns)) continue;
67
+ const ddlSchema = resolveDdlSchemaForNamespaceStorage(contract.storage, ns.id);
68
+ ownedSchemas.push(ddlSchema);
69
+
70
+ // Convert only THIS namespace's tables (passing the full storage for
71
+ // type/value-set/enum resolution that spans namespaces), so the same table
72
+ // name can exist in two schemas without colliding in a bare-keyed record.
73
+ const sqlTables = contractNamespaceToSchemaIR(contract.storage, ns.id, options).tables;
74
+
75
+ const policiesByTable = new Map<string, PostgresPolicySchemaNode[]>();
76
+ for (const policy of Object.values(ns.policy)) {
77
+ const list = policiesByTable.get(policy.tableName) ?? [];
78
+ list.push(toPolicyNode(policy, ddlSchema));
79
+ policiesByTable.set(policy.tableName, list);
80
+ }
81
+
82
+ const tables: Record<string, PostgresTableSchemaNode> = {};
83
+ for (const tableName of Object.keys(ns.table)) {
84
+ const sqlTable = sqlTables[tableName];
85
+ if (sqlTable === undefined) continue;
86
+ tables[tableName] = new PostgresTableSchemaNode({
87
+ name: sqlTable.name,
88
+ columns: sqlTable.columns,
89
+ foreignKeys: sqlTable.foreignKeys,
90
+ uniques: sqlTable.uniques,
91
+ indexes: sqlTable.indexes,
92
+ ...ifDefined('primaryKey', sqlTable.primaryKey),
93
+ ...ifDefined('annotations', sqlTable.annotations),
94
+ ...ifDefined('checks', sqlTable.checks),
95
+ policies: policiesByTable.get(tableName) ?? [],
96
+ });
97
+ }
98
+
99
+ for (const [tableName, tablePolicies] of policiesByTable) {
100
+ if (!(tableName in tables)) {
101
+ const policyName = tablePolicies[0]?.name ?? '(unknown)';
102
+ throw new Error(
103
+ `contract-to-postgres-database-schema-node: policy "${policyName}" references table "${tableName}" not present in namespace "${ddlSchema}"`,
104
+ );
105
+ }
106
+ }
107
+
108
+ namespaces[ddlSchema] = new PostgresNamespaceSchemaNode({
109
+ schemaName: ddlSchema,
110
+ tables,
111
+ nativeEnumTypeNames: [],
112
+ });
113
+
114
+ for (const role of Object.values(ns.role)) {
115
+ roles.push(new PostgresRoleSchemaNode({ name: role.name, namespaceId: role.namespaceId }));
116
+ }
117
+ }
118
+
119
+ return new PostgresDatabaseSchemaNode({
120
+ namespaces,
121
+ roles,
122
+ existingSchemas: ownedSchemas,
123
+ pgVersion: '',
124
+ });
125
+ }
@@ -21,6 +21,8 @@ import type { PostgresOpFactoryCall } from './op-factory-call';
21
21
  const OBJECT_CREATION_FACTORIES: ReadonlySet<string> = new Set<string>([
22
22
  'createTable',
23
23
  'createSchema',
24
+ 'createRlsPolicy',
25
+ 'enableRowLevelSecurity',
24
26
  ]);
25
27
 
26
28
  function createsNewTopLevelObject(call: PostgresOpFactoryCall): boolean {
@@ -151,6 +153,7 @@ export function resolvePostgresCallControlPolicySubject(
151
153
  const POSTGRES_ISSUE_CREATION_FACTORY: Readonly<Record<string, string>> = Object.freeze({
152
154
  missing_schema: 'createSchema',
153
155
  missing_table: 'createTable',
156
+ missing_rls_policy: 'createRlsPolicy',
154
157
  });
155
158
 
156
159
  export function resolvePostgresIssueCreationFactoryName(issue: SchemaIssue): string | undefined {
@@ -0,0 +1,191 @@
1
+ import type { Contract } from '@prisma-next/contract/types';
2
+ import { verifySqlSchemaTree } from '@prisma-next/family-sql/diff';
3
+ import type { TargetBoundComponentDescriptor } from '@prisma-next/framework-components/components';
4
+ import type {
5
+ SchemaDiffIssue,
6
+ VerifyDatabaseSchemaResult,
7
+ } from '@prisma-next/framework-components/control';
8
+ import { diffSchemas, SchemaDiff } from '@prisma-next/framework-components/control';
9
+ import type { SqlStorage } from '@prisma-next/sql-contract/types';
10
+ import type { SqlSchemaIRNode } from '@prisma-next/sql-schema-ir/types';
11
+ import { blindCast } from '@prisma-next/utils/casts';
12
+ import { parsePostgresDefault } from '../default-normalizer';
13
+ import { normalizeSchemaNativeType } from '../native-type-normalizer';
14
+ import type { PostgresContract } from '../postgres-schema';
15
+ import { PostgresDatabaseSchemaNode } from '../schema-ir/postgres-database-schema-node';
16
+ import { PostgresPolicySchemaNode } from '../schema-ir/postgres-policy-schema-node';
17
+ import type { SqlSchemaDiffNode } from '../schema-ir/schema-node-kinds';
18
+ import { contractToPostgresDatabaseSchemaNode } from './contract-to-postgres-database-schema-node';
19
+
20
+ interface PostgresDiffDatabaseSchemaInput {
21
+ readonly contract: Contract<SqlStorage>;
22
+ readonly actualSchema: SqlSchemaIRNode;
23
+ readonly strict: boolean;
24
+ readonly typeMetadataRegistry: ReadonlyMap<string, { readonly nativeType?: string }>;
25
+ readonly frameworkComponents: ReadonlyArray<TargetBoundComponentDescriptor<'sql', string>>;
26
+ }
27
+
28
+ /**
29
+ * The single combined database-schema comparison — the one computation the
30
+ * migration planner and the family schema verify both consume. Composes,
31
+ * once each:
32
+ *
33
+ * - the per-namespace-paired relational diff (`verifySqlSchemaTree`) → table /
34
+ * column / constraint findings as framework `SchemaIssue`s (with the
35
+ * verification-tree `root` and pass/warn/fail counts);
36
+ * - the policy diff (`diffPostgresSchema` over the two trees) → RLS policy
37
+ * presence as `SchemaDiffIssue`s, ownership-filtered to the contract's owned
38
+ * schemas.
39
+ *
40
+ * `diffPostgresDatabaseSchema` and `verifyPostgresDatabaseSchema` both read
41
+ * this single result, so the relational walk runs once per caller — never
42
+ * once for the diff and again for the verify tree.
43
+ */
44
+ function computePostgresSchemaComparison(input: PostgresDiffDatabaseSchemaInput): {
45
+ readonly relational: VerifyDatabaseSchemaResult;
46
+ readonly schemaDiffIssues: readonly SchemaDiffIssue<SqlSchemaDiffNode>[];
47
+ } {
48
+ const postgresContract = blindCast<
49
+ PostgresContract,
50
+ 'diffPostgresDatabaseSchema is only called with a postgres contract'
51
+ >(input.contract);
52
+
53
+ // Relational diff: per-namespace-paired so a multi-schema database checks each
54
+ // contract namespace against its own actual node.
55
+ const relational = verifySqlSchemaTree({
56
+ contract: input.contract,
57
+ actualSchema: input.actualSchema,
58
+ buildExpectedSchema: (scopedContract) =>
59
+ contractToPostgresDatabaseSchemaNode(
60
+ blindCast<
61
+ PostgresContract | null,
62
+ 'the relational pairing projects a scoped postgres contract'
63
+ >(scopedContract),
64
+ { annotationNamespace: 'pg' },
65
+ ),
66
+ strict: input.strict,
67
+ typeMetadataRegistry: input.typeMetadataRegistry,
68
+ frameworkComponents: input.frameworkComponents,
69
+ normalizeDefault: parsePostgresDefault,
70
+ normalizeNativeType: normalizeSchemaNativeType,
71
+ });
72
+
73
+ // Policy diff: the generic node differ over the expected/actual policy trees,
74
+ // ownership-filtered to the schemas the contract owns (so unowned-namespace
75
+ // policies are not reported as extras). The actual schema is always the
76
+ // Postgres database root in production — assert it, matching the prior
77
+ // `collectSchemaDiffIssues` / `planPostgresSchemaDiff` behaviour.
78
+ PostgresDatabaseSchemaNode.assert(input.actualSchema);
79
+ const expected = contractToPostgresDatabaseSchemaNode(postgresContract, {
80
+ annotationNamespace: 'pg',
81
+ });
82
+ const schemaDiffIssues = filterIssuesByOwnership(
83
+ diffPostgresSchema(expected, input.actualSchema),
84
+ ownedSchemaNames(expected),
85
+ );
86
+
87
+ return { relational, schemaDiffIssues };
88
+ }
89
+
90
+ /**
91
+ * The `SchemaDiffer` for Postgres: the target's black-box comparison,
92
+ * projected to the two issue lists. Namespace presence (`missing_schema` →
93
+ * `CREATE SCHEMA`) is intentionally NOT composed here: it is a planner-only
94
+ * op-generation concern (verify rejects on the relational `missing_table` a
95
+ * missing schema already produces), so the planner stitches it in around this
96
+ * diff. Control-policy suppression of the policy issues is likewise a
97
+ * per-consumer post-step (verify filters the issues; the planner filters the
98
+ * calls).
99
+ */
100
+ export function diffPostgresDatabaseSchema(
101
+ input: PostgresDiffDatabaseSchemaInput,
102
+ ): SchemaDiff<SqlSchemaDiffNode> {
103
+ const { relational, schemaDiffIssues } = computePostgresSchemaComparison(input);
104
+ return new SchemaDiff(relational.schema.issues, schemaDiffIssues);
105
+ }
106
+
107
+ /**
108
+ * The same combined comparison as {@link diffPostgresDatabaseSchema}, wrapped
109
+ * in the verify envelope (`ok`/`summary`/`code`/`target`/`timings`) plus the
110
+ * pass/warn/fail tree the CLI renders — i.e. exactly the existing verify-result
111
+ * schema shape, so nothing downstream changes.
112
+ */
113
+ export function verifyPostgresDatabaseSchema(
114
+ input: PostgresDiffDatabaseSchemaInput,
115
+ ): VerifyDatabaseSchemaResult {
116
+ const { relational, schemaDiffIssues } = computePostgresSchemaComparison(input);
117
+ return {
118
+ ...relational,
119
+ schema: { ...relational.schema, schemaDiffIssues },
120
+ };
121
+ }
122
+
123
+ function ownedSchemaNames(expected: PostgresDatabaseSchemaNode): ReadonlySet<string> {
124
+ const policyNamespaces = Object.values(expected.namespaces).flatMap((ns) =>
125
+ Object.values(ns.tables).flatMap((t) => t.policies.map((p) => p.namespaceId)),
126
+ );
127
+ return new Set([...policyNamespaces, ...expected.existingSchemas]);
128
+ }
129
+
130
+ // Renders a display-only reference string for the diff message. If policy
131
+ // rendering grows, route it through the adapter's SQL renderer so the message
132
+ // can't diverge from the emitted policy SQL.
133
+ function renderPostgresPolicyReference(policy: PostgresPolicySchemaNode): string {
134
+ return `policy "${policy.name}" on "${policy.namespaceId}"."${policy.tableName}"`;
135
+ }
136
+
137
+ /**
138
+ * The policy node-diff — the structural half of the combined comparison above.
139
+ * Computes RLS-policy drift between two derived schema trees:
140
+ *
141
+ * 1. Runs the framework total diff over the two `PostgresDatabaseSchemaNode`
142
+ * roots (database → namespace → table → policy).
143
+ * 2. Filters to policy-subject issues only — this is transitional: the generic
144
+ * differ walks the whole tree, but the legacy relational verifier still owns
145
+ * table/column drift, so non-policy issues are dropped here.
146
+ * 3. Remaps the message to a human-readable policy reference.
147
+ *
148
+ * Both trees are `PostgresDatabaseSchemaNode`s, so every issue node is a
149
+ * `SqlSchemaDiffNode` — narrow the framework's `SchemaDiffIssue<DiffableNode>`
150
+ * output once here (the single boundary cast), so every downstream consumer
151
+ * (the ownership filter, the planner) reads the concrete node with no cast.
152
+ *
153
+ * Ownership filtering (dropping `extra` issues in namespaces a contract doesn't
154
+ * own) is the caller's responsibility — use `filterIssuesByOwnership`.
155
+ */
156
+ export function diffPostgresSchema(
157
+ expected: PostgresDatabaseSchemaNode,
158
+ actual: PostgresDatabaseSchemaNode,
159
+ ): readonly SchemaDiffIssue<SqlSchemaDiffNode>[] {
160
+ const issues = blindCast<
161
+ readonly SchemaDiffIssue<SqlSchemaDiffNode>[],
162
+ 'both trees are PostgresDatabaseSchemaNodes, so every diff-issue node is a SqlSchemaDiffNode'
163
+ >(diffSchemas(expected, actual));
164
+
165
+ return issues
166
+ .filter((i) => {
167
+ const node = i.expected ?? i.actual;
168
+ return node !== undefined && PostgresPolicySchemaNode.is(node);
169
+ })
170
+ .map((i) => {
171
+ const node = i.expected ?? i.actual;
172
+ if (node === undefined || !PostgresPolicySchemaNode.is(node)) return i;
173
+ return { ...i, message: `${i.outcome}: ${renderPostgresPolicyReference(node)}` };
174
+ });
175
+ }
176
+
177
+ /**
178
+ * Filters `extra` policy issues to those in owned namespaces. Call after
179
+ * `diffPostgresSchema` with the union of namespace ids from the expected tree's
180
+ * policies and its `existingSchemas`.
181
+ */
182
+ export function filterIssuesByOwnership(
183
+ issues: readonly SchemaDiffIssue<SqlSchemaDiffNode>[],
184
+ ownedSchemaNameSet: ReadonlySet<string>,
185
+ ): readonly SchemaDiffIssue<SqlSchemaDiffNode>[] {
186
+ return issues.filter((i) => {
187
+ if (i.outcome !== 'extra') return true;
188
+ if (i.actual === undefined) return false;
189
+ return PostgresPolicySchemaNode.is(i.actual) && ownedSchemaNameSet.has(i.actual.namespaceId);
190
+ });
191
+ }
@@ -15,7 +15,7 @@ import type {
15
15
  SqlPlannerConflict,
16
16
  SqlPlannerConflictLocation,
17
17
  } from '@prisma-next/family-sql/control';
18
- import { arraysEqual } from '@prisma-next/family-sql/schema-verify';
18
+ import { arraysEqual } from '@prisma-next/family-sql/diff';
19
19
  import type { TargetBoundComponentDescriptor } from '@prisma-next/framework-components/components';
20
20
  import type { SchemaIssue } from '@prisma-next/framework-components/control';
21
21
  import type {
@@ -31,6 +31,7 @@ import { blindCast } from '@prisma-next/utils/casts';
31
31
  import { ifDefined } from '@prisma-next/utils/defined';
32
32
  import type { Result } from '@prisma-next/utils/result';
33
33
  import { notOk, ok } from '@prisma-next/utils/result';
34
+ import { quoteIdentifier } from '../sql-utils';
34
35
  import {
35
36
  AddColumnCall,
36
37
  AddForeignKeyCall,
@@ -67,6 +68,26 @@ import { resolveColumnTypeMetadata } from './planner-type-resolution';
67
68
 
68
69
  export type { CallMigrationStrategy, StrategyContext };
69
70
 
71
+ /**
72
+ * Deterministic name for the element-non-null CHECK constraint on a scalar-array
73
+ * column. Distinct `_elem_not_null` suffix avoids collision with the enum
74
+ * value-set `_check` constraints. Re-emitting the same schema produces the same
75
+ * name, so `pg_get_constraintdef`-based verify sees no drift.
76
+ */
77
+ function elementNonNullCheckName(tableName: string, columnName: string): string {
78
+ return `${tableName}_${columnName}_elem_not_null`;
79
+ }
80
+
81
+ /**
82
+ * Predicate enforcing that a scalar-array column carries no NULL element. The
83
+ * array column itself may be NULL (container nullability is the column's NOT NULL
84
+ * clause); `array_position` over a NULL array yields NULL, which a CHECK treats
85
+ * as satisfied, so a nullable array column is unaffected.
86
+ */
87
+ function elementNonNullCheckExpression(columnName: string): string {
88
+ return `array_position(${quoteIdentifier(columnName)}, NULL) IS NULL`;
89
+ }
90
+
70
91
  // ============================================================================
71
92
  // Issue kind ordering (dependency order)
72
93
  // ============================================================================
@@ -254,16 +275,28 @@ function mapIssueToCall(
254
275
  );
255
276
  }
256
277
  const schemaForTable = tableSchema(issue);
278
+ const missingTableName = issue.table;
257
279
  const ddlColumns: DdlColumn[] = Object.entries(contractTable.columns).map(([name, column]) =>
258
280
  toDdlColumn(name, column, codecHooks, storageTypes),
259
281
  );
260
- const ddlConstraints: DdlTableConstraint[] | undefined = contractTable.primaryKey
282
+ const primaryKeyConstraints: DdlTableConstraint[] = contractTable.primaryKey
261
283
  ? [
262
284
  contractFree.primaryKey(contractTable.primaryKey.columns, {
263
285
  ...(contractTable.primaryKey.name ? { name: contractTable.primaryKey.name } : {}),
264
286
  }),
265
287
  ]
266
- : undefined;
288
+ : [];
289
+ const elementNonNullChecks: DdlTableConstraint[] = Object.entries(contractTable.columns)
290
+ .filter(([, column]) => column.many === true)
291
+ .map(([columnName]) =>
292
+ contractFree.checkExpression(
293
+ elementNonNullCheckName(missingTableName, columnName),
294
+ elementNonNullCheckExpression(columnName),
295
+ ),
296
+ );
297
+ const allTableConstraints = [...primaryKeyConstraints, ...elementNonNullChecks];
298
+ const ddlConstraints: DdlTableConstraint[] | undefined =
299
+ allTableConstraints.length > 0 ? allTableConstraints : undefined;
267
300
  const calls: PostgresOpFactoryCall[] = [
268
301
  new CreateTableCall(schemaForTable, issue.table, ddlColumns, ddlConstraints),
269
302
  ];
@@ -695,6 +728,8 @@ type CallCategory =
695
728
  | 'dep'
696
729
  | 'drop'
697
730
  | 'table'
731
+ | 'rlsEnable'
732
+ | 'rlsPolicy'
698
733
  | 'column'
699
734
  | 'alter'
700
735
  | 'primaryKey'
@@ -724,6 +759,12 @@ function classifyCall(call: PostgresOpFactoryCall): CallCategory {
724
759
  return 'unique'; // after uniques, before indexes
725
760
  case 'createTable':
726
761
  return 'table';
762
+ case 'enableRowLevelSecurity':
763
+ return 'rlsEnable';
764
+ case 'createRlsPolicy':
765
+ return 'rlsPolicy';
766
+ case 'dropRlsPolicy':
767
+ return 'drop';
727
768
  case 'addColumn':
728
769
  return 'column';
729
770
  case 'alterColumnType':
@@ -40,8 +40,10 @@ import { blindCast } from '@prisma-next/utils/casts';
40
40
  import { ifDefined } from '@prisma-next/utils/defined';
41
41
  import { columnExistsAst, tableExistsAst } from '../../contract-free/checks';
42
42
  import * as contractFreeDdl from '../../contract-free/ddl';
43
+ import type { PostgresRlsPolicy } from '../postgres-rls-policy';
43
44
  import { escapeLiteral, quoteIdentifier } from '../sql-utils';
44
45
  import type { PostgresColumnDefault } from '../types';
46
+ import { boundSchema } from './bound-schema';
45
47
  import {
46
48
  addNotNullColumnDirect,
47
49
  alterColumnType,
@@ -61,6 +63,7 @@ import {
61
63
  } from './operations/constraints';
62
64
  import { createExtension } from './operations/dependencies';
63
65
  import { createIndex, dropIndex } from './operations/indexes';
66
+ import { createRlsPolicy, dropRlsPolicy, enableRowLevelSecurity } from './operations/rls';
64
67
  import type { ForeignKeySpec } from './operations/shared';
65
68
  import { step, targetDetails } from './operations/shared';
66
69
  import { dropTable } from './operations/tables';
@@ -83,10 +86,6 @@ type Op = SqlMigrationPlanOperation<PostgresPlanTargetDetails>;
83
86
  // transitive package on disk.
84
87
  const POSTGRES_MIGRATION_FACADE = '@prisma-next/postgres/migration';
85
88
 
86
- function boundSchema(schemaName: string): string | undefined {
87
- return schemaName === UNBOUND_NAMESPACE_ID ? undefined : schemaName;
88
- }
89
-
90
89
  abstract class PostgresOpFactoryCallNode extends TsExpression implements FrameworkOpFactoryCall {
91
90
  abstract readonly factoryName: string;
92
91
  abstract readonly operationClass: MigrationOperationClass;
@@ -169,6 +168,8 @@ function renderDdlConstraintAsTsCall(constraint: DdlTableConstraint): string {
169
168
  const nameOpt = constraint.name ? `, { name: ${jsonToTsSource(constraint.name)} }` : '';
170
169
  return `unique(${jsonToTsSource(constraint.columns)}${nameOpt})`;
171
170
  }
171
+ case 'check-expression':
172
+ return `checkExpression(${jsonToTsSource(constraint.name)}, ${jsonToTsSource(constraint.expression)})`;
172
173
  }
173
174
  }
174
175
 
@@ -183,6 +184,7 @@ function constraintImportSymbols(constraints: readonly DdlTableConstraint[] | un
183
184
  if (c.kind === 'primary-key') symbols.add('primaryKey');
184
185
  else if (c.kind === 'foreign-key') symbols.add('foreignKey');
185
186
  else if (c.kind === 'unique') symbols.add('unique');
187
+ else if (c.kind === 'check-expression') symbols.add('checkExpression');
186
188
  }
187
189
  return [...symbols];
188
190
  }
@@ -684,10 +686,8 @@ export class DropDefaultCall extends PostgresOpFactoryCallNode {
684
686
 
685
687
  /**
686
688
  * Planner-internal call for adding a NOT NULL column (no contract default) to
687
- * a table that must be empty at migration time. Carries the raw ADD COLUMN SQL
688
- * (produced by `buildAddColumnSql` at plan time deferred from slice-7 typed
689
- * DDL) plus the parameters needed to lower the typed pre/postchecks at render
690
- * time.
689
+ * a table that must be empty at migration time. Carries the typed `DdlColumn`
690
+ * and lowers it to an ADD COLUMN execute step via the adapter at `toOp` time.
691
691
  *
692
692
  * No authored `PostgresMigration` method: this call is only emitted by the
693
693
  * planner, never hand-written by migration authors.
@@ -698,15 +698,15 @@ export class AddNotNullColumnDirectCall extends PostgresOpFactoryCallNode {
698
698
  readonly schemaName: string;
699
699
  readonly tableName: string;
700
700
  readonly columnName: string;
701
- readonly executeStepSql: string;
701
+ readonly column: DdlColumn;
702
702
  readonly label: string;
703
703
 
704
- constructor(schemaName: string, tableName: string, columnName: string, executeStepSql: string) {
704
+ constructor(schemaName: string, tableName: string, columnName: string, column: DdlColumn) {
705
705
  super();
706
706
  this.schemaName = schemaName;
707
707
  this.tableName = tableName;
708
708
  this.columnName = columnName;
709
- this.executeStepSql = executeStepSql;
709
+ this.column = column;
710
710
  this.label = `Add column ${columnName} to ${tableName}`;
711
711
  this.freeze();
712
712
  }
@@ -717,13 +717,7 @@ export class AddNotNullColumnDirectCall extends PostgresOpFactoryCallNode {
717
717
  `AddNotNullColumnDirectCall.toOp: a lowerer is required on the Postgres planner path (column "${this.columnName}" on table "${this.tableName}"). Pass the control adapter to createPostgresMigrationPlanner.`,
718
718
  );
719
719
  }
720
- return addNotNullColumnDirect(
721
- this.schemaName,
722
- this.tableName,
723
- this.columnName,
724
- this.executeStepSql,
725
- lowerer,
726
- );
720
+ return addNotNullColumnDirect(this.schemaName, this.tableName, this.column, lowerer);
727
721
  }
728
722
 
729
723
  renderTypeScript(): string {
@@ -738,9 +732,9 @@ export class AddNotNullColumnDirectCall extends PostgresOpFactoryCallNode {
738
732
  /**
739
733
  * Planner-internal call for adding a NOT NULL column (no contract default)
740
734
  * using a temporary default value for non-empty tables. Carries all parameters
741
- * needed for `buildAddNotNullColumnWithTemporaryDefaultOperation`; the typed
742
- * pre/postchecks are lowered at render time via `toOp(lowerer)`. The execute
743
- * SQL (`buildAddColumnSql`) is still raw per slice-7 deferral.
735
+ * needed for `buildAddNotNullColumnWithTemporaryDefaultOperation`; both the
736
+ * typed ADD COLUMN execute step and the pre/postchecks are lowered via the
737
+ * adapter at `toOp` time.
744
738
  *
745
739
  * No authored `PostgresMigration` method: this call is only emitted by the
746
740
  * planner, never hand-written by migration authors.
@@ -1376,6 +1370,97 @@ export class DataTransformCall extends PostgresOpFactoryCallNode {
1376
1370
  }
1377
1371
  }
1378
1372
 
1373
+ export class CreatePostgresRlsPolicyCall extends PostgresOpFactoryCallNode {
1374
+ readonly factoryName = 'createRlsPolicy' as const;
1375
+ readonly operationClass = 'additive' as const;
1376
+ readonly schemaName: string;
1377
+ readonly tableName: string;
1378
+ readonly policy: PostgresRlsPolicy;
1379
+ readonly label: string;
1380
+
1381
+ constructor(schemaName: string, tableName: string, policy: PostgresRlsPolicy) {
1382
+ super();
1383
+ this.schemaName = schemaName;
1384
+ this.tableName = tableName;
1385
+ this.policy = policy;
1386
+ this.label = `Create RLS policy "${policy.name}" on "${tableName}"`;
1387
+ this.freeze();
1388
+ }
1389
+
1390
+ async toOp(lowerer?: ExecuteRequestLowerer): Promise<Op> {
1391
+ if (lowerer === undefined) {
1392
+ throw new Error(
1393
+ `CreatePostgresRlsPolicyCall.toOp: a lowerer is required on the Postgres planner path (policy "${this.policy.name}" on table "${this.tableName}"). Pass the control adapter to createPostgresMigrationPlanner.`,
1394
+ );
1395
+ }
1396
+ return createRlsPolicy(this.schemaName, this.tableName, this.policy, lowerer);
1397
+ }
1398
+
1399
+ renderTypeScript(): string {
1400
+ return `createRlsPolicy(${jsonToTsSource(this.schemaName)}, ${jsonToTsSource(this.tableName)}, ${jsonToTsSource(this.policy)})`;
1401
+ }
1402
+ }
1403
+
1404
+ export class DropPostgresRlsPolicyCall extends PostgresOpFactoryCallNode {
1405
+ readonly factoryName = 'dropRlsPolicy' as const;
1406
+ readonly operationClass = 'destructive' as const;
1407
+ readonly schemaName: string;
1408
+ readonly tableName: string;
1409
+ readonly policyName: string;
1410
+ readonly label: string;
1411
+
1412
+ constructor(schemaName: string, tableName: string, policyName: string) {
1413
+ super();
1414
+ this.schemaName = schemaName;
1415
+ this.tableName = tableName;
1416
+ this.policyName = policyName;
1417
+ this.label = `Drop RLS policy "${policyName}" on "${tableName}"`;
1418
+ this.freeze();
1419
+ }
1420
+
1421
+ async toOp(lowerer?: ExecuteRequestLowerer): Promise<Op> {
1422
+ if (lowerer === undefined) {
1423
+ throw new Error(
1424
+ `DropPostgresRlsPolicyCall.toOp: a lowerer is required on the Postgres planner path (policy "${this.policyName}" on table "${this.tableName}"). Pass the control adapter to createPostgresMigrationPlanner.`,
1425
+ );
1426
+ }
1427
+ return dropRlsPolicy(this.schemaName, this.tableName, this.policyName, lowerer);
1428
+ }
1429
+
1430
+ renderTypeScript(): string {
1431
+ return `dropRlsPolicy(${jsonToTsSource(this.schemaName)}, ${jsonToTsSource(this.tableName)}, ${jsonToTsSource(this.policyName)})`;
1432
+ }
1433
+ }
1434
+
1435
+ export class EnableRowLevelSecurityCall extends PostgresOpFactoryCallNode {
1436
+ readonly factoryName = 'enableRowLevelSecurity' as const;
1437
+ readonly operationClass = 'additive' as const;
1438
+ readonly schemaName: string;
1439
+ readonly tableName: string;
1440
+ readonly label: string;
1441
+
1442
+ constructor(schemaName: string, tableName: string) {
1443
+ super();
1444
+ this.schemaName = schemaName;
1445
+ this.tableName = tableName;
1446
+ this.label = `Enable row-level security on "${tableName}"`;
1447
+ this.freeze();
1448
+ }
1449
+
1450
+ async toOp(lowerer?: ExecuteRequestLowerer): Promise<Op> {
1451
+ if (lowerer === undefined) {
1452
+ throw new Error(
1453
+ `EnableRowLevelSecurityCall.toOp: a lowerer is required on the Postgres planner path (table "${this.tableName}"). Pass the control adapter to createPostgresMigrationPlanner.`,
1454
+ );
1455
+ }
1456
+ return enableRowLevelSecurity(this.schemaName, this.tableName, lowerer);
1457
+ }
1458
+
1459
+ renderTypeScript(): string {
1460
+ return `enableRowLevelSecurity(${jsonToTsSource(this.schemaName)}, ${jsonToTsSource(this.tableName)})`;
1461
+ }
1462
+ }
1463
+
1379
1464
  export type PostgresOpFactoryCall =
1380
1465
  | CreateTableCall
1381
1466
  | DropTableCall
@@ -1399,4 +1484,7 @@ export type PostgresOpFactoryCall =
1399
1484
  | RawSqlCall
1400
1485
  | CreateExtensionCall
1401
1486
  | CreateSchemaCall
1487
+ | CreatePostgresRlsPolicyCall
1488
+ | DropPostgresRlsPolicyCall
1489
+ | EnableRowLevelSecurityCall
1402
1490
  | DataTransformCall;