@prisma-next/target-postgres 0.14.0-dev.61 → 0.14.0-dev.62

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 (117) hide show
  1. package/dist/{authoring-BLkL4fFG.mjs → authoring-CdIZ0yvD.mjs} +2 -2
  2. package/dist/{authoring-BLkL4fFG.mjs.map → authoring-CdIZ0yvD.mjs.map} +1 -1
  3. package/dist/contract-free.d.mts +1 -1
  4. package/dist/contract-free.d.mts.map +1 -1
  5. package/dist/contract-free.mjs +2 -2
  6. package/dist/{control-policy-BpmChlAK.mjs → control-policy-DKp0HKpY.mjs} +180 -45
  7. package/dist/control-policy-DKp0HKpY.mjs.map +1 -0
  8. package/dist/control.mjs +21 -25
  9. package/dist/control.mjs.map +1 -1
  10. package/dist/{ddl-BovUVFVi.mjs → ddl-gmxHf68z.mjs} +20 -4
  11. package/dist/{ddl-BovUVFVi.mjs.map → ddl-gmxHf68z.mjs.map} +1 -1
  12. package/dist/ddl.d.mts +2 -2
  13. package/dist/ddl.mjs +2 -2
  14. package/dist/{descriptor-meta-C1T4BXJ8.mjs → descriptor-meta-DEuXJfpR.mjs} +3 -3
  15. package/dist/{descriptor-meta-C1T4BXJ8.mjs.map → descriptor-meta-DEuXJfpR.mjs.map} +1 -1
  16. package/dist/{diff-database-schema-RMwnSOnz.mjs → diff-database-schema-D2sjvMw9.mjs} +14 -10
  17. package/dist/diff-database-schema-D2sjvMw9.mjs.map +1 -0
  18. package/dist/diff-database-schema.d.mts +1 -1
  19. package/dist/diff-database-schema.d.mts.map +1 -1
  20. package/dist/diff-database-schema.mjs +1 -1
  21. package/dist/issue-planner.d.mts +1 -1
  22. package/dist/issue-planner.d.mts.map +1 -1
  23. package/dist/issue-planner.mjs +1 -1
  24. package/dist/migration.d.mts +2 -2
  25. package/dist/migration.mjs +2 -2
  26. package/dist/{nodes-BtQyZha6.d.mts → nodes-Bc2N3tMy.d.mts} +27 -3
  27. package/dist/nodes-Bc2N3tMy.d.mts.map +1 -0
  28. package/dist/{nodes-B815pj_X.mjs → nodes-DJC5B8Id.mjs} +32 -2
  29. package/dist/nodes-DJC5B8Id.mjs.map +1 -0
  30. package/dist/{op-factory-call-CI00nS4z.mjs → op-factory-call-C4flTtTG.mjs} +105 -6
  31. package/dist/op-factory-call-C4flTtTG.mjs.map +1 -0
  32. package/dist/{op-factory-call-D6_7-7Vc.d.mts → op-factory-call-CSIg5XDu.d.mts} +26 -3
  33. package/dist/{op-factory-call-D6_7-7Vc.d.mts.map → op-factory-call-CSIg5XDu.d.mts.map} +1 -1
  34. package/dist/op-factory-call.d.mts +2 -2
  35. package/dist/op-factory-call.mjs +2 -2
  36. package/dist/pack.mjs +1 -1
  37. package/dist/{planner-CAHZN1Ez.mjs → planner-BtEd5LMr.mjs} +32 -64
  38. package/dist/planner-BtEd5LMr.mjs.map +1 -0
  39. package/dist/{planner-ddl-builders-B8Nn6nHN.mjs → planner-ddl-builders-BKlmGDJl.mjs} +7 -3
  40. package/dist/planner-ddl-builders-BKlmGDJl.mjs.map +1 -0
  41. package/dist/planner-ddl-builders.d.mts.map +1 -1
  42. package/dist/planner-ddl-builders.mjs +1 -1
  43. package/dist/{planner-produced-postgres-migration-a-tjfKsA.d.mts → planner-produced-postgres-migration-CXue-6M9.d.mts} +2 -2
  44. package/dist/{planner-produced-postgres-migration-a-tjfKsA.d.mts.map → planner-produced-postgres-migration-CXue-6M9.d.mts.map} +1 -1
  45. package/dist/{planner-produced-postgres-migration-flkG4Y7n.mjs → planner-produced-postgres-migration-Jat3hg4u.mjs} +2 -2
  46. package/dist/{planner-produced-postgres-migration-flkG4Y7n.mjs.map → planner-produced-postgres-migration-Jat3hg4u.mjs.map} +1 -1
  47. package/dist/planner-produced-postgres-migration.d.mts +1 -1
  48. package/dist/planner-produced-postgres-migration.mjs +1 -1
  49. package/dist/{planner-sql-checks-C1-Kk3Eu.mjs → planner-sql-checks-Br1c_QsH.mjs} +3 -3
  50. package/dist/{planner-sql-checks-C1-Kk3Eu.mjs.map → planner-sql-checks-Br1c_QsH.mjs.map} +1 -1
  51. package/dist/planner-sql-checks.mjs +1 -1
  52. package/dist/planner.d.mts +2 -2
  53. package/dist/planner.d.mts.map +1 -1
  54. package/dist/planner.mjs +2 -2
  55. package/dist/{postgres-contract-view-DWLNQbdR.mjs → postgres-contract-view-BAPW0ec1.mjs} +3 -3
  56. package/dist/{postgres-contract-view-DWLNQbdR.mjs.map → postgres-contract-view-BAPW0ec1.mjs.map} +1 -1
  57. package/dist/{postgres-database-schema-node-BVdOffjC.d.mts → postgres-database-schema-node-CJv5bfbs.d.mts} +62 -21
  58. package/dist/postgres-database-schema-node-CJv5bfbs.d.mts.map +1 -0
  59. package/dist/{postgres-migration-C92swCzd.mjs → postgres-migration-ChLMQRd-.mjs} +19 -3
  60. package/dist/postgres-migration-ChLMQRd-.mjs.map +1 -0
  61. package/dist/{postgres-migration-Bzzi0Asr.d.mts → postgres-migration-DkmgGNcv.d.mts} +21 -2
  62. package/dist/postgres-migration-DkmgGNcv.d.mts.map +1 -0
  63. package/dist/{postgres-role-schema-node-BmCboEa_.mjs → postgres-role-schema-node-Cm-j0ZGM.mjs} +11 -19
  64. package/dist/postgres-role-schema-node-Cm-j0ZGM.mjs.map +1 -0
  65. package/dist/{postgres-schema-COEca21H.mjs → postgres-schema-DWQ0-CPb.mjs} +13 -2
  66. package/dist/postgres-schema-DWQ0-CPb.mjs.map +1 -0
  67. package/dist/postgres-schema-DsrtYp2v.d.mts.map +1 -1
  68. package/dist/{postgres-table-schema-node-9FCtCUz2.mjs → postgres-table-schema-node-D6LvInCe.mjs} +71 -8
  69. package/dist/postgres-table-schema-node-D6LvInCe.mjs.map +1 -0
  70. package/dist/{resolve-ddl-schema-ZkKxt2Fm.mjs → resolve-ddl-schema-CAza65Iy.mjs} +2 -2
  71. package/dist/{resolve-ddl-schema-ZkKxt2Fm.mjs.map → resolve-ddl-schema-CAza65Iy.mjs.map} +1 -1
  72. package/dist/runtime.mjs +1 -1
  73. package/dist/schema-ir-annotations.mjs +1 -1
  74. package/dist/{sql-utils-DcfMz4MQ.mjs → sql-utils-SU4FDvIV.mjs} +12 -3
  75. package/dist/{sql-utils-DcfMz4MQ.mjs.map → sql-utils-SU4FDvIV.mjs.map} +1 -1
  76. package/dist/sql-utils.d.mts +8 -1
  77. package/dist/sql-utils.d.mts.map +1 -1
  78. package/dist/sql-utils.mjs +2 -2
  79. package/dist/types.d.mts +2 -2
  80. package/dist/types.mjs +4 -4
  81. package/package.json +20 -20
  82. package/src/contract-free/ddl.ts +27 -0
  83. package/src/core/ddl/nodes.ts +44 -0
  84. package/src/core/migrations/contract-to-postgres-database-schema-node.ts +12 -1
  85. package/src/core/migrations/control-policy.ts +143 -45
  86. package/src/core/migrations/diff-database-schema.ts +1 -2
  87. package/src/core/migrations/issue-planner.ts +77 -4
  88. package/src/core/migrations/op-factory-call.ts +84 -0
  89. package/src/core/migrations/operations/native-enum-types.ts +62 -0
  90. package/src/core/migrations/planner-ddl-builders.ts +12 -2
  91. package/src/core/migrations/planner.ts +25 -62
  92. package/src/core/migrations/postgres-migration.ts +31 -0
  93. package/src/core/migrations/verify-postgres-namespaces.ts +0 -1
  94. package/src/core/postgres-schema.ts +22 -1
  95. package/src/core/psl-infer/infer-psl-contract.ts +6 -16
  96. package/src/core/schema-ir/node-storage-coordinate.ts +26 -0
  97. package/src/core/schema-ir/postgres-namespace-schema-node.ts +18 -27
  98. package/src/core/schema-ir/postgres-native-enum-schema-node.ts +83 -0
  99. package/src/core/schema-ir/schema-node-kinds.ts +12 -3
  100. package/src/core/sql-utils.ts +15 -2
  101. package/src/exports/ddl.ts +2 -0
  102. package/src/exports/op-factory-call.ts +2 -0
  103. package/src/exports/sql-utils.ts +1 -0
  104. package/src/exports/types.ts +4 -1
  105. package/dist/control-policy-BpmChlAK.mjs.map +0 -1
  106. package/dist/diff-database-schema-RMwnSOnz.mjs.map +0 -1
  107. package/dist/nodes-B815pj_X.mjs.map +0 -1
  108. package/dist/nodes-BtQyZha6.d.mts.map +0 -1
  109. package/dist/op-factory-call-CI00nS4z.mjs.map +0 -1
  110. package/dist/planner-CAHZN1Ez.mjs.map +0 -1
  111. package/dist/planner-ddl-builders-B8Nn6nHN.mjs.map +0 -1
  112. package/dist/postgres-database-schema-node-BVdOffjC.d.mts.map +0 -1
  113. package/dist/postgres-migration-Bzzi0Asr.d.mts.map +0 -1
  114. package/dist/postgres-migration-C92swCzd.mjs.map +0 -1
  115. package/dist/postgres-role-schema-node-BmCboEa_.mjs.map +0 -1
  116. package/dist/postgres-schema-COEca21H.mjs.map +0 -1
  117. package/dist/postgres-table-schema-node-9FCtCUz2.mjs.map +0 -1
