@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.
- package/dist/authoring-DzHOdWTl.mjs +364 -0
- package/dist/authoring-DzHOdWTl.mjs.map +1 -0
- package/dist/canonicalize-Bn3BM1Cn.mjs +46 -0
- package/dist/canonicalize-Bn3BM1Cn.mjs.map +1 -0
- package/dist/canonicalize-CAbXtVtB.d.mts +37 -0
- package/dist/canonicalize-CAbXtVtB.d.mts.map +1 -0
- package/dist/{codec-ids-CnXu9Qy3.d.mts → codec-ids-BzrFF-I4.d.mts} +3 -2
- package/dist/codec-ids-BzrFF-I4.d.mts.map +1 -0
- package/dist/{codec-ids-BvytN2P8.mjs → codec-ids-C_-Hj6bL.mjs} +3 -2
- package/dist/codec-ids-C_-Hj6bL.mjs.map +1 -0
- package/dist/codec-ids.d.mts +2 -2
- package/dist/codec-ids.mjs +2 -2
- package/dist/{codec-types-DHCkwPKE.d.mts → codec-types-BDVmvKAW.d.mts} +3 -2
- package/dist/codec-types-BDVmvKAW.d.mts.map +1 -0
- package/dist/codec-types.d.mts +1 -1
- package/dist/{codecs--0A5_4Bq.d.mts → codecs-Dk2sxGLv.d.mts} +77 -4
- package/dist/codecs-Dk2sxGLv.d.mts.map +1 -0
- package/dist/codecs.d.mts +1 -1
- package/dist/codecs.mjs +117 -2
- package/dist/codecs.mjs.map +1 -1
- package/dist/contract-free.d.mts +8 -3
- package/dist/contract-free.d.mts.map +1 -1
- package/dist/contract-free.mjs +4 -4
- package/dist/control-policy-DUFgy9jO.mjs +1103 -0
- package/dist/control-policy-DUFgy9jO.mjs.map +1 -0
- package/dist/control.d.mts +2 -2
- package/dist/control.d.mts.map +1 -1
- package/dist/control.mjs +757 -35
- package/dist/control.mjs.map +1 -1
- package/dist/data-transform-BOWpliq8.mjs.map +1 -1
- package/dist/{data-transform-DDgWdB5o.d.mts → data-transform-DPzwBMhW.d.mts} +3 -3
- package/dist/{data-transform-DDgWdB5o.d.mts.map → data-transform-DPzwBMhW.d.mts.map} +1 -1
- package/dist/data-transform.d.mts +1 -1
- package/dist/{ddl-QDyOSeLc.mjs → ddl-ujxH2A7r.mjs} +63 -6
- package/dist/ddl-ujxH2A7r.mjs.map +1 -0
- package/dist/ddl.d.mts +3 -2
- package/dist/ddl.mjs +2 -2
- package/dist/default-normalizer.d.mts +1 -1
- package/dist/default-normalizer.d.mts.map +1 -1
- package/dist/default-normalizer.mjs +247 -1
- package/dist/default-normalizer.mjs.map +1 -0
- package/dist/descriptor-meta-CuGDsrG4.mjs +19 -0
- package/dist/descriptor-meta-CuGDsrG4.mjs.map +1 -0
- package/dist/descriptor-meta-runtime-UwYdV8fD.mjs +12 -0
- package/dist/descriptor-meta-runtime-UwYdV8fD.mjs.map +1 -0
- package/dist/diff-database-schema-BmSmWcaY.mjs +313 -0
- package/dist/diff-database-schema-BmSmWcaY.mjs.map +1 -0
- package/dist/diff-database-schema.d.mts +46 -0
- package/dist/diff-database-schema.d.mts.map +1 -0
- package/dist/diff-database-schema.mjs +2 -0
- package/dist/issue-planner.d.mts +35 -11
- package/dist/issue-planner.d.mts.map +1 -1
- package/dist/issue-planner.mjs +2 -2
- package/dist/migration.d.mts +5 -5
- package/dist/migration.d.mts.map +1 -1
- package/dist/migration.mjs +4 -4
- package/dist/namespace-ids-xtp_ZY86.mjs +7 -0
- package/dist/namespace-ids-xtp_ZY86.mjs.map +1 -0
- package/dist/native-type-normalizer.mjs +37 -1
- package/dist/native-type-normalizer.mjs.map +1 -0
- package/dist/nodes-CSkPX3rI.d.mts +120 -0
- package/dist/nodes-CSkPX3rI.d.mts.map +1 -0
- package/dist/{nodes-Bbhs2rwj.mjs → nodes-Dh__NS58.mjs} +56 -2
- package/dist/nodes-Dh__NS58.mjs.map +1 -0
- package/dist/{op-factory-call-D_p5vxwt.mjs → op-factory-call-BKRnMgrB.mjs} +205 -31
- package/dist/op-factory-call-BKRnMgrB.mjs.map +1 -0
- package/dist/{op-factory-call-DmQEc3XV.d.mts → op-factory-call-Dt-Jx5JS.d.mts} +46 -15
- package/dist/op-factory-call-Dt-Jx5JS.d.mts.map +1 -0
- package/dist/op-factory-call.d.mts +2 -2
- package/dist/op-factory-call.mjs +2 -2
- package/dist/pack.d.mts +115 -3
- package/dist/pack.d.mts.map +1 -1
- package/dist/pack.mjs +1 -1
- package/dist/planner-BMBRPzsv.mjs +367 -0
- package/dist/planner-BMBRPzsv.mjs.map +1 -0
- package/dist/{planner-ddl-builders-B2wOwLqI.mjs → planner-ddl-builders-B8Nn6nHN.mjs} +12 -27
- package/dist/planner-ddl-builders-B8Nn6nHN.mjs.map +1 -0
- package/dist/planner-ddl-builders.d.mts +4 -6
- package/dist/planner-ddl-builders.d.mts.map +1 -1
- package/dist/planner-ddl-builders.mjs +2 -2
- package/dist/planner-identity-values-CJPha2Sz.mjs.map +1 -1
- package/dist/planner-identity-values.d.mts +8 -2
- package/dist/planner-identity-values.d.mts.map +1 -1
- package/dist/planner-identity-values.mjs +2 -2
- package/dist/{planner-produced-postgres-migration-QqHa2C2l.d.mts → planner-produced-postgres-migration-C0a7I1-e.d.mts} +4 -4
- package/dist/{planner-produced-postgres-migration-QqHa2C2l.d.mts.map → planner-produced-postgres-migration-C0a7I1-e.d.mts.map} +1 -1
- package/dist/{planner-produced-postgres-migration-Cji5vxUf.mjs → planner-produced-postgres-migration-Dp6IO_gj.mjs} +3 -3
- package/dist/{planner-produced-postgres-migration-Cji5vxUf.mjs.map → planner-produced-postgres-migration-Dp6IO_gj.mjs.map} +1 -1
- package/dist/planner-produced-postgres-migration.d.mts +1 -1
- package/dist/planner-produced-postgres-migration.mjs +1 -1
- package/dist/{planner-sql-checks-jqUUGyQR.mjs → planner-sql-checks-Cze3vMfe.mjs} +2 -2
- package/dist/{planner-sql-checks-jqUUGyQR.mjs.map → planner-sql-checks-Cze3vMfe.mjs.map} +1 -1
- package/dist/planner-sql-checks.d.mts +2 -2
- package/dist/planner-sql-checks.d.mts.map +1 -1
- package/dist/planner-sql-checks.mjs +1 -1
- package/dist/{planner-target-details-CIY6tLeo.d.mts → planner-target-details-HkP4RrRO.d.mts} +2 -2
- package/dist/planner-target-details-HkP4RrRO.d.mts.map +1 -0
- package/dist/planner-target-details.d.mts +1 -1
- package/dist/planner.d.mts +55 -17
- package/dist/planner.d.mts.map +1 -1
- package/dist/planner.mjs +3 -2
- package/dist/{postgres-contract-serializer-k3TAcPMY.mjs → postgres-contract-view-3xfUgjnC.mjs} +44 -41
- package/dist/postgres-contract-view-3xfUgjnC.mjs.map +1 -0
- package/dist/postgres-contract-view-C9j5cqP0.d.mts +34 -0
- package/dist/postgres-contract-view-C9j5cqP0.d.mts.map +1 -0
- package/dist/postgres-database-schema-node-C9bTuMrd.d.mts +190 -0
- package/dist/postgres-database-schema-node-C9bTuMrd.d.mts.map +1 -0
- package/dist/{postgres-migration-B5jKrXv3.mjs → postgres-migration-D0jwzYDT.mjs} +21 -2
- package/dist/postgres-migration-D0jwzYDT.mjs.map +1 -0
- package/dist/{postgres-migration-Y4YBJqkS.d.mts → postgres-migration-DFaxuRyI.d.mts} +20 -7
- package/dist/postgres-migration-DFaxuRyI.d.mts.map +1 -0
- package/dist/postgres-native-enum-xdiqmBf_.mjs +41 -0
- package/dist/postgres-native-enum-xdiqmBf_.mjs.map +1 -0
- package/dist/postgres-rls-policy--CSddvMR.d.mts +52 -0
- package/dist/postgres-rls-policy--CSddvMR.d.mts.map +1 -0
- package/dist/{postgres-schema-COGZ1ark.mjs → postgres-schema-CajYAwny.mjs} +135 -7
- package/dist/postgres-schema-CajYAwny.mjs.map +1 -0
- package/dist/postgres-schema-DqnhQRvR.d.mts +226 -0
- package/dist/postgres-schema-DqnhQRvR.d.mts.map +1 -0
- package/dist/postgres-table-schema-node-BgaSrxYN.mjs +249 -0
- package/dist/postgres-table-schema-node-BgaSrxYN.mjs.map +1 -0
- package/dist/render-ops.d.mts +1 -1
- package/dist/{render-typescript-KMgosran.mjs → render-typescript-7yqILcwr.mjs} +45 -16
- package/dist/render-typescript-7yqILcwr.mjs.map +1 -0
- package/dist/render-typescript.mjs +1 -1
- package/dist/resolve-ddl-schema-Bv1ZvjX8.mjs +22 -0
- package/dist/resolve-ddl-schema-Bv1ZvjX8.mjs.map +1 -0
- package/dist/rls-canonicalize.d.mts +2 -0
- package/dist/rls-canonicalize.mjs +2 -0
- package/dist/runtime.d.mts +6 -8
- package/dist/runtime.d.mts.map +1 -1
- package/dist/runtime.mjs +3 -3
- package/dist/runtime.mjs.map +1 -1
- package/dist/schema-ir-annotations.d.mts +15 -0
- package/dist/schema-ir-annotations.d.mts.map +1 -0
- package/dist/schema-ir-annotations.mjs +2 -0
- package/dist/schema-node-kinds-ClScchhi.mjs +76 -0
- package/dist/schema-node-kinds-ClScchhi.mjs.map +1 -0
- package/dist/types.d.mts +4 -140
- package/dist/types.mjs +3 -2
- package/package.json +23 -17
- package/src/contract-free/checks.ts +73 -0
- package/src/contract-free/ddl.ts +45 -1
- package/src/core/authoring.ts +287 -2
- package/src/core/codec-ids.ts +1 -0
- package/src/core/codec-type-map.ts +2 -0
- package/src/core/codecs.ts +141 -1
- package/src/core/ddl/nodes.ts +94 -2
- package/src/core/default-normalizer.ts +122 -2
- package/src/core/descriptor-meta.ts +6 -1
- package/src/core/entity-kinds.ts +30 -0
- package/src/core/migrations/bound-schema.ts +10 -0
- package/src/core/migrations/column-ddl-rendering.ts +117 -0
- package/src/core/migrations/contract-to-postgres-database-schema-node.ts +149 -0
- package/src/core/migrations/control-policy.ts +70 -47
- package/src/core/migrations/diff-database-schema.ts +280 -0
- package/src/core/migrations/issue-planner.ts +647 -652
- package/src/core/migrations/op-factory-call.ts +109 -21
- package/src/core/migrations/operations/columns.ts +24 -12
- package/src/core/migrations/operations/constraints.ts +1 -1
- package/src/core/migrations/operations/data-transform.ts +6 -0
- package/src/core/migrations/operations/dependencies.ts +2 -1
- package/src/core/migrations/operations/rls.ts +106 -0
- package/src/core/migrations/operations/shared.ts +0 -30
- package/src/core/migrations/planner-ddl-builders.ts +20 -58
- package/src/core/migrations/planner-identity-values.ts +1 -1
- package/src/core/migrations/planner-recipes.ts +31 -13
- package/src/core/migrations/planner-sql-checks.ts +1 -1
- package/src/core/migrations/planner-strategies.ts +248 -216
- package/src/core/migrations/planner-target-details.ts +3 -1
- package/src/core/migrations/planner.ts +297 -59
- package/src/core/migrations/postgres-migration.ts +34 -4
- package/src/core/migrations/render-typescript.ts +42 -15
- package/src/core/migrations/resolve-ddl-schema.ts +26 -0
- package/src/core/migrations/runner.ts +10 -16
- package/src/core/migrations/verify-postgres-namespaces.ts +47 -30
- package/src/core/namespace-ids.ts +3 -0
- package/src/core/postgres-contract-serializer.ts +41 -71
- package/src/core/postgres-contract-view.ts +48 -0
- package/src/core/postgres-native-enum.ts +51 -0
- package/src/core/postgres-rls-policy.ts +64 -0
- package/src/core/postgres-role.ts +37 -0
- package/src/core/postgres-schema-verifier.ts +10 -7
- package/src/core/postgres-schema.ts +62 -14
- package/src/core/postgres-validators.ts +27 -0
- package/src/core/psl-infer/infer-psl-contract.ts +1068 -0
- package/src/core/psl-infer/postgres-default-mapping.ts +16 -0
- package/src/core/psl-infer/postgres-type-map.ts +157 -0
- package/src/core/rls/canonicalize.ts +48 -0
- package/src/core/schema-ir/postgres-database-schema-node.ts +60 -0
- package/src/core/schema-ir/postgres-namespace-schema-node.ts +60 -0
- package/src/core/schema-ir/postgres-policy-schema-node.ts +90 -0
- package/src/core/schema-ir/postgres-role-schema-node.ts +63 -0
- package/src/core/schema-ir/postgres-table-schema-node.ts +118 -0
- package/src/core/schema-ir/schema-node-kinds.ts +106 -0
- package/src/exports/contract-free.ts +7 -1
- package/src/exports/control.ts +27 -33
- package/src/exports/ddl.ts +4 -0
- package/src/exports/diff-database-schema.ts +7 -0
- package/src/exports/issue-planner.ts +1 -1
- package/src/exports/migration.ts +1 -0
- package/src/exports/op-factory-call.ts +1 -0
- package/src/exports/planner-ddl-builders.ts +0 -2
- package/src/exports/planner-identity-values.ts +4 -1
- package/src/exports/planner.ts +1 -0
- package/src/exports/rls-canonicalize.ts +6 -0
- package/src/exports/runtime.ts +1 -0
- package/src/exports/schema-ir-annotations.ts +1 -0
- package/src/exports/types.ts +28 -0
- package/dist/codec-ids-BvytN2P8.mjs.map +0 -1
- package/dist/codec-ids-CnXu9Qy3.d.mts.map +0 -1
- package/dist/codec-types-DHCkwPKE.d.mts.map +0 -1
- package/dist/codecs--0A5_4Bq.d.mts.map +0 -1
- package/dist/ddl-QDyOSeLc.mjs.map +0 -1
- package/dist/default-normalizer-DyyCHQWs.mjs +0 -131
- package/dist/default-normalizer-DyyCHQWs.mjs.map +0 -1
- package/dist/descriptor-meta-CpGygXpI.mjs +0 -140
- package/dist/descriptor-meta-CpGygXpI.mjs.map +0 -1
- package/dist/issue-planner-DL6g3CmE.mjs +0 -824
- package/dist/issue-planner-DL6g3CmE.mjs.map +0 -1
- package/dist/native-type-normalizer-Bc9XJzWC.mjs +0 -38
- package/dist/native-type-normalizer-Bc9XJzWC.mjs.map +0 -1
- package/dist/nodes-Bbhs2rwj.mjs.map +0 -1
- package/dist/nodes-pLeLgdis.d.mts +0 -67
- package/dist/nodes-pLeLgdis.d.mts.map +0 -1
- package/dist/op-factory-call-D_p5vxwt.mjs.map +0 -1
- package/dist/op-factory-call-DmQEc3XV.d.mts.map +0 -1
- package/dist/planner-Bs_baQax.mjs +0 -324
- package/dist/planner-Bs_baQax.mjs.map +0 -1
- package/dist/planner-ddl-builders-B2wOwLqI.mjs.map +0 -1
- package/dist/planner-target-details-CIY6tLeo.d.mts.map +0 -1
- package/dist/postgres-contract-serializer-k3TAcPMY.mjs.map +0 -1
- package/dist/postgres-migration-B5jKrXv3.mjs.map +0 -1
- package/dist/postgres-migration-Y4YBJqkS.d.mts.map +0 -1
- package/dist/postgres-schema-COGZ1ark.mjs.map +0 -1
- package/dist/render-typescript-KMgosran.mjs.map +0 -1
- package/dist/types.d.mts.map +0 -1
- package/src/core/postgres-enum-type-schema.ts +0 -17
|
@@ -13,28 +13,47 @@ import {
|
|
|
13
13
|
plannerFailure,
|
|
14
14
|
} from '@prisma-next/family-sql/control';
|
|
15
15
|
import type { ExecuteRequestLowerer } from '@prisma-next/family-sql/control-adapter';
|
|
16
|
-
import { verifySqlSchema } from '@prisma-next/family-sql/schema-verify';
|
|
17
16
|
import type { TargetBoundComponentDescriptor } from '@prisma-next/framework-components/components';
|
|
18
17
|
import type {
|
|
19
18
|
MigrationPlanner,
|
|
20
19
|
MigrationPlanWithAuthoringSurface,
|
|
21
20
|
MigrationScaffoldContext,
|
|
22
|
-
|
|
21
|
+
SchemaDiffIssue,
|
|
22
|
+
SchemaOwnership,
|
|
23
23
|
} from '@prisma-next/framework-components/control';
|
|
24
24
|
import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir';
|
|
25
|
+
import type { SqlStorage } from '@prisma-next/sql-contract/types';
|
|
26
|
+
import type { SqlSchemaIR } from '@prisma-next/sql-schema-ir/types';
|
|
25
27
|
import { blindCast } from '@prisma-next/utils/casts';
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
28
|
+
import { ifDefined } from '@prisma-next/utils/defined';
|
|
29
|
+
import { PostgresRlsPolicy } from '../postgres-rls-policy';
|
|
30
|
+
import { PostgresDatabaseSchemaNode } from '../schema-ir/postgres-database-schema-node';
|
|
31
|
+
import { PostgresPolicySchemaNode } from '../schema-ir/postgres-policy-schema-node';
|
|
32
|
+
import { PostgresSchemaNodeKind, type SqlSchemaDiffNode } from '../schema-ir/schema-node-kinds';
|
|
28
33
|
import {
|
|
29
34
|
formatPostgresControlPolicySubjectLabel,
|
|
35
|
+
resolveNamespaceIdForDdlSchema,
|
|
30
36
|
resolvePostgresCallControlPolicySubject,
|
|
31
|
-
|
|
32
|
-
|
|
37
|
+
resolvePostgresNodeIssueControlPolicySubject,
|
|
38
|
+
resolvePostgresNodeIssueCreationFactoryName,
|
|
33
39
|
} from './control-policy';
|
|
34
|
-
import {
|
|
40
|
+
import { buildPostgresPlanDiff } from './diff-database-schema';
|
|
41
|
+
import {
|
|
42
|
+
coalesceSubtreeIssues,
|
|
43
|
+
issueNode,
|
|
44
|
+
issueSchemaName,
|
|
45
|
+
issueTableName,
|
|
46
|
+
planIssues,
|
|
47
|
+
} from './issue-planner';
|
|
35
48
|
import type { PostgresOpFactoryCall } from './op-factory-call';
|
|
49
|
+
import {
|
|
50
|
+
CreatePostgresRlsPolicyCall,
|
|
51
|
+
DropPostgresRlsPolicyCall,
|
|
52
|
+
EnableRowLevelSecurityCall,
|
|
53
|
+
} from './op-factory-call';
|
|
36
54
|
import { TypeScriptRenderablePostgresMigration } from './planner-produced-postgres-migration';
|
|
37
55
|
import { postgresPlannerStrategies } from './planner-strategies';
|
|
56
|
+
import { resolveDdlSchemaForNamespaceStorage } from './resolve-ddl-schema';
|
|
38
57
|
import { verifyPostgresNamespacePresence } from './verify-postgres-namespaces';
|
|
39
58
|
|
|
40
59
|
type PlannerFrameworkComponents = SqlMigrationPlannerPlanOptions extends {
|
|
@@ -47,10 +66,6 @@ type PlannerOptionsWithComponents = SqlMigrationPlannerPlanOptions & {
|
|
|
47
66
|
readonly frameworkComponents: PlannerFrameworkComponents;
|
|
48
67
|
};
|
|
49
68
|
|
|
50
|
-
type VerifySqlSchemaOptionsWithComponents = Parameters<typeof verifySqlSchema>[0] & {
|
|
51
|
-
readonly frameworkComponents: PlannerFrameworkComponents;
|
|
52
|
-
};
|
|
53
|
-
|
|
54
69
|
export function createPostgresMigrationPlanner(
|
|
55
70
|
lowerer: ExecuteRequestLowerer,
|
|
56
71
|
): PostgresMigrationPlanner {
|
|
@@ -75,18 +90,20 @@ export type PostgresPlanResult =
|
|
|
75
90
|
/**
|
|
76
91
|
* Postgres migration planner — a thin wrapper over `planIssues`.
|
|
77
92
|
*
|
|
78
|
-
* `plan()`
|
|
79
|
-
* `
|
|
80
|
-
* `
|
|
81
|
-
* type-change, nullable-tightening, codec-hook
|
|
82
|
-
* component-declared dependency installs, and
|
|
83
|
-
* empty-table-guarded NOT-NULL add-column. The same
|
|
84
|
-
* `migration plan`, `db update`, and `db init`;
|
|
85
|
-
* `policy.allowedOperationClasses` (the
|
|
86
|
-
* when `'data'` is excluded). The issue
|
|
87
|
-
* policy gates and emits a single
|
|
88
|
-
* the runtime-ops view (via
|
|
89
|
-
* authoring surface.
|
|
93
|
+
* `plan()` diffs the target contract against the live schema via the one
|
|
94
|
+
* differ (`buildPostgresPlanDiff`, producing node-typed `SchemaDiffIssue[]`)
|
|
95
|
+
* and delegates to `planIssues` with the unified `postgresPlannerStrategies`
|
|
96
|
+
* list: NOT-NULL backfill, type-change, nullable-tightening, codec-hook
|
|
97
|
+
* storage types, component-declared dependency installs, and
|
|
98
|
+
* shared-temp-default / empty-table-guarded NOT-NULL add-column. The same
|
|
99
|
+
* strategy list runs for `migration plan`, `db update`, and `db init`;
|
|
100
|
+
* behavior diverges purely on `policy.allowedOperationClasses` (the
|
|
101
|
+
* data-safe strategies short-circuit when `'data'` is excluded). The issue
|
|
102
|
+
* planner applies operation-class policy gates and emits a single
|
|
103
|
+
* `PostgresOpFactoryCall[]` that drives both the runtime-ops view (via
|
|
104
|
+
* `renderOps`) and the `renderTypeScript()` authoring surface. RLS policy
|
|
105
|
+
* drift (the structural half of the same one-differ tree) is handled
|
|
106
|
+
* separately via `planPostgresSchemaDiff`.
|
|
90
107
|
*/
|
|
91
108
|
export class PostgresMigrationPlanner implements MigrationPlanner<'sql', 'postgres'> {
|
|
92
109
|
readonly #lowerer: ExecuteRequestLowerer | undefined;
|
|
@@ -122,6 +139,11 @@ export class PostgresMigrationPlanner implements MigrationPlanner<'sql', 'postgr
|
|
|
122
139
|
* the marker row by the right space.
|
|
123
140
|
*/
|
|
124
141
|
readonly spaceId: string;
|
|
142
|
+
/**
|
|
143
|
+
* Ownership oracle over the contract-space composition — see
|
|
144
|
+
* {@link SqlMigrationPlannerPlanOptions.ownership}.
|
|
145
|
+
*/
|
|
146
|
+
readonly ownership?: SchemaOwnership;
|
|
125
147
|
}): PostgresPlanResult {
|
|
126
148
|
return this.planSql(options as SqlMigrationPlannerPlanOptions);
|
|
127
149
|
}
|
|
@@ -151,9 +173,68 @@ export class PostgresMigrationPlanner implements MigrationPlanner<'sql', 'postgr
|
|
|
151
173
|
return policyResult;
|
|
152
174
|
}
|
|
153
175
|
|
|
154
|
-
|
|
176
|
+
// The one combined tree diff drives the whole plan: relational findings
|
|
177
|
+
// become structural DDL via `planIssues`, policy findings become RLS ops
|
|
178
|
+
// via `planPostgresSchemaDiff`. Verify runs its own full-tree node diff
|
|
179
|
+
// (`diffSchema`) over the same schema and rejects on a
|
|
180
|
+
// surviving failure.
|
|
181
|
+
PostgresDatabaseSchemaNode.assert(options.schema);
|
|
182
|
+
const { issues: rawIssues } = buildPostgresPlanDiff({
|
|
183
|
+
contract: options.contract,
|
|
184
|
+
actualSchema: options.schema,
|
|
185
|
+
frameworkComponents: options.frameworkComponents,
|
|
186
|
+
});
|
|
187
|
+
const policyDiffIssues = rawIssues.filter((issue) => isPolicyDiffIssue(issue));
|
|
188
|
+
const relationalDiffIssues = rawIssues.filter((issue) => !isPolicyDiffIssue(issue));
|
|
189
|
+
|
|
190
|
+
// The generic differ is total and un-gated: strict-mode extras filtering
|
|
191
|
+
// (dropping `not-expected` findings outside strict mode, mirroring the
|
|
192
|
+
// retired coordinate walk's `if (strict) { ...extra_* } }` guards),
|
|
193
|
+
// subtree coalescing (a missing/extra table also emits an issue for
|
|
194
|
+
// every child under it — redundant once the table-level Create/Drop call
|
|
195
|
+
// already accounts for the whole subtree), and ownership are all post-diff
|
|
196
|
+
// planner steps.
|
|
197
|
+
//
|
|
198
|
+
// Ownership: a live extra is only this plan's to drop when no contract
|
|
199
|
+
// space owns it. The differ ran against THIS space's contract, so a table
|
|
200
|
+
// a sibling space owns surfaces here as `not-expected`; the planner asks
|
|
201
|
+
// the ownership oracle (the passive aggregate) whether any space declares
|
|
202
|
+
// it and, if so, leaves it alone — it is a sibling's table, not an orphan.
|
|
203
|
+
// A table no space owns stays a genuine extra to drop under a destructive
|
|
204
|
+
// policy. Ownership lives in the aggregate; the planner only asks. Absent
|
|
205
|
+
// oracle (single-space, none handed) keeps every extra. Coalescing MUST
|
|
206
|
+
// run before this so a sibling-owned table's child issues have already
|
|
207
|
+
// collapsed into the one table-level issue that carries the table name.
|
|
208
|
+
const strict =
|
|
209
|
+
options.policy.allowedOperationClasses.includes('widening') ||
|
|
210
|
+
options.policy.allowedOperationClasses.includes('destructive');
|
|
211
|
+
const coalesced = coalesceSubtreeIssues(relationalDiffIssues);
|
|
212
|
+
const owned = retainUnownedExtras(coalesced, options.ownership, options.contract);
|
|
213
|
+
const gated = strict ? owned : owned.filter((issue) => issue.reason !== 'not-expected');
|
|
214
|
+
|
|
215
|
+
// Namespace presence (`CREATE SCHEMA`) is a planner-only op-generation
|
|
216
|
+
// concern stitched in here rather than inside the shared diff — verify
|
|
217
|
+
// never needs it (a missing schema already surfaces as a `not-found`
|
|
218
|
+
// table in the relational findings). These synthesized issues are added
|
|
219
|
+
// AFTER coalescing/scoping (never coalesced against the table diff —
|
|
220
|
+
// their path is an ancestor of every table path under that schema, so
|
|
221
|
+
// running them through the same coalesce would swallow the table-level
|
|
222
|
+
// `not-found` issues that drive `CREATE TABLE`) and are NOT subject to
|
|
223
|
+
// sibling-space scoping, matching the retired coordinate walk exactly.
|
|
224
|
+
const namespaceIssues = verifyPostgresNamespacePresence({
|
|
225
|
+
contract: options.contract,
|
|
226
|
+
schema: options.schema,
|
|
227
|
+
});
|
|
228
|
+
const schemaIssues = [...namespaceIssues, ...gated];
|
|
229
|
+
|
|
155
230
|
const codecHooks = extractCodecControlHooks(options.frameworkComponents);
|
|
156
231
|
const storageTypes = options.contract.storage.types ?? {};
|
|
232
|
+
// The strategy layer reads the live schema by bare table name for existence
|
|
233
|
+
// checks (shared-temp-default safety, FK/unique probes), so it takes one
|
|
234
|
+
// per-schema namespace node — never the whole tree root, and never a flat
|
|
235
|
+
// merge of every namespace (which would collide same-named tables across
|
|
236
|
+
// schemas). Probing more than one namespace at once is future work.
|
|
237
|
+
const relationalSchema = relationalNamespaceNode(options.schema, schemaName);
|
|
157
238
|
|
|
158
239
|
// Input-side control-policy partition. `external` / `observed` subjects
|
|
159
240
|
// — and non-creation issues for `tolerated` subjects — are dropped from
|
|
@@ -166,8 +247,8 @@ export class PostgresMigrationPlanner implements MigrationPlanner<'sql', 'postgr
|
|
|
166
247
|
issues: schemaIssues,
|
|
167
248
|
contract: options.contract,
|
|
168
249
|
resolveControlPolicySubject: (issue) =>
|
|
169
|
-
|
|
170
|
-
resolveCreationFactoryName:
|
|
250
|
+
resolvePostgresNodeIssueControlPolicySubject(issue, options.contract),
|
|
251
|
+
resolveCreationFactoryName: resolvePostgresNodeIssueCreationFactoryName,
|
|
171
252
|
formatSubjectLabel: (factoryName, subject) =>
|
|
172
253
|
formatPostgresControlPolicySubjectLabel(factoryName, subject, options.contract),
|
|
173
254
|
});
|
|
@@ -179,12 +260,12 @@ export class PostgresMigrationPlanner implements MigrationPlanner<'sql', 'postgr
|
|
|
179
260
|
// `db update` / `db init`, which means data-safety strategies needing
|
|
180
261
|
// from/to comparisons (unsafe type change, nullable tightening) are
|
|
181
262
|
// inapplicable there — reconciliation falls through to
|
|
182
|
-
// `
|
|
263
|
+
// `mapNodeIssueToCall`'s direct destructive handlers.
|
|
183
264
|
fromContract: options.fromContract,
|
|
184
265
|
schemaName,
|
|
185
266
|
codecHooks,
|
|
186
267
|
storageTypes,
|
|
187
|
-
schema
|
|
268
|
+
...ifDefined('schema', relationalSchema),
|
|
188
269
|
policy: options.policy,
|
|
189
270
|
frameworkComponents: options.frameworkComponents,
|
|
190
271
|
strategies: postgresPlannerStrategies,
|
|
@@ -194,6 +275,17 @@ export class PostgresMigrationPlanner implements MigrationPlanner<'sql', 'postgr
|
|
|
194
275
|
return plannerFailure(result.failure);
|
|
195
276
|
}
|
|
196
277
|
|
|
278
|
+
const schemaDiffCalls = this.planPostgresSchemaDiff(options, policyDiffIssues);
|
|
279
|
+
const schemaDiffPartition = partitionCallsByControlPolicy({
|
|
280
|
+
calls: schemaDiffCalls,
|
|
281
|
+
contract: options.contract,
|
|
282
|
+
resolveControlPolicySubject: (call) =>
|
|
283
|
+
resolvePostgresCallControlPolicySubject(call, options.contract),
|
|
284
|
+
resolveFactoryName: (call) => call.factoryName,
|
|
285
|
+
formatSubjectLabel: (factoryName, subject) =>
|
|
286
|
+
formatPostgresControlPolicySubjectLabel(factoryName, subject, options.contract),
|
|
287
|
+
});
|
|
288
|
+
|
|
197
289
|
// Inline `onFieldEvent`-emitted ops after structural DDL. The fixed
|
|
198
290
|
// ordering is `structural → added → dropped → altered`, with
|
|
199
291
|
// within-group sorting by `(tableName, fieldName)` so re-emits are
|
|
@@ -219,9 +311,10 @@ export class PostgresMigrationPlanner implements MigrationPlanner<'sql', 'postgr
|
|
|
219
311
|
formatSubjectLabel: (factoryName, subject) =>
|
|
220
312
|
formatPostgresControlPolicySubjectLabel(factoryName, subject, options.contract),
|
|
221
313
|
});
|
|
222
|
-
const calls = [...result.value.calls, ...fieldEventPartition.kept];
|
|
314
|
+
const calls = [...result.value.calls, ...schemaDiffPartition.kept, ...fieldEventPartition.kept];
|
|
223
315
|
const warnings: SqlPlannerConflict[] = [
|
|
224
316
|
...issuePartition.warnings,
|
|
317
|
+
...schemaDiffPartition.warnings,
|
|
225
318
|
...fieldEventPartition.warnings,
|
|
226
319
|
];
|
|
227
320
|
|
|
@@ -240,6 +333,59 @@ export class PostgresMigrationPlanner implements MigrationPlanner<'sql', 'postgr
|
|
|
240
333
|
});
|
|
241
334
|
}
|
|
242
335
|
|
|
336
|
+
/**
|
|
337
|
+
* Maps the RLS policy presence findings of the one combined tree diff
|
|
338
|
+
* (`buildPostgresPlanDiff`, already ownership-filtered) into `ENABLE RLS`
|
|
339
|
+
* / `CREATE POLICY` / `DROP POLICY` ops. It does not re-diff — it consumes
|
|
340
|
+
* exactly the policy-node subset of the shared diff's issues.
|
|
341
|
+
*/
|
|
342
|
+
private planPostgresSchemaDiff(
|
|
343
|
+
options: PlannerOptionsWithComponents,
|
|
344
|
+
filteredDiffIssues: readonly SchemaDiffIssue<SqlSchemaDiffNode>[],
|
|
345
|
+
): readonly PostgresOpFactoryCall[] {
|
|
346
|
+
const allowsDestructive = options.policy.allowedOperationClasses.includes('destructive');
|
|
347
|
+
const calls: PostgresOpFactoryCall[] = [];
|
|
348
|
+
const seenEnableTables = new Set<string>();
|
|
349
|
+
|
|
350
|
+
for (const issue of filteredDiffIssues) {
|
|
351
|
+
// 'not-equal' is unreachable for content-addressed policies: the wire name
|
|
352
|
+
// encodes the body hash, so two policies sharing a local key (same name)
|
|
353
|
+
// are always equal and isEqualTo never returns false.
|
|
354
|
+
if (issue.reason === 'not-found') {
|
|
355
|
+
const expected = issue.expected;
|
|
356
|
+
PostgresPolicySchemaNode.assert(expected);
|
|
357
|
+
// expected.namespaceId is the DDL schema name (resolved during projection);
|
|
358
|
+
// this re-resolution is a no-op as long as PostgresSchema.ddlSchemaName() returns this.id.
|
|
359
|
+
const schemaForTable = resolveDdlSchemaForNamespaceStorage(
|
|
360
|
+
options.contract.storage,
|
|
361
|
+
expected.namespaceId,
|
|
362
|
+
);
|
|
363
|
+
const tableKey = `${schemaForTable}.${expected.tableName}`;
|
|
364
|
+
if (!seenEnableTables.has(tableKey)) {
|
|
365
|
+
seenEnableTables.add(tableKey);
|
|
366
|
+
calls.push(new EnableRowLevelSecurityCall(schemaForTable, expected.tableName));
|
|
367
|
+
}
|
|
368
|
+
calls.push(
|
|
369
|
+
new CreatePostgresRlsPolicyCall(
|
|
370
|
+
schemaForTable,
|
|
371
|
+
expected.tableName,
|
|
372
|
+
policyNodeToContractPolicy(expected),
|
|
373
|
+
),
|
|
374
|
+
);
|
|
375
|
+
} else if (issue.reason === 'not-expected' && allowsDestructive) {
|
|
376
|
+
const actual = issue.actual;
|
|
377
|
+
PostgresPolicySchemaNode.assert(actual);
|
|
378
|
+
const schemaForTable = resolveDdlSchemaForNamespaceStorage(
|
|
379
|
+
options.contract.storage,
|
|
380
|
+
actual.namespaceId,
|
|
381
|
+
);
|
|
382
|
+
calls.push(new DropPostgresRlsPolicyCall(schemaForTable, actual.tableName, actual.name));
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
return calls;
|
|
387
|
+
}
|
|
388
|
+
|
|
243
389
|
private ensureAdditivePolicy(policy: MigrationOperationPolicy) {
|
|
244
390
|
if (!policy.allowedOperationClasses.includes('additive')) {
|
|
245
391
|
return plannerFailure([
|
|
@@ -252,35 +398,127 @@ export class PostgresMigrationPlanner implements MigrationPlanner<'sql', 'postgr
|
|
|
252
398
|
}
|
|
253
399
|
return null;
|
|
254
400
|
}
|
|
401
|
+
}
|
|
255
402
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
403
|
+
/**
|
|
404
|
+
* A diff issue whose node is an RLS policy — the structural half of the one
|
|
405
|
+
* combined tree diff, routed to `planPostgresSchemaDiff` instead of
|
|
406
|
+
* `planIssues`.
|
|
407
|
+
*/
|
|
408
|
+
function isPolicyDiffIssue(issue: SchemaDiffIssue<SqlSchemaDiffNode>): boolean {
|
|
409
|
+
const node = issue.expected ?? issue.actual;
|
|
410
|
+
return node !== undefined && PostgresPolicySchemaNode.is(node);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Drops a `not-expected` issue when it is a whole extra TABLE that some
|
|
415
|
+
* contract space owns, asking the ownership oracle per node.
|
|
416
|
+
*
|
|
417
|
+
* The consultation applies ONLY to table-level extras — a live table this
|
|
418
|
+
* space's contract lacks — identified by asking the issue's own node
|
|
419
|
+
* (`nodeKind === table`), never by counting path segments. `declaresEntity`
|
|
420
|
+
* answers over the whole composition (self included), keyed on the schema-IR
|
|
421
|
+
* entity coordinate so a genuine orphan in one namespace is never conflated
|
|
422
|
+
* with a same-named table a sibling space declares in another, or with a
|
|
423
|
+
* same-named non-table entity (an enum, an RLS policy) a sibling declares in
|
|
424
|
+
* the SAME namespace: a positive answer means another space owns THIS table
|
|
425
|
+
* in THIS namespace (a table this space owned would be in its expected tree,
|
|
426
|
+
* never an extra) — leave it. A negative answer means no space owns it — a
|
|
427
|
+
* genuine orphan to drop. `entityKind` is the literal `'table'` here: this
|
|
428
|
+
* function only ever asks about a node already confirmed to be
|
|
429
|
+
* `PostgresSchemaNodeKind.table` (checked just above), and the diff tree's
|
|
430
|
+
* `nodeKind` vocabulary (`'postgres-table'`) is distinct from the storage
|
|
431
|
+
* `entries` vocabulary `elementCoordinates` walks (`'table'`) — the literal
|
|
432
|
+
* is that storage-entries spelling, not the node kind.
|
|
433
|
+
*
|
|
434
|
+
* The issue path carries the *resolved DDL schema* (e.g. `public`), but a
|
|
435
|
+
* contract space's own declared namespace id can be the unbound sentinel
|
|
436
|
+
* (`__unbound__`) that resolves to that schema — the two spellings would
|
|
437
|
+
* never string-match. `resolveNamespaceIdForDdlSchema` recovers the raw
|
|
438
|
+
* namespace id THIS space's own contract would use for that DDL schema, so
|
|
439
|
+
* a table this space's own unbound namespace declares (and any sibling
|
|
440
|
+
* whose own unbound namespace resolves the same way) is compared on the
|
|
441
|
+
* same coordinate the aggregate's `elementCoordinates` walk yields.
|
|
442
|
+
*
|
|
443
|
+
* A DEEPER extra (an extra column/constraint on a table this space DOES own —
|
|
444
|
+
* only the child drifted, so the table is in the expected tree) is this
|
|
445
|
+
* space's own drift and is always kept for dropping; asking the oracle there
|
|
446
|
+
* would wrongly suppress it, because the owned table answers `true`. Shallower
|
|
447
|
+
* issues (namespace/root) are never ownership-scoped here. No oracle ⇒ every
|
|
448
|
+
* extra is kept (single-space plan).
|
|
449
|
+
*/
|
|
450
|
+
function retainUnownedExtras(
|
|
451
|
+
issues: readonly SchemaDiffIssue<SqlSchemaDiffNode>[],
|
|
452
|
+
ownership: SchemaOwnership | undefined,
|
|
453
|
+
contract: Contract<SqlStorage>,
|
|
454
|
+
): readonly SchemaDiffIssue<SqlSchemaDiffNode>[] {
|
|
455
|
+
if (ownership === undefined) return issues;
|
|
456
|
+
return issues.filter((issue) => {
|
|
457
|
+
if (issue.reason !== 'not-expected') return true;
|
|
458
|
+
const node = issueNode(issue);
|
|
459
|
+
if (node === undefined || node.nodeKind !== PostgresSchemaNodeKind.table) return true;
|
|
460
|
+
const ddlSchemaName = issueSchemaName(issue);
|
|
461
|
+
const tableName = issueTableName(issue);
|
|
462
|
+
if (ddlSchemaName === undefined || tableName === undefined) return true;
|
|
463
|
+
const namespaceId = resolveNamespaceIdForDdlSchema(contract, ddlSchemaName);
|
|
464
|
+
return !ownership.declaresEntity({ namespaceId, entityKind: 'table', entityName: tableName });
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* Returns the one namespace node the relational strategy layer probes for
|
|
470
|
+
* live-table existence and reads codec-hook context off — the namespace
|
|
471
|
+
* matching the planner's resolved schema name, or the first namespace when
|
|
472
|
+
* none matches. `undefined` when the tree has no namespaces, so the strategy
|
|
473
|
+
* context uses its empty-schema default.
|
|
474
|
+
*
|
|
475
|
+
* The relational strategies key tables by bare name, so they can only probe one
|
|
476
|
+
* namespace at a time; probing across every namespace at once is future work.
|
|
477
|
+
*
|
|
478
|
+
* Returns the real `PostgresNamespaceSchemaNode` reference rather than a
|
|
479
|
+
* projection: `storageTypePlanCallStrategy` hands this same value to codec
|
|
480
|
+
* `planTypeOperations` hooks as `schema`, and hooks read the Postgres-specific
|
|
481
|
+
* `nativeEnumTypeNames` field off it (via `PostgresNamespaceSchemaNode.is`) to
|
|
482
|
+
* decide whether a native enum type already exists — a projection that only
|
|
483
|
+
* copies `tables` would silently drop that signal. `StrategyContext.schema`'s
|
|
484
|
+
* declared type (`SqlSchemaIR`, shared with SQLite's flat shape) doesn't
|
|
485
|
+
* capture this, so the return is `blindCast` the same way `namespaceSchemaNodes`
|
|
486
|
+
* in the family's relational walk already treats a namespace node as a
|
|
487
|
+
* structurally-`SqlSchemaIR`-shaped value.
|
|
488
|
+
*/
|
|
489
|
+
function relationalNamespaceNode(
|
|
490
|
+
schema: PostgresDatabaseSchemaNode,
|
|
491
|
+
schemaName: string,
|
|
492
|
+
): SqlSchemaIR | undefined {
|
|
493
|
+
const namespaceNodes = Object.values(schema.namespaces);
|
|
494
|
+
const namespaceNode =
|
|
495
|
+
namespaceNodes.find((node) => node.schemaName === schemaName) ?? namespaceNodes[0];
|
|
496
|
+
if (namespaceNode === undefined) return undefined;
|
|
497
|
+
return blindCast<
|
|
498
|
+
SqlSchemaIR,
|
|
499
|
+
'PostgresNamespaceSchemaNode carries tables (+ nativeEnumTypeNames, read by codec hooks via PostgresNamespaceSchemaNode.is) structurally compatible with the SqlSchemaIR shape the strategy layer declares'
|
|
500
|
+
>(namespaceNode);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* Rebuilds the `PostgresRlsPolicy` contract entity `CreatePostgresRlsPolicyCall`
|
|
505
|
+
* carries (its `renderTypeScript`/`createRlsPolicy` paths serialize the whole
|
|
506
|
+
* entity, `namespaceId` included). This reconstructs rather than looking the
|
|
507
|
+
* original up in the contract on purpose: the diff node's `namespaceId` is the
|
|
508
|
+
* *resolved DDL schema* (set when the expected tree was built), which is the
|
|
509
|
+
* value the emitted op must carry; the contract-stored entity holds the raw,
|
|
510
|
+
* pre-resolution coordinate, so a lookup would change the migration output.
|
|
511
|
+
*/
|
|
512
|
+
function policyNodeToContractPolicy(node: PostgresPolicySchemaNode): PostgresRlsPolicy {
|
|
513
|
+
return new PostgresRlsPolicy({
|
|
514
|
+
name: node.name,
|
|
515
|
+
prefix: node.prefix,
|
|
516
|
+
tableName: node.tableName,
|
|
517
|
+
namespaceId: node.namespaceId,
|
|
518
|
+
operation: node.operation,
|
|
519
|
+
roles: [...node.roles],
|
|
520
|
+
...ifDefined('using', node.using),
|
|
521
|
+
...ifDefined('withCheck', node.withCheck),
|
|
522
|
+
permissive: node.permissive,
|
|
523
|
+
});
|
|
286
524
|
}
|
|
@@ -3,9 +3,11 @@ import type { SqlMigrationPlanOperation } from '@prisma-next/family-sql/control'
|
|
|
3
3
|
import type { SqlControlAdapter } from '@prisma-next/family-sql/control-adapter';
|
|
4
4
|
import { Migration as SqlMigration } from '@prisma-next/family-sql/migration';
|
|
5
5
|
import type { ControlStack } from '@prisma-next/framework-components/control';
|
|
6
|
+
import { MigrationContractViews } from '@prisma-next/migration-tools/migration';
|
|
6
7
|
import type { SqlStorage } from '@prisma-next/sql-contract/types';
|
|
7
8
|
import type { DdlColumn, DdlTableConstraint } from '@prisma-next/sql-relational-core/ast';
|
|
8
9
|
import { errorPostgresMigrationStackMissing } from '../errors';
|
|
10
|
+
import { PostgresContractView } from '../postgres-contract-view';
|
|
9
11
|
import {
|
|
10
12
|
AddCheckConstraintCall,
|
|
11
13
|
AddColumnCall,
|
|
@@ -60,10 +62,10 @@ import type { PostgresPlanTargetDetails } from './planner-target-details';
|
|
|
60
62
|
* is an antipattern in a migration. (The unbound/unspecified namespace concept
|
|
61
63
|
* remains for SQLite, which has no schemas, and for Mongo's connection `db`.)
|
|
62
64
|
*/
|
|
63
|
-
export abstract class PostgresMigration
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
> {
|
|
65
|
+
export abstract class PostgresMigration<
|
|
66
|
+
Start extends Contract<SqlStorage> = Contract<SqlStorage>,
|
|
67
|
+
End extends Contract<SqlStorage> = Contract<SqlStorage>,
|
|
68
|
+
> extends SqlMigration<PostgresPlanTargetDetails, 'postgres', Start, End> {
|
|
67
69
|
readonly targetId = 'postgres' as const;
|
|
68
70
|
|
|
69
71
|
/**
|
|
@@ -74,6 +76,17 @@ export abstract class PostgresMigration extends SqlMigration<
|
|
|
74
76
|
*/
|
|
75
77
|
protected readonly controlAdapter: SqlControlAdapter<'postgres'> | undefined;
|
|
76
78
|
|
|
79
|
+
#endView = new MigrationContractViews<PostgresContractView<End>>(
|
|
80
|
+
this,
|
|
81
|
+
'PostgresMigration',
|
|
82
|
+
(json) => PostgresContractView.fromJson<End>(json),
|
|
83
|
+
);
|
|
84
|
+
#startView = new MigrationContractViews<PostgresContractView<Start>>(
|
|
85
|
+
this,
|
|
86
|
+
'PostgresMigration',
|
|
87
|
+
(json) => PostgresContractView.fromJson<Start>(json),
|
|
88
|
+
);
|
|
89
|
+
|
|
77
90
|
constructor(stack?: ControlStack<'sql', 'postgres'>) {
|
|
78
91
|
super(stack);
|
|
79
92
|
// The descriptor `create()` is typed as the wider `ControlAdapterInstance`;
|
|
@@ -84,6 +97,23 @@ export abstract class PostgresMigration extends SqlMigration<
|
|
|
84
97
|
: undefined;
|
|
85
98
|
}
|
|
86
99
|
|
|
100
|
+
/**
|
|
101
|
+
* The typed, schema-qualified Postgres view over this migration's end-state
|
|
102
|
+
* contract — `this.endContract.namespace.<schema>.table.<name>`, etc. Throws
|
|
103
|
+
* if no `endContractJson` was provided.
|
|
104
|
+
*/
|
|
105
|
+
get endContract(): PostgresContractView<End> {
|
|
106
|
+
return this.#endView.endContract;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* The typed Postgres view over this migration's start-state contract, or
|
|
111
|
+
* `null` for a baseline migration (no `startContractJson`).
|
|
112
|
+
*/
|
|
113
|
+
get startContract(): PostgresContractView<Start> | null {
|
|
114
|
+
return this.#startView.startContract;
|
|
115
|
+
}
|
|
116
|
+
|
|
87
117
|
/**
|
|
88
118
|
* Instance-method wrapper around the free `dataTransform` factory that
|
|
89
119
|
* supplies the stored control adapter. Authors call this from inside
|
|
@@ -45,15 +45,19 @@ export function renderCallsToTypeScript(
|
|
|
45
45
|
calls: ReadonlyArray<OpFactoryCall>,
|
|
46
46
|
meta: RenderMigrationMeta,
|
|
47
47
|
): string {
|
|
48
|
-
const imports = buildImports(calls);
|
|
48
|
+
const imports = buildImports(calls, meta);
|
|
49
49
|
const operationsBody = calls.map((c) => renderCall(c)).join(',\n');
|
|
50
|
+
const hasStart = meta.from !== null;
|
|
51
|
+
const startField = hasStart ? [' override readonly startContractJson = startContract;'] : [];
|
|
50
52
|
|
|
51
53
|
return [
|
|
52
54
|
shebangLineFor(detectScaffoldRuntime()),
|
|
53
55
|
imports,
|
|
54
56
|
'',
|
|
55
|
-
|
|
56
|
-
|
|
57
|
+
`export default class M extends Migration<${hasStart ? 'Start' : 'never'}, End> {`,
|
|
58
|
+
...startField,
|
|
59
|
+
' override readonly endContractJson = endContract;',
|
|
60
|
+
'',
|
|
57
61
|
' override get operations() {',
|
|
58
62
|
' return [',
|
|
59
63
|
indent(operationsBody, 6),
|
|
@@ -70,8 +74,8 @@ function renderCall(call: OpFactoryCall): string {
|
|
|
70
74
|
return call.renderTypeScript();
|
|
71
75
|
}
|
|
72
76
|
|
|
73
|
-
function buildImports(calls: ReadonlyArray<OpFactoryCall
|
|
74
|
-
const requirements: ImportRequirement[] = [...BASE_IMPORTS];
|
|
77
|
+
function buildImports(calls: ReadonlyArray<OpFactoryCall>, meta: RenderMigrationMeta): string {
|
|
78
|
+
const requirements: ImportRequirement[] = [...BASE_IMPORTS, ...contractImports(meta)];
|
|
75
79
|
for (const call of calls) {
|
|
76
80
|
for (const req of call.importRequirements()) {
|
|
77
81
|
requirements.push(req);
|
|
@@ -80,16 +84,39 @@ function buildImports(calls: ReadonlyArray<OpFactoryCall>): string {
|
|
|
80
84
|
return renderImports(requirements);
|
|
81
85
|
}
|
|
82
86
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
87
|
+
/**
|
|
88
|
+
* The committed contract-JSON imports the scaffold reads its from/to identity
|
|
89
|
+
* from. `end-contract.json` is always present; `start-contract.json` is added
|
|
90
|
+
* only for a non-baseline migration (`meta.from !== null`). The matching
|
|
91
|
+
* `Contract` type imports (aliased `Start`/`End`) feed the
|
|
92
|
+
* `Migration<Start, End>` generics. Baseline emits `Migration<never, End>` with
|
|
93
|
+
* no start imports — `never` is the honest "no prior contract" Start.
|
|
94
|
+
*/
|
|
95
|
+
function contractImports(meta: RenderMigrationMeta): readonly ImportRequirement[] {
|
|
96
|
+
const reqs: ImportRequirement[] = [
|
|
97
|
+
{
|
|
98
|
+
moduleSpecifier: './end-contract.json',
|
|
99
|
+
symbol: 'endContract',
|
|
100
|
+
kind: 'default',
|
|
101
|
+
attributes: { type: 'json' },
|
|
102
|
+
},
|
|
103
|
+
{ moduleSpecifier: './end-contract', symbol: 'Contract', alias: 'End', typeOnly: true },
|
|
104
|
+
];
|
|
105
|
+
if (meta.from !== null) {
|
|
106
|
+
reqs.push({
|
|
107
|
+
moduleSpecifier: './start-contract.json',
|
|
108
|
+
symbol: 'startContract',
|
|
109
|
+
kind: 'default',
|
|
110
|
+
attributes: { type: 'json' },
|
|
111
|
+
});
|
|
112
|
+
reqs.push({
|
|
113
|
+
moduleSpecifier: './start-contract',
|
|
114
|
+
symbol: 'Contract',
|
|
115
|
+
alias: 'Start',
|
|
116
|
+
typeOnly: true,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
return reqs;
|
|
93
120
|
}
|
|
94
121
|
|
|
95
122
|
function indent(text: string, spaces: number): string {
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir';
|
|
2
|
+
import type { SqlStorage } from '@prisma-next/sql-contract/types';
|
|
3
|
+
import { DEFAULT_NAMESPACE_ID } from '../namespace-ids';
|
|
4
|
+
import { isPostgresSchema } from '../postgres-schema';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Given the contract's storage and a namespace id, returns the live Postgres
|
|
8
|
+
* DDL schema name that namespace maps to. A named Postgres namespace dispatches
|
|
9
|
+
* to its `ddlSchemaName(storage)`; the unbound sentinel resolves to
|
|
10
|
+
* `DEFAULT_NAMESPACE_ID` (the search-path default for offline planning); a
|
|
11
|
+
* bare object payload (used by some tests) falls back to the namespace id
|
|
12
|
+
* itself.
|
|
13
|
+
*/
|
|
14
|
+
export function resolveDdlSchemaForNamespaceStorage(
|
|
15
|
+
storage: SqlStorage,
|
|
16
|
+
namespaceId: string,
|
|
17
|
+
): string {
|
|
18
|
+
if (namespaceId === UNBOUND_NAMESPACE_ID) {
|
|
19
|
+
return DEFAULT_NAMESPACE_ID;
|
|
20
|
+
}
|
|
21
|
+
const namespace = storage.namespaces[namespaceId];
|
|
22
|
+
if (namespace && isPostgresSchema(namespace)) {
|
|
23
|
+
return namespace.ddlSchemaName(storage);
|
|
24
|
+
}
|
|
25
|
+
return namespaceId;
|
|
26
|
+
}
|