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