@@ -28,24 +28,19 @@ import { blindCast } from '@prisma-next/utils/casts';
28
28
  import { ifDefined } from '@prisma-next/utils/defined';
29
29
  import { PostgresRlsPolicy } from '../postgres-rls-policy';
30
30
  import { parseRlsPolicyWireName } from '../rls/wire-name';
31
+ import { postgresNodeStorageCoordinate } from '../schema-ir/node-storage-coordinate';
31
32
  import { PostgresDatabaseSchemaNode } from '../schema-ir/postgres-database-schema-node';
32
33
  import { PostgresPolicySchemaNode } from '../schema-ir/postgres-policy-schema-node';
33
- import { PostgresSchemaNodeKind, type SqlSchemaDiffNode } from '../schema-ir/schema-node-kinds';
34
+ import type { SqlSchemaDiffNode } from '../schema-ir/schema-node-kinds';
34
35
  import {
35
- formatPostgresControlPolicySubjectLabel,
36
+ renderPostgresSuppression,
36
37
  resolveNamespaceIdForDdlSchema,
37
38
  resolvePostgresCallControlPolicySubject,
38
39
  resolvePostgresNodeIssueControlPolicySubject,
39
40
  resolvePostgresNodeIssueCreationFactoryName,
40
41
  } from './control-policy';
41
42
  import { buildPostgresPlanDiff } from './diff-database-schema';
42
- import {
43
- coalesceSubtreeIssues,
44
- issueNode,
45
- issueSchemaName,
46
- issueTableName,
47
- planIssues,
48
- } from './issue-planner';
43
+ import { coalesceSubtreeIssues, issueNode, issueSchemaName, planIssues } from './issue-planner';
49
44
  import type { PostgresOpFactoryCall } from './op-factory-call';
50
45
  import {
51
46
  CreatePostgresRlsPolicyCall,
@@ -255,8 +250,6 @@ export class PostgresMigrationPlanner implements MigrationPlanner<'sql', 'postgr
255
250
  resolveControlPolicySubject: (issue) =>
256
251
  resolvePostgresNodeIssueControlPolicySubject(issue, options.contract),
257
252
  resolveCreationFactoryName: resolvePostgresNodeIssueCreationFactoryName,
258
- formatSubjectLabel: (factoryName, subject) =>
259
- formatPostgresControlPolicySubjectLabel(factoryName, subject, options.contract),
260
253
  });
261
254
 
262
255
  const result = planIssues({
@@ -288,8 +281,6 @@ export class PostgresMigrationPlanner implements MigrationPlanner<'sql', 'postgr
288
281
  resolveControlPolicySubject: (call) =>
289
282
  resolvePostgresCallControlPolicySubject(call, options.contract),
290
283
  resolveFactoryName: (call) => call.factoryName,
291
- formatSubjectLabel: (factoryName, subject) =>
292
- formatPostgresControlPolicySubjectLabel(factoryName, subject, options.contract),
293
284
  });
294
285
 
295
286
  // Inline `onFieldEvent`-emitted ops after structural DDL. The fixed
@@ -314,15 +305,13 @@ export class PostgresMigrationPlanner implements MigrationPlanner<'sql', 'postgr
314
305
  resolveControlPolicySubject: (call) =>
315
306
  resolvePostgresCallControlPolicySubject(call, options.contract),
316
307
  resolveFactoryName: (call) => call.factoryName,
317
- formatSubjectLabel: (factoryName, subject) =>
318
- formatPostgresControlPolicySubjectLabel(factoryName, subject, options.contract),
319
308
  });
320
309
  const calls = [...result.value.calls, ...schemaDiffPartition.kept, ...fieldEventPartition.kept];
321
310
  const warnings: SqlPlannerConflict[] = [
322
- ...issuePartition.warnings,
323
- ...schemaDiffPartition.warnings,
324
- ...fieldEventPartition.warnings,
325
- ];
311
+ ...issuePartition.suppressions,
312
+ ...schemaDiffPartition.suppressions,
313
+ ...fieldEventPartition.suppressions,
314
+ ].map((record) => renderPostgresSuppression(record, options.contract));
326
315
 
327
316
  return Object.freeze({
328
317
  kind: 'success' as const,
@@ -500,41 +489,14 @@ function isPolicyDiffIssue(issue: SchemaDiffIssue<SqlSchemaDiffNode>): boolean {
500
489
  }
501
490
 
502
491
  /**
503
- * Drops a `not-expected` issue when it is a whole extra TABLE that some
504
- * contract space owns, asking the ownership oracle per node.
505
- *
506
- * The consultation applies ONLY to table-level extras a live table this
507
- * space's contract lacks identified by asking the issue's own node
508
- * (`nodeKind === table`), never by counting path segments. `declaresEntity`
509
- * answers over the whole composition (self included), keyed on the schema-IR
510
- * entity coordinate so a genuine orphan in one namespace is never conflated
511
- * with a same-named table a sibling space declares in another, or with a
512
- * same-named non-table entity (an enum, an RLS policy) a sibling declares in
513
- * the SAME namespace: a positive answer means another space owns THIS table
514
- * in THIS namespace (a table this space owned would be in its expected tree,
515
- * never an extra) — leave it. A negative answer means no space owns it — a
516
- * genuine orphan to drop. `entityKind` is the literal `'table'` here: this
517
- * function only ever asks about a node already confirmed to be
518
- * `PostgresSchemaNodeKind.table` (checked just above), and the diff tree's
519
- * `nodeKind` vocabulary (`'postgres-table'`) is distinct from the storage
520
- * `entries` vocabulary `elementCoordinates` walks (`'table'`) — the literal
521
- * is that storage-entries spelling, not the node kind.
522
- *
523
- * The issue path carries the *resolved DDL schema* (e.g. `public`), but a
524
- * contract space's own declared namespace id can be the unbound sentinel
525
- * (`__unbound__`) that resolves to that schema — the two spellings would
526
- * never string-match. `resolveNamespaceIdForDdlSchema` recovers the raw
527
- * namespace id THIS space's own contract would use for that DDL schema, so
528
- * a table this space's own unbound namespace declares (and any sibling
529
- * whose own unbound namespace resolves the same way) is compared on the
530
- * same coordinate the aggregate's `elementCoordinates` walk yields.
531
- *
532
- * A DEEPER extra (an extra column/constraint on a table this space DOES own —
533
- * only the child drifted, so the table is in the expected tree) is this
534
- * space's own drift and is always kept for dropping; asking the oracle there
535
- * would wrongly suppress it, because the owned table answers `true`. Shallower
536
- * issues (namespace/root) are never ownership-scoped here. No oracle ⇒ every
537
- * extra is kept (single-space plan).
492
+ * Drops a `not-expected` issue when it is a whole extra storage entity (a table
493
+ * or a native enum) that some space in the composition owns. Each such node
494
+ * yields its own storage coordinate (see {@link postgresNodeStorageCoordinate}),
495
+ * so `declaresEntity` answers over the whole composition on one uniform
496
+ * coordinate: a positive answer means a sibling owns this entity here — leave
497
+ * it; a negative answer means a genuine orphan — drop it. A node with no storage
498
+ * coordinate (a namespace, or a deeper column/constraint drift on an owned
499
+ * table) is this space's own and is always kept. No oracle ⇒ keep everything.
538
500
  */
539
501
  function retainUnownedExtras(
540
502
  issues: readonly SchemaDiffIssue<SqlSchemaDiffNode>[],
@@ -545,12 +507,13 @@ function retainUnownedExtras(
545
507
  return issues.filter((issue) => {
546
508
  if (issue.reason !== 'not-expected') return true;
547
509
  const node = issueNode(issue);
548
- if (node === undefined || node.nodeKind !== PostgresSchemaNodeKind.table) return true;
510
+ if (node === undefined) return true;
511
+ const coordinate = postgresNodeStorageCoordinate(node);
512
+ if (coordinate === undefined) return true;
549
513
  const ddlSchemaName = issueSchemaName(issue);
550
- const tableName = issueTableName(issue);
551
- if (ddlSchemaName === undefined || tableName === undefined) return true;
514
+ if (ddlSchemaName === undefined) return true;
552
515
  const namespaceId = resolveNamespaceIdForDdlSchema(contract, ddlSchemaName);
553
- return !ownership.declaresEntity({ namespaceId, entityKind: 'table', entityName: tableName });
516
+ return !ownership.declaresEntity({ namespaceId, ...coordinate });
554
517
  });
555
518
  }
556
519
 
@@ -567,9 +530,9 @@ function retainUnownedExtras(
567
530
  * Returns the real `PostgresNamespaceSchemaNode` reference rather than a
568
531
  * projection: `storageTypePlanCallStrategy` hands this same value to codec
569
532
  * `planTypeOperations` hooks as `schema`, and hooks read the Postgres-specific
570
- * `nativeEnumTypeNames` field off it (via `PostgresNamespaceSchemaNode.is`) to
571
- * decide whether a native enum type already exists — a projection that only
572
- * copies `tables` would silently drop that signal. `StrategyContext.schema`'s
533
+ * `nativeEnums` field off it (via `PostgresNamespaceSchemaNode.is`) to decide
534
+ * whether a native enum type already exists — a projection that only copies
535
+ * `tables` would silently drop that signal. `StrategyContext.schema`'s
573
536
  * declared type (`SqlSchemaIR`, shared with SQLite's flat shape) doesn't
574
537
  * capture this, so the return is `blindCast` the same way `namespaceSchemaNodes`
575
538
  * in the family's relational walk already treats a namespace node as a
@@ -585,7 +548,7 @@ function relationalNamespaceNode(
585
548
  if (namespaceNode === undefined) return undefined;
586
549
  return blindCast<
587
550
  SqlSchemaIR,
588
- 'PostgresNamespaceSchemaNode carries tables (+ nativeEnumTypeNames, read by codec hooks via PostgresNamespaceSchemaNode.is) structurally compatible with the SqlSchemaIR shape the strategy layer declares'
551
+ 'PostgresNamespaceSchemaNode carries tables (+ nativeEnums, read by codec hooks via PostgresNamespaceSchemaNode.is) structurally compatible with the SqlSchemaIR shape the strategy layer declares'
589
552
  >(namespaceNode);
590
553
  }
591
554
 
@@ -17,6 +17,7 @@ import {
17
17
  AlterColumnTypeCall,
18
18
  type AlterColumnTypeOptions,
19
19
  CreateIndexCall,
20
+ CreateNativeEnumTypeCall,
20
21
  CreateSchemaCall,
21
22
  CreateTableCall,
22
23
  DropCheckConstraintCall,
@@ -24,6 +25,7 @@ import {
24
25
  DropConstraintCall,
25
26
  DropDefaultCall,
26
27
  DropIndexCall,
28
+ DropNativeEnumTypeCall,
27
29
  DropNotNullCall,
28
30
  DropTableCall,
29
31
  SetDefaultCall,
@@ -171,6 +173,35 @@ export abstract class PostgresMigration<
171
173
  return new CreateSchemaCall(options.schema).toOp(this.controlAdapterFor('createSchema'));
172
174
  }
173
175
 
176
+ /**
177
+ * Emit a `CREATE TYPE ... AS ENUM (...)` migration operation for a managed
178
+ * native enum. Builds a typed DDL node and lowers it through the stored
179
+ * control adapter (members render in declaration order). Throws if no adapter
180
+ * is present.
181
+ */
182
+ protected createNativeEnumType(options: {
183
+ readonly schema: string;
184
+ readonly typeName: string;
185
+ readonly members: readonly string[];
186
+ }): Promise<SqlMigrationPlanOperation<PostgresPlanTargetDetails>> {
187
+ return new CreateNativeEnumTypeCall(options.schema, options.typeName, options.members).toOp(
188
+ this.controlAdapterFor('createNativeEnumType'),
189
+ );
190
+ }
191
+
192
+ /**
193
+ * Emit a `DROP TYPE` migration operation for a managed native enum, lowered
194
+ * through the stored control adapter. Throws if no adapter is present.
195
+ */
196
+ protected dropNativeEnumType(options: {
197
+ readonly schema: string;
198
+ readonly typeName: string;
199
+ }): Promise<SqlMigrationPlanOperation<PostgresPlanTargetDetails>> {
200
+ return new DropNativeEnumTypeCall(options.schema, options.typeName).toOp(
201
+ this.controlAdapterFor('dropNativeEnumType'),
202
+ );
203
+ }
204
+
174
205
  protected addColumn(options: {
175
206
  readonly schema: string;
176
207
  readonly table: string;
@@ -91,7 +91,6 @@ export function verifyPostgresNamespacePresence(input: {
91
91
  const namespace = new PostgresNamespaceSchemaNode({
92
92
  schemaName: ddlName,
93
93
  tables: {},
94
- nativeEnumTypeNames: [],
95
94
  });
96
95
  issues.push({
97
96
  path: ['database', ddlName],
@@ -15,6 +15,7 @@ import {
15
15
  } from '@prisma-next/sql-contract/types';
16
16
 
17
17
  import { type CfExpr, cfExpr } from '@prisma-next/sql-relational-core/contract-free';
18
+ import { invariant } from '@prisma-next/utils/assertions';
18
19
  import { blindCast } from '@prisma-next/utils/casts';
19
20
  import { ifDefined } from '@prisma-next/utils/defined';
20
21
  import { PostgresTableSource } from './ast/table-source';
@@ -25,7 +26,7 @@ import {
25
26
  rlsEnablementEntityKind,
26
27
  roleEntityKind,
27
28
  } from './entity-kinds';
28
- import type { PostgresNativeEnum } from './postgres-native-enum';
29
+ import { PostgresNativeEnum } from './postgres-native-enum';
29
30
  import type { PostgresRlsEnablement } from './postgres-rls-enablement';
30
31
  import type { PostgresRlsPolicy } from './postgres-rls-policy';
31
32
  import type { PostgresRole } from './postgres-role';
@@ -88,6 +89,26 @@ export class PostgresSchema extends SqlNamespaceBase {
88
89
  'carry',
89
90
  );
90
91
 
92
+ // A native enum keys under its PHYSICAL type name in storage `entries`
93
+ // (ADR 221 coordinate `entityName`). The family/authoring layer keys every
94
+ // block by its author handle, so re-key the `native_enum` slot by each
95
+ // entity's `typeName` here — target-local Postgres code that already knows
96
+ // this entity kind. This runs at both authoring and hydration; a serialized
97
+ // contract's key already equals the `typeName`, so the re-key is idempotent.
98
+ const nativeEnumSlot = dispatched['native_enum'];
99
+ if (nativeEnumSlot !== undefined) {
100
+ const rekeyed: Record<string, unknown> = {};
101
+ for (const [handle, entity] of Object.entries(nativeEnumSlot)) {
102
+ const physicalName = PostgresNativeEnum.is(entity) ? entity.typeName : handle;
103
+ invariant(
104
+ !Object.hasOwn(rekeyed, physicalName),
105
+ `PostgresSchema "${input.id}": two native_enum entities resolve to the same physical type name "${physicalName}". Give them distinct @@map type names.`,
106
+ );
107
+ rekeyed[physicalName] = entity;
108
+ }
109
+ dispatched['native_enum'] = Object.freeze(rekeyed);
110
+ }
111
+
91
112
  // Drop an empty valueSet so presence signals non-emptiness.
92
113
  const valueSetRaw = dispatched['valueSet'];
93
114
  const withPresence =
@@ -300,10 +300,10 @@ export function inferPostgresPslContract(
300
300
  const owners = describedContractOwners(describedContracts ?? []);
301
301
  const enumOwners = describedNativeEnumOwnersByTypeName(describedContracts ?? []);
302
302
 
303
- // Native enum adoption: each namespace's introspected `{ typeName, values }`
304
- // entries become `native_enum` blocks + `pg.enum(<Name>)` columns, minus the
305
- // types a described pack contract already declares (matched by TYPE NAME —
306
- // see describedNativeEnumOwnersByTypeName). Transitional grade gap: an
303
+ // Native enum adoption: each namespace's introspected `enums` nodes become
304
+ // `native_enum` blocks + `pg.enum(<Name>)` columns, minus the types a
305
+ // described pack contract already declares (matched by TYPE NAME — see
306
+ // describedNativeEnumOwnersByTypeName). Transitional grade gap: an
307
307
  // inferred block carries no explicit `control` and inherits the contract's
308
308
  // `defaultControl`; under `defaultControl: 'managed'` the planner does not
309
309
  // yet enforce the native enum lifecycle (CREATE TYPE / DROP TYPE / ADD
@@ -313,17 +313,7 @@ export function inferPostgresPslContract(
313
313
  const packOwnedEnumTypesByNamespace = new Map<string, Map<string, string>>();
314
314
  const enumNamespaceNames = new Set<string>();
315
315
  for (const namespace of namespaces) {
316
- const definitionsForNamespace = new Set(namespace.nativeEnums.map((e) => e.typeName));
317
- for (const typeName of namespace.nativeEnumTypeNames) {
318
- if (!definitionsForNamespace.has(typeName)) {
319
- throw new Error(
320
- `contract infer: introspection reported native enum type "${typeName}" in schema ` +
321
- `"${namespace.schemaName}" without member values — the introspected tree's ` +
322
- '`nativeEnumTypeNames` and `nativeEnums` are out of sync (internal invariant).',
323
- );
324
- }
325
- }
326
- for (const { typeName, values } of namespace.nativeEnums) {
316
+ for (const { typeName, members } of namespace.nativeEnums) {
327
317
  const owner = enumOwners.get(
328
318
  coordinateKey({
329
319
  namespaceId: namespace.schemaName,
@@ -341,7 +331,7 @@ export function inferPostgresPslContract(
341
331
  packOwnedEnumTypesByNamespace.set(namespace.schemaName, owned);
342
332
  continue;
343
333
  }
344
- enumDefinitions.set(typeName, values);
334
+ enumDefinitions.set(typeName, members);
345
335
  enumNamespaceNames.add(namespace.schemaName);
346
336
  }
347
337
  }
@@ -0,0 +1,26 @@
1
+ import type { EntityCoordinate } from '@prisma-next/framework-components/ir';
2
+ import type { SqlSchemaIRNode } from '@prisma-next/sql-schema-ir/types';
3
+ import { PostgresNativeEnumSchemaNode } from './postgres-native-enum-schema-node';
4
+ import { PostgresTableSchemaNode } from './postgres-table-schema-node';
5
+ import { postgresNodeEntityKind } from './schema-node-kinds';
6
+
7
+ /**
8
+ * The storage-`entries` coordinate `(entityKind, entityName)` a Postgres diff
9
+ * node addresses — a table by its name, a native enum by its physical type
10
+ * name. `undefined` for a node that is not a whole storage entity (a namespace,
11
+ * a column, a policy). Lets ownership/subject resolution treat every entity kind
12
+ * uniformly, the node self-describing its storage identity.
13
+ */
14
+ export function postgresNodeStorageCoordinate(
15
+ node: SqlSchemaIRNode,
16
+ ): Pick<EntityCoordinate, 'entityKind' | 'entityName'> | undefined {
17
+ if (PostgresTableSchemaNode.is(node)) {
18
+ const entityKind = postgresNodeEntityKind(node.nodeKind);
19
+ return entityKind === undefined ? undefined : { entityKind, entityName: node.name };
20
+ }
21
+ if (PostgresNativeEnumSchemaNode.is(node)) {
22
+ const entityKind = postgresNodeEntityKind(node.nodeKind);
23
+ return entityKind === undefined ? undefined : { entityKind, entityName: node.typeName };
24
+ }
25
+ return undefined;
26
+ }
@@ -1,20 +1,20 @@
1
1
  import type { DiffableNode } from '@prisma-next/framework-components/control';
2
2
  import { freezeNode } from '@prisma-next/framework-components/ir';
3
3
  import { assertNode, SqlSchemaIRNode } from '@prisma-next/sql-schema-ir/types';
4
+ import type { PostgresNativeEnumSchemaNode } from './postgres-native-enum-schema-node';
4
5
  import type { PostgresTableSchemaNode } from './postgres-table-schema-node';
5
6
  import { PostgresSchemaNodeKind } from './schema-node-kinds';
6
7
 
7
- /** One introspected native Postgres enum type, in `pg_enum.enumsortorder` (declaration) order. */
8
- export interface PostgresNativeEnumIntrospection {
9
- readonly typeName: string;
10
- readonly values: readonly string[];
11
- }
12
-
13
8
  export interface PostgresNamespaceSchemaNodeInput {
14
9
  readonly schemaName: string;
15
10
  readonly tables: Readonly<Record<string, PostgresTableSchemaNode>>;
16
- readonly nativeEnumTypeNames: readonly string[];
17
- readonly nativeEnums?: readonly PostgresNativeEnumIntrospection[];
11
+ /**
12
+ * The native-enum diff nodes belonging to this namespace, member values in
13
+ * `pg_enum.enumsortorder` (declaration) order. The expected
14
+ * (contract-projected) side attaches each node's `control` (the contract
15
+ * entity's grade); introspection builds nodes with no `control`.
16
+ */
17
+ readonly nativeEnums?: readonly PostgresNativeEnumSchemaNode[];
18
18
  }
19
19
 
20
20
  /**
@@ -22,38 +22,29 @@ export interface PostgresNamespaceSchemaNodeInput {
22
22
  * single namespace. Per-schema consumers (the relational planner,
23
23
  * toSchemaView) read this node's `tables` field structurally via
24
24
  * `blindCast`/`SqlSchemaIRNode` — not through a static `SqlSchemaIR`
25
- * assignment — because `nodeKind` now carries this node's own literal
25
+ * assignment — because `nodeKind` carries this node's own literal
26
26
  * (`postgres-namespace`), distinct from `SqlSchemaIR`'s own (`sql-schema`).
27
27
  *
28
- * `id` is the schema name. `isEqualTo` is identity two namespace nodes are
29
- * equal iff their ids (schema names) match. `children()` returns the table
30
- * nodes. Per-schema metadata is carried on the typed `nativeEnumTypeNames`
31
- * field, not an annotations bag.
28
+ * `id` is the schema name; `isEqualTo` is identity on it; `children()` returns
29
+ * the table nodes plus `nativeEnums`.
32
30
  *
33
- * `nativeEnums` carries the same enum types with their ordered member
34
- * values (`{ typeName, values }`), for consumers that need the values
35
- * (PSL inference, the printer). `nativeEnumTypeNames` stays a plain name
36
- * list read independently by existing consumers (codec `planTypeOperations`
37
- * hooks, the infer throw) so none of them need the values to keep working.
31
+ * `nativeEnums` is the diff-tree representation of native enum types the
32
+ * differ pairs the sole enum carrier, built directly by both sides: the
33
+ * expected (contract-projected) side and introspection alike hand in
34
+ * `PostgresNativeEnumSchemaNode`s.
38
35
  */
39
36
  export class PostgresNamespaceSchemaNode extends SqlSchemaIRNode implements DiffableNode {
40
37
  override readonly nodeKind = PostgresSchemaNodeKind.namespace;
41
38
 
42
39
  readonly schemaName: string;
43
40
  readonly tables: Readonly<Record<string, PostgresTableSchemaNode>>;
44
- readonly nativeEnumTypeNames: readonly string[];
45
- readonly nativeEnums: readonly PostgresNativeEnumIntrospection[];
41
+ readonly nativeEnums: readonly PostgresNativeEnumSchemaNode[];
46
42
 
47
43
  constructor(input: PostgresNamespaceSchemaNodeInput) {
48
44
  super();
49
45
  this.schemaName = input.schemaName;
50
46
  this.tables = Object.freeze({ ...input.tables });
51
- this.nativeEnumTypeNames = Object.freeze([...input.nativeEnumTypeNames]);
52
- this.nativeEnums = Object.freeze(
53
- (input.nativeEnums ?? []).map((entry) =>
54
- Object.freeze({ typeName: entry.typeName, values: Object.freeze([...entry.values]) }),
55
- ),
56
- );
47
+ this.nativeEnums = Object.freeze([...(input.nativeEnums ?? [])]);
57
48
  freezeNode(this);
58
49
  }
59
50
 
@@ -66,7 +57,7 @@ export class PostgresNamespaceSchemaNode extends SqlSchemaIRNode implements Diff
66
57
  }
67
58
 
68
59
  children(): readonly DiffableNode[] {
69
- return Object.values(this.tables);
60
+ return [...Object.values(this.tables), ...this.nativeEnums];
70
61
  }
71
62
 
72
63
  static is(node: SqlSchemaIRNode): node is PostgresNamespaceSchemaNode {
@@ -0,0 +1,83 @@
1
+ import type { ControlPolicy } from '@prisma-next/contract/types';
2
+ import type { DiffableNode } from '@prisma-next/framework-components/control';
3
+ import { freezeNode } from '@prisma-next/framework-components/ir';
4
+ import { assertNode, SqlSchemaIRNode } from '@prisma-next/sql-schema-ir/types';
5
+ import { blindCast } from '@prisma-next/utils/casts';
6
+ import { PostgresSchemaNodeKind } from './schema-node-kinds';
7
+
8
+ export interface PostgresNativeEnumSchemaNodeInput {
9
+ /** The Postgres type name (`CREATE TYPE <typeName> AS ENUM (…)`). */
10
+ readonly typeName: string;
11
+ /** The owning DDL schema name — enum types are schema-scoped. */
12
+ readonly namespaceId: string;
13
+ /** Member values in declaration (`pg_enum.enumsortorder`) order. */
14
+ readonly members: readonly string[];
15
+ /** The contract entity's control grade — expected-side nodes only. */
16
+ readonly control?: ControlPolicy;
17
+ }
18
+
19
+ /**
20
+ * Schema-diff leaf node for a native Postgres enum type.
21
+ *
22
+ * This is a derived, transient node walked by the differ — it is NEVER
23
+ * serialized. Both sides derive it on the namespace node: the expected side
24
+ * from the contract's hydrated `entries.native_enum` entities, the actual
25
+ * side from the adapter's introspected `nativeEnums`.
26
+ *
27
+ * Enum types are schema-scoped, so identity is (namespace, type name): the
28
+ * namespace coordinate comes from tree position (the differ pairs enum nodes
29
+ * only inside an already-paired namespace) and `id` carries the type name,
30
+ * prefixed with the entries-kind vocabulary (`native_enum:<typeName>`) so it
31
+ * cannot collide with a sibling table of the same name — the same
32
+ * sibling-prefix convention relational column nodes use (`column:<name>`).
33
+ *
34
+ * `isEqualTo` compares members POSITIONALLY — `['a','b'] ≠ ['b','a']` —
35
+ * because Postgres enum sort order is semantic. `control` is contract
36
+ * metadata, not database state, and does not participate in equality.
37
+ */
38
+ export class PostgresNativeEnumSchemaNode extends SqlSchemaIRNode implements DiffableNode {
39
+ override readonly nodeKind = PostgresSchemaNodeKind.nativeEnum;
40
+
41
+ readonly typeName: string;
42
+ readonly namespaceId: string;
43
+ readonly members: readonly string[];
44
+ declare readonly control?: ControlPolicy;
45
+
46
+ constructor(input: PostgresNativeEnumSchemaNodeInput) {
47
+ super();
48
+ this.typeName = input.typeName;
49
+ this.namespaceId = input.namespaceId;
50
+ this.members = Object.freeze([...input.members]);
51
+ if (input.control !== undefined) this.control = input.control;
52
+ freezeNode(this);
53
+ }
54
+
55
+ get id(): string {
56
+ return `native_enum:${this.typeName}`;
57
+ }
58
+
59
+ children(): readonly DiffableNode[] {
60
+ return [];
61
+ }
62
+
63
+ isEqualTo(other: DiffableNode): boolean {
64
+ const node = blindCast<
65
+ SqlSchemaIRNode,
66
+ 'every diff-tree node the differ pairs is a SqlSchemaIRNode; the guard rejects non-enum kinds'
67
+ >(other);
68
+ PostgresNativeEnumSchemaNode.assert(node);
69
+ return (
70
+ this.typeName === node.typeName &&
71
+ this.members.length === node.members.length &&
72
+ this.members.every((member, index) => member === node.members[index])
73
+ );
74
+ }
75
+
76
+ static is(node: SqlSchemaIRNode): node is PostgresNativeEnumSchemaNode {
77
+ return node.nodeKind === PostgresSchemaNodeKind.nativeEnum;
78
+ }
79
+
80
+ static assert(node: SqlSchemaIRNode): asserts node is PostgresNativeEnumSchemaNode {
81
+ assertNode(node, 'PostgresNativeEnumSchemaNode', PostgresNativeEnumSchemaNode.is);
82
+ }
83
+ }
@@ -29,6 +29,7 @@ export const PostgresSchemaNodeKind = {
29
29
  table: 'postgres-table',
30
30
  policy: 'postgres-policy',
31
31
  role: 'postgres-role',
32
+ nativeEnum: 'postgres-native-enum',
32
33
  } as const;
33
34
 
34
35
  export type PostgresSchemaNodeKind =
@@ -54,6 +55,7 @@ const POSTGRES_NODE_GRANULARITY: Readonly<Record<PostgresSchemaNodeKind, DiffSub
54
55
  // not from the granularity.
55
56
  [PostgresSchemaNodeKind.policy]: 'structural',
56
57
  [PostgresSchemaNodeKind.role]: 'structural',
58
+ [PostgresSchemaNodeKind.nativeEnum]: 'entity',
57
59
  };
58
60
 
59
61
  function isPostgresSchemaNodeKind(nodeKind: string): nodeKind is PostgresSchemaNodeKind {
@@ -82,15 +84,22 @@ export function postgresDiffSubjectGranularity(nodeKind: string): DiffSubjectGra
82
84
  }
83
85
 
84
86
  /**
85
- * The one real map from a Postgres-specific `nodeKind` to its storage
86
- * `entityKind` — the same vocabulary the contract storage's `entries`
87
- * dictionary keys use. Only the whole-table kind has an entity of its own;
87
+ * The map from a Postgres-specific `nodeKind` to its storage `entityKind` — the
88
+ * same vocabulary the contract storage's `entries` dictionary keys use. The
89
+ * whole-table and native-enum kinds each have an entity of their own;
88
90
  * database/namespace/policy/role nodes map to nothing here (a namespace is
89
91
  * addressed by id, not by an `entries` kind; policies and roles are
90
92
  * structural, never a sibling space's unclaimed entity).
93
+ *
94
+ * The native enum keys under its PHYSICAL type name in `entries.native_enum`
95
+ * (ADR 221), so its diff node's `typeName` addresses the entity on the generic
96
+ * coordinate directly — no by-type-name walk needed. This lets the framework's
97
+ * unclaimed-elements sweep classify enum ownership the same coordinate way it
98
+ * classifies tables.
91
99
  */
92
100
  const POSTGRES_NODE_ENTITY_KIND: Partial<Readonly<Record<PostgresSchemaNodeKind, string>>> = {
93
101
  [PostgresSchemaNodeKind.table]: 'table',
102
+ [PostgresSchemaNodeKind.nativeEnum]: 'native_enum',
94
103
  };
95
104
 
96
105
  /** Looks up the storage entityKind for a Postgres-specific `nodeKind`. */
@@ -81,6 +81,19 @@ export function qualifyName(schemaName: string, objectName: string): string {
81
81
  return `${quoteIdentifier(schemaName)}.${quoteIdentifier(objectName)}`;
82
82
  }
83
83
 
84
+ /**
85
+ * Quotes a possibly dot-qualified name segment-by-segment:
86
+ * `order_status` -> `"order_status"`, `auth.aal_level` -> `"auth"."aal_level"`.
87
+ * A single-segment name round-trips to exactly {@link quoteIdentifier}; whole-
88
+ * string quoting (`"auth.aal_level"`) would be one wrong identifier.
89
+ */
90
+ export function quoteQualifiedName(name: string): string {
91
+ return name
92
+ .split('.')
93
+ .map((segment) => quoteIdentifier(segment))
94
+ .join('.');
95
+ }
96
+
84
97
  /**
85
98
  * Validates that an enum value doesn't exceed PostgreSQL's label length limit.
86
99
  *
@@ -93,10 +106,10 @@ export function qualifyName(schemaName: string, objectName: string): string {
93
106
  * @throws {SqlEscapeError} If the value exceeds the maximum length
94
107
  */
95
108
  export function validateEnumValueLength(value: string, enumTypeName: string): void {
96
- if (value.length > MAX_IDENTIFIER_LENGTH) {
109
+ if (new TextEncoder().encode(value).length > MAX_IDENTIFIER_LENGTH) {
97
110
  throw new SqlEscapeError(
98
111
  `Enum value "${value.slice(0, 20)}..." for type "${enumTypeName}" exceeds PostgreSQL's ` +
99
- `${MAX_IDENTIFIER_LENGTH}-character label limit`,
112
+ `${MAX_IDENTIFIER_LENGTH}-byte label limit`,
100
113
  value,
101
114
  'literal',
102
115
  );
@@ -9,9 +9,11 @@ export {
9
9
  PostgresCreatePolicy,
10
10
  PostgresCreateSchema,
11
11
  PostgresCreateTable,
12
+ PostgresCreateType,
12
13
  PostgresDdlNode,
13
14
  type PostgresDdlVisitor,
14
15
  PostgresDisableRowLevelSecurity,
15
16
  PostgresDropPolicy,
17
+ PostgresDropType,
16
18
  type RlsPolicyOperation,
17
19
  } from '../core/ddl/nodes';
@@ -8,6 +8,7 @@ export {
8
8
  AlterColumnTypeCall,
9
9
  CreateExtensionCall,
10
10
  CreateIndexCall,
11
+ CreateNativeEnumTypeCall,
11
12
  CreateSchemaCall,
12
13
  CreateTableCall,
13
14
  DataTransformCall,
@@ -16,6 +17,7 @@ export {
16
17
  DropConstraintCall,
17
18
  DropDefaultCall,
18
19
  DropIndexCall,
20
+ DropNativeEnumTypeCall,
19
21
  DropNotNullCall,
20
22
  DropTableCall,
21
23
  RawSqlCall,
@@ -2,6 +2,7 @@ export {
2
2
  escapeLiteral,
3
3
  qualifyName,
4
4
  quoteIdentifier,
5
+ quoteQualifiedName,
5
6
  SqlEscapeError,
6
7
  validateEnumValueLength,
7
8
  } from '../core/sql-utils';
@@ -23,8 +23,11 @@ export {
23
23
  export {
24
24
  PostgresNamespaceSchemaNode,
25
25
  type PostgresNamespaceSchemaNodeInput,
26
- type PostgresNativeEnumIntrospection,
27
26
  } from '../core/schema-ir/postgres-namespace-schema-node';
27
+ export {
28
+ PostgresNativeEnumSchemaNode,
29
+ type PostgresNativeEnumSchemaNodeInput,
30
+ } from '../core/schema-ir/postgres-native-enum-schema-node';
28
31
  export {
29
32
  PostgresPolicySchemaNode,
30
33
  type PostgresPolicySchemaNodeInput,