@prisma-next/target-postgres 0.14.0-dev.60 → 0.14.0-dev.62
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{authoring-WkqG_MkU.mjs → authoring-CdIZ0yvD.mjs} +147 -38
- package/dist/authoring-CdIZ0yvD.mjs.map +1 -0
- package/dist/contract-free.d.mts +1 -1
- package/dist/contract-free.d.mts.map +1 -1
- package/dist/contract-free.mjs +2 -2
- package/dist/{control-policy-Co_13Hxa.mjs → control-policy-DKp0HKpY.mjs} +203 -45
- package/dist/control-policy-DKp0HKpY.mjs.map +1 -0
- package/dist/control.mjs +22 -25
- package/dist/control.mjs.map +1 -1
- package/dist/{ddl-B-krEXY1.mjs → ddl-gmxHf68z.mjs} +20 -4
- package/dist/{ddl-B-krEXY1.mjs.map → ddl-gmxHf68z.mjs.map} +1 -1
- package/dist/ddl.d.mts +2 -2
- package/dist/ddl.mjs +2 -2
- package/dist/{descriptor-meta-BOcKPaLE.mjs → descriptor-meta-DEuXJfpR.mjs} +3 -3
- package/dist/{descriptor-meta-BOcKPaLE.mjs.map → descriptor-meta-DEuXJfpR.mjs.map} +1 -1
- package/dist/{diff-database-schema-BOsDnH64.mjs → diff-database-schema-D2sjvMw9.mjs} +55 -19
- package/dist/diff-database-schema-D2sjvMw9.mjs.map +1 -0
- package/dist/diff-database-schema.d.mts +4 -4
- package/dist/diff-database-schema.d.mts.map +1 -1
- package/dist/diff-database-schema.mjs +1 -1
- package/dist/issue-planner.d.mts +1 -1
- package/dist/issue-planner.d.mts.map +1 -1
- package/dist/issue-planner.mjs +1 -1
- package/dist/migration.d.mts +2 -2
- package/dist/migration.mjs +2 -2
- package/dist/{nodes-BtQyZha6.d.mts → nodes-Bc2N3tMy.d.mts} +27 -3
- package/dist/nodes-Bc2N3tMy.d.mts.map +1 -0
- package/dist/{nodes-B815pj_X.mjs → nodes-DJC5B8Id.mjs} +32 -2
- package/dist/nodes-DJC5B8Id.mjs.map +1 -0
- package/dist/{op-factory-call-DxNNKUeZ.mjs → op-factory-call-C4flTtTG.mjs} +105 -6
- package/dist/op-factory-call-C4flTtTG.mjs.map +1 -0
- package/dist/{op-factory-call-D6_7-7Vc.d.mts → op-factory-call-CSIg5XDu.d.mts} +26 -3
- package/dist/{op-factory-call-D6_7-7Vc.d.mts.map → op-factory-call-CSIg5XDu.d.mts.map} +1 -1
- package/dist/op-factory-call.d.mts +2 -2
- package/dist/op-factory-call.mjs +2 -2
- package/dist/pack.d.mts +146 -3
- package/dist/pack.d.mts.map +1 -1
- package/dist/pack.mjs +1 -1
- package/dist/{planner-CNrc1Odu.mjs → planner-BtEd5LMr.mjs} +32 -64
- package/dist/planner-BtEd5LMr.mjs.map +1 -0
- package/dist/{planner-ddl-builders-B8Nn6nHN.mjs → planner-ddl-builders-BKlmGDJl.mjs} +7 -3
- package/dist/planner-ddl-builders-BKlmGDJl.mjs.map +1 -0
- package/dist/planner-ddl-builders.d.mts.map +1 -1
- package/dist/planner-ddl-builders.mjs +1 -1
- package/dist/{planner-produced-postgres-migration-a-tjfKsA.d.mts → planner-produced-postgres-migration-CXue-6M9.d.mts} +2 -2
- package/dist/{planner-produced-postgres-migration-a-tjfKsA.d.mts.map → planner-produced-postgres-migration-CXue-6M9.d.mts.map} +1 -1
- package/dist/{planner-produced-postgres-migration-NI-oGKcw.mjs → planner-produced-postgres-migration-Jat3hg4u.mjs} +2 -2
- package/dist/{planner-produced-postgres-migration-NI-oGKcw.mjs.map → planner-produced-postgres-migration-Jat3hg4u.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-AXoxB1Jl.mjs → planner-sql-checks-Br1c_QsH.mjs} +3 -3
- package/dist/{planner-sql-checks-AXoxB1Jl.mjs.map → planner-sql-checks-Br1c_QsH.mjs.map} +1 -1
- package/dist/planner-sql-checks.mjs +1 -1
- package/dist/planner.d.mts +3 -3
- package/dist/planner.d.mts.map +1 -1
- package/dist/planner.mjs +2 -2
- package/dist/{postgres-contract-view-Dg-N7KA7.mjs → postgres-contract-view-BAPW0ec1.mjs} +3 -3
- package/dist/{postgres-contract-view-Dg-N7KA7.mjs.map → postgres-contract-view-BAPW0ec1.mjs.map} +1 -1
- package/dist/{postgres-database-schema-node-CfdTY-Qs.d.mts → postgres-database-schema-node-CJv5bfbs.d.mts} +70 -26
- package/dist/postgres-database-schema-node-CJv5bfbs.d.mts.map +1 -0
- package/dist/{postgres-migration-ChyRsWvE.mjs → postgres-migration-ChLMQRd-.mjs} +19 -3
- package/dist/postgres-migration-ChLMQRd-.mjs.map +1 -0
- package/dist/{postgres-migration-Bzzi0Asr.d.mts → postgres-migration-DkmgGNcv.d.mts} +21 -2
- package/dist/postgres-migration-DkmgGNcv.d.mts.map +1 -0
- package/dist/{postgres-role-schema-node-BiSTCTNg.mjs → postgres-role-schema-node-Cm-j0ZGM.mjs} +20 -25
- package/dist/postgres-role-schema-node-Cm-j0ZGM.mjs.map +1 -0
- package/dist/{postgres-schema-DTLA_aRi.mjs → postgres-schema-DWQ0-CPb.mjs} +32 -4
- package/dist/postgres-schema-DWQ0-CPb.mjs.map +1 -0
- package/dist/{postgres-schema-CU4gF6QZ.d.mts → postgres-schema-DsrtYp2v.d.mts} +13 -1
- package/dist/postgres-schema-DsrtYp2v.d.mts.map +1 -0
- package/dist/{postgres-table-schema-node-9FCtCUz2.mjs → postgres-table-schema-node-D6LvInCe.mjs} +71 -8
- package/dist/postgres-table-schema-node-D6LvInCe.mjs.map +1 -0
- package/dist/{resolve-ddl-schema-Ca88Tg6W.mjs → resolve-ddl-schema-CAza65Iy.mjs} +2 -2
- package/dist/{resolve-ddl-schema-Ca88Tg6W.mjs.map → resolve-ddl-schema-CAza65Iy.mjs.map} +1 -1
- package/dist/runtime.mjs +1 -1
- package/dist/schema-ir-annotations.mjs +1 -1
- 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 +3 -3
- package/dist/types.mjs +4 -4
- package/package.json +20 -20
- package/src/contract-free/ddl.ts +27 -0
- package/src/core/authoring.ts +136 -25
- package/src/core/ddl/nodes.ts +44 -0
- package/src/core/migrations/contract-to-postgres-database-schema-node.ts +12 -1
- package/src/core/migrations/control-policy.ts +172 -45
- package/src/core/migrations/diff-database-schema.ts +48 -13
- package/src/core/migrations/issue-planner.ts +77 -4
- package/src/core/migrations/op-factory-call.ts +84 -0
- package/src/core/migrations/operations/native-enum-types.ts +62 -0
- package/src/core/migrations/planner-ddl-builders.ts +12 -2
- package/src/core/migrations/planner.ts +30 -62
- package/src/core/migrations/postgres-migration.ts +31 -0
- package/src/core/migrations/verify-postgres-namespaces.ts +0 -1
- package/src/core/postgres-role.ts +22 -0
- package/src/core/postgres-schema.ts +22 -1
- package/src/core/postgres-validators.ts +1 -0
- package/src/core/psl-infer/infer-psl-contract.ts +7 -16
- package/src/core/schema-ir/node-storage-coordinate.ts +26 -0
- package/src/core/schema-ir/postgres-database-schema-node.ts +5 -4
- package/src/core/schema-ir/postgres-namespace-schema-node.ts +18 -27
- package/src/core/schema-ir/postgres-native-enum-schema-node.ts +83 -0
- package/src/core/schema-ir/postgres-role-schema-node.ts +4 -2
- package/src/core/schema-ir/schema-node-kinds.ts +19 -3
- package/src/core/sql-utils.ts +15 -2
- package/src/exports/ddl.ts +2 -0
- package/src/exports/op-factory-call.ts +2 -0
- package/src/exports/sql-utils.ts +1 -0
- package/src/exports/types.ts +4 -1
- package/dist/authoring-WkqG_MkU.mjs.map +0 -1
- package/dist/control-policy-Co_13Hxa.mjs.map +0 -1
- package/dist/diff-database-schema-BOsDnH64.mjs.map +0 -1
- package/dist/nodes-B815pj_X.mjs.map +0 -1
- package/dist/nodes-BtQyZha6.d.mts.map +0 -1
- package/dist/op-factory-call-DxNNKUeZ.mjs.map +0 -1
- package/dist/planner-CNrc1Odu.mjs.map +0 -1
- package/dist/planner-ddl-builders-B8Nn6nHN.mjs.map +0 -1
- package/dist/postgres-database-schema-node-CfdTY-Qs.d.mts.map +0 -1
- package/dist/postgres-migration-Bzzi0Asr.d.mts.map +0 -1
- package/dist/postgres-migration-ChyRsWvE.mjs.map +0 -1
- package/dist/postgres-role-schema-node-BiSTCTNg.mjs.map +0 -1
- package/dist/postgres-schema-CU4gF6QZ.d.mts.map +0 -1
- package/dist/postgres-schema-DTLA_aRi.mjs.map +0 -1
- package/dist/postgres-table-schema-node-9FCtCUz2.mjs.map +0 -1
|
@@ -1,11 +1,17 @@
|
|
|
1
|
-
import type { Contract } from '@prisma-next/contract/types';
|
|
2
|
-
import type {
|
|
1
|
+
import type { Contract, ControlPolicy } from '@prisma-next/contract/types';
|
|
2
|
+
import type {
|
|
3
|
+
ControlPolicySubject,
|
|
4
|
+
SqlPlannerConflict,
|
|
5
|
+
SuppressionRecord,
|
|
6
|
+
} from '@prisma-next/family-sql/control';
|
|
3
7
|
import type { SchemaDiffIssue } from '@prisma-next/framework-components/control';
|
|
4
8
|
import { entityAt, UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir';
|
|
5
9
|
import type { SqlStorage, StorageTable } from '@prisma-next/sql-contract/types';
|
|
6
10
|
import { blindCast } from '@prisma-next/utils/casts';
|
|
7
11
|
import { ifDefined } from '@prisma-next/utils/defined';
|
|
12
|
+
import { type PostgresRole, ROLE_DEFAULT_CONTROL_POLICY } from '../postgres-role';
|
|
8
13
|
import { isPostgresSchema } from '../postgres-schema';
|
|
14
|
+
import { postgresNodeStorageCoordinate } from '../schema-ir/node-storage-coordinate';
|
|
9
15
|
import type { PostgresNamespaceSchemaNode } from '../schema-ir/postgres-namespace-schema-node';
|
|
10
16
|
import { PostgresTableSchemaNode } from '../schema-ir/postgres-table-schema-node';
|
|
11
17
|
import type { SqlSchemaDiffNode } from '../schema-ir/schema-node-kinds';
|
|
@@ -23,13 +29,16 @@ import type { PostgresOpFactoryCall } from './op-factory-call';
|
|
|
23
29
|
* and is fail-closed. Any call not listed here — including future or
|
|
24
30
|
* extension-contributed factories — is treated as NOT object-creation, so it
|
|
25
31
|
* is suppressed under `tolerated` rather than permissively emitted.
|
|
32
|
+
*
|
|
33
|
+
* Lists the creation factories that actually reach the call-side resolver
|
|
34
|
+
* (`resolvePostgresCallControlPolicySubject`) — today only RLS policy
|
|
35
|
+
* creation, from `planPostgresSchemaDiff`. Every other whole-object create
|
|
36
|
+
* (table, schema, native enum, and RLS enablement) is constructed inside
|
|
37
|
+
* `planIssues` and graded on the node side by
|
|
38
|
+
* `resolvePostgresNodeIssueCreationFactoryName`, so none of them are listed
|
|
39
|
+
* here.
|
|
26
40
|
*/
|
|
27
|
-
const OBJECT_CREATION_FACTORIES: ReadonlySet<string> = new Set<string>([
|
|
28
|
-
'createTable',
|
|
29
|
-
'createSchema',
|
|
30
|
-
'createRlsPolicy',
|
|
31
|
-
'enableRowLevelSecurity',
|
|
32
|
-
]);
|
|
41
|
+
const OBJECT_CREATION_FACTORIES: ReadonlySet<string> = new Set<string>(['createRlsPolicy']);
|
|
33
42
|
|
|
34
43
|
function createsNewTopLevelObject(call: PostgresOpFactoryCall): boolean {
|
|
35
44
|
return OBJECT_CREATION_FACTORIES.has(call.factoryName);
|
|
@@ -40,18 +49,25 @@ function ddlSchemaNameForNamespace(contract: Contract<SqlStorage>, namespaceId:
|
|
|
40
49
|
return isPostgresSchema(namespace) ? namespace.ddlSchemaName(contract.storage) : namespaceId;
|
|
41
50
|
}
|
|
42
51
|
|
|
43
|
-
|
|
52
|
+
/**
|
|
53
|
+
* Resolve the namespace a declared storage entity lives in by walking every
|
|
54
|
+
* namespace for one whose `entries` map actually contains the coordinate —
|
|
55
|
+
* a table by its name, a native enum by its physical type name (see
|
|
56
|
+
* {@link postgresNodeStorageCoordinate}). When `ddlSchemaName` is given, the
|
|
57
|
+
* match must also land in that DDL schema (disambiguates same-named entities
|
|
58
|
+
* declared under different namespaces); when omitted, the first namespace
|
|
59
|
+
* that declares the entity wins. Falls back to {@link UNBOUND_NAMESPACE_ID}
|
|
60
|
+
* when no namespace declares it — e.g. an extra/dropped entity the contract
|
|
61
|
+
* doesn't claim at all.
|
|
62
|
+
*/
|
|
63
|
+
function resolveNamespaceIdForEntity(
|
|
44
64
|
contract: Contract<SqlStorage>,
|
|
45
|
-
|
|
65
|
+
coordinate: { readonly entityKind: string; readonly entityName: string },
|
|
46
66
|
ddlSchemaName: string | undefined,
|
|
47
67
|
): string {
|
|
48
68
|
for (const namespaceId of Object.keys(contract.storage.namespaces)) {
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
entityKind: 'table',
|
|
52
|
-
entityName: tableName,
|
|
53
|
-
});
|
|
54
|
-
if (!table) continue;
|
|
69
|
+
const entity = entityAt(contract.storage, { namespaceId, ...coordinate });
|
|
70
|
+
if (!entity) continue;
|
|
55
71
|
if (
|
|
56
72
|
ddlSchemaName === undefined ||
|
|
57
73
|
ddlSchemaNameForNamespace(contract, namespaceId) === ddlSchemaName
|
|
@@ -62,6 +78,18 @@ function resolveNamespaceIdForTable(
|
|
|
62
78
|
return UNBOUND_NAMESPACE_ID;
|
|
63
79
|
}
|
|
64
80
|
|
|
81
|
+
function resolveNamespaceIdForTable(
|
|
82
|
+
contract: Contract<SqlStorage>,
|
|
83
|
+
tableName: string,
|
|
84
|
+
ddlSchemaName: string | undefined,
|
|
85
|
+
): string {
|
|
86
|
+
return resolveNamespaceIdForEntity(
|
|
87
|
+
contract,
|
|
88
|
+
{ entityKind: 'table', entityName: tableName },
|
|
89
|
+
ddlSchemaName,
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
65
93
|
export function resolveNamespaceIdForDdlSchema(
|
|
66
94
|
contract: Contract<SqlStorage>,
|
|
67
95
|
ddlSchemaName: string,
|
|
@@ -92,16 +120,60 @@ function postgresCallFields(call: PostgresOpFactoryCall): PostgresCallFields {
|
|
|
92
120
|
};
|
|
93
121
|
}
|
|
94
122
|
|
|
95
|
-
|
|
96
|
-
factoryName: string,
|
|
123
|
+
function formatSuppressionSubjectLabel(
|
|
97
124
|
subject: ControlPolicySubject | undefined,
|
|
98
125
|
contract: Contract<SqlStorage>,
|
|
99
126
|
): string {
|
|
100
|
-
if (subject
|
|
101
|
-
|
|
102
|
-
|
|
127
|
+
if (subject === undefined) return 'unknown';
|
|
128
|
+
const ddlSchema = ddlSchemaNameForNamespace(contract, subject.namespaceId);
|
|
129
|
+
if (subject.entityKind !== undefined && subject.entityName !== undefined) {
|
|
130
|
+
return `${subject.entityKind} "${ddlSchema}.${subject.entityName}"`;
|
|
131
|
+
}
|
|
132
|
+
return `namespace "${ddlSchema}"`;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function postgresSuppressionSummary(
|
|
136
|
+
subjectLabel: string,
|
|
137
|
+
subject: ControlPolicySubject | undefined,
|
|
138
|
+
policy: string,
|
|
139
|
+
): string {
|
|
140
|
+
const namespace = subject?.namespaceId ?? 'unknown';
|
|
141
|
+
const declared = subject?.explicitNodeControlPolicy;
|
|
142
|
+
if (policy === 'external' && declared === 'managed') {
|
|
143
|
+
return `control policy suppressed: ${subjectLabel} — namespace '${namespace}' has effective control 'external' but declared 'managed'`;
|
|
103
144
|
}
|
|
104
|
-
|
|
145
|
+
const declaredSuffix = declared ? ` but declared '${declared}'` : '';
|
|
146
|
+
return `control policy suppressed: ${subjectLabel} — namespace '${namespace}' has effective control '${policy}'${declaredSuffix}`;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Render one family {@link SuppressionRecord} into a target `SqlPlannerConflict`.
|
|
151
|
+
* The family decides *that* a subject is suppressed and hands over the raw
|
|
152
|
+
* coordinate + policy; the label, message, and location are rendered here,
|
|
153
|
+
* driven entirely by the subject's own `(entityKind, entityName)` coordinate
|
|
154
|
+
* — no target-owned table-vs-enum vocabulary.
|
|
155
|
+
*/
|
|
156
|
+
export function renderPostgresSuppression(
|
|
157
|
+
record: SuppressionRecord,
|
|
158
|
+
contract: Contract<SqlStorage>,
|
|
159
|
+
): SqlPlannerConflict {
|
|
160
|
+
const subject = record.subject;
|
|
161
|
+
const subjectLabel = formatSuppressionSubjectLabel(subject, contract);
|
|
162
|
+
return {
|
|
163
|
+
kind: 'controlPolicySuppressedCall',
|
|
164
|
+
summary: postgresSuppressionSummary(subjectLabel, subject, record.policy),
|
|
165
|
+
location: {
|
|
166
|
+
...ifDefined('namespaceId', subject?.namespaceId),
|
|
167
|
+
...ifDefined('entityKind', subject?.entityKind),
|
|
168
|
+
...ifDefined('entityName', subject?.entityName),
|
|
169
|
+
...ifDefined('column', subject?.column),
|
|
170
|
+
},
|
|
171
|
+
meta: {
|
|
172
|
+
controlPolicy: record.policy,
|
|
173
|
+
...ifDefined('factoryName', record.factoryName),
|
|
174
|
+
...ifDefined('declaredControlPolicy', subject?.explicitNodeControlPolicy),
|
|
175
|
+
},
|
|
176
|
+
};
|
|
105
177
|
}
|
|
106
178
|
|
|
107
179
|
export function resolvePostgresCallControlPolicySubject(
|
|
@@ -131,9 +203,10 @@ export function resolvePostgresCallControlPolicySubject(
|
|
|
131
203
|
})?.control;
|
|
132
204
|
return {
|
|
133
205
|
namespaceId,
|
|
134
|
-
|
|
135
|
-
|
|
206
|
+
entityKind: 'table',
|
|
207
|
+
entityName: callFields.tableName,
|
|
136
208
|
...ifDefined('column', callFields.columnName),
|
|
209
|
+
...ifDefined('explicitNodeControlPolicy', tableControlPolicy),
|
|
137
210
|
createsNewObject,
|
|
138
211
|
};
|
|
139
212
|
}
|
|
@@ -149,10 +222,10 @@ export function resolvePostgresCallControlPolicySubject(
|
|
|
149
222
|
}
|
|
150
223
|
|
|
151
224
|
/**
|
|
152
|
-
* Node kinds
|
|
153
|
-
* object
|
|
154
|
-
*
|
|
155
|
-
*
|
|
225
|
+
* Node kinds whose *absence* is the creation of a whole, top-level Postgres
|
|
226
|
+
* object: a namespace, a table, or a native enum. Used by
|
|
227
|
+
* {@link resolvePostgresNodeIssueCreationFactoryName} to decide whether a
|
|
228
|
+
* `tolerated` subject permits the issue to flow into the planner
|
|
156
229
|
* (create-if-absent) and to seed the suppressed-subject warning's
|
|
157
230
|
* `factoryName` when the planner is skipped. RLS policy creation is not
|
|
158
231
|
* listed here — policy issues never reach this issue-based partition (they
|
|
@@ -162,13 +235,15 @@ export function resolvePostgresCallControlPolicySubject(
|
|
|
162
235
|
const POSTGRES_NODE_CREATION_FACTORY: Readonly<Record<string, string>> = Object.freeze({
|
|
163
236
|
[PostgresSchemaNodeKind.namespace]: 'createSchema',
|
|
164
237
|
[PostgresSchemaNodeKind.table]: 'createTable',
|
|
238
|
+
[PostgresSchemaNodeKind.nativeEnum]: 'createNativeEnumType',
|
|
165
239
|
});
|
|
166
240
|
|
|
167
241
|
/**
|
|
168
242
|
* A table `not-equal` issue whose `rlsEnabled` flips OFF→ON (expected on,
|
|
169
|
-
* actual off) is enablement toward `ENABLE ROW LEVEL SECURITY
|
|
170
|
-
* creation-class
|
|
171
|
-
*
|
|
243
|
+
* actual off) is enablement toward `ENABLE ROW LEVEL SECURITY`. It is
|
|
244
|
+
* creation-class on the node side because `isEnablementCreationIssue` and
|
|
245
|
+
* {@link resolvePostgresNodeIssueCreationFactoryName} treat this OFF→ON delta
|
|
246
|
+
* as a creation: enabling RLS establishes the fail-closed guard the declared
|
|
172
247
|
* policy set attaches to, the same grant `tolerated` extends to creating the
|
|
173
248
|
* policies themselves. The opposite direction (`DISABLE`) is a modification
|
|
174
249
|
* and stays managed-only. Keying on the actual delta (not just the expected
|
|
@@ -206,15 +281,36 @@ export function resolvePostgresNodeIssueCreationFactoryName(
|
|
|
206
281
|
* {@link SchemaDiffIssue}. Mirrors the resolution
|
|
207
282
|
* `resolvePostgresCallControlPolicySubject` performs for a generated DDL
|
|
208
283
|
* call, but works *off the issue* — so the planner can partition issues by
|
|
209
|
-
* effective policy before the diff engine runs.
|
|
210
|
-
* derived from the node kind + reason: a `not-found` schema/table describes
|
|
211
|
-
* a brand-new top-level object; everything else touches an existing object.
|
|
284
|
+
* effective policy before the diff engine runs.
|
|
212
285
|
*
|
|
213
|
-
* A
|
|
214
|
-
*
|
|
215
|
-
* `
|
|
216
|
-
*
|
|
217
|
-
*
|
|
286
|
+
* A whole storage entity (table or native enum) resolves generically off
|
|
287
|
+
* {@link postgresNodeStorageCoordinate} — the node self-describes its
|
|
288
|
+
* `(entityKind, entityName)`, so table and enum issues share one code path
|
|
289
|
+
* with no per-kind branch. A sub-entity issue (a column/constraint drift
|
|
290
|
+
* inside an owned table) carries no such coordinate on its own node, so it
|
|
291
|
+
* falls back to reading the enclosing table name off the issue path.
|
|
292
|
+
*
|
|
293
|
+
* `createsNewObject` is delegated to {@link resolvePostgresNodeIssueCreationFactoryName}
|
|
294
|
+
* in every branch below — that function already encodes exactly which
|
|
295
|
+
* node-kind/reason combinations describe a brand-new top-level object
|
|
296
|
+
* (a `not-found` table/enum, or an RLS-enablement `not-equal`).
|
|
297
|
+
*
|
|
298
|
+
* A `not-expected` (extra/dropped) entity carries no contract namespace
|
|
299
|
+
* coordinate — the live object isn't claimed by any contract namespace — so
|
|
300
|
+
* the subject's `namespaceId` falls back to {@link UNBOUND_NAMESPACE_ID} via
|
|
301
|
+
* `resolveNamespaceIdForEntity`'s own fallback, matching how the call-side
|
|
302
|
+
* resolver treats the `DropTableCall`/`DropNativeEnumTypeCall` it produces.
|
|
303
|
+
*
|
|
304
|
+
* A role node resolves against the `PostgresRole` contract entity at that
|
|
305
|
+
* role's name (roles are root-level diff subjects, so the name is the
|
|
306
|
+
* second path segment after the tree root). A `not-found` role is declared,
|
|
307
|
+
* so the entity is always found and its `control` read directly — which is
|
|
308
|
+
* always {@link ROLE_DEFAULT_CONTROL_POLICY} today, since no authoring
|
|
309
|
+
* surface sets it otherwise. A `not-expected` role has no contract entity
|
|
310
|
+
* (an undeclared live role, by definition), so the subject falls back to
|
|
311
|
+
* the same default — roles are referenced but never owned, so an
|
|
312
|
+
* undeclared role gets exactly the same governance as a declared one.
|
|
313
|
+
* Never creates a new object: role provisioning is a non-goal.
|
|
218
314
|
*/
|
|
219
315
|
export function resolvePostgresNodeIssueControlPolicySubject(
|
|
220
316
|
issue: SchemaDiffIssue<SqlSchemaDiffNode>,
|
|
@@ -234,6 +330,39 @@ export function resolvePostgresNodeIssueControlPolicySubject(
|
|
|
234
330
|
};
|
|
235
331
|
}
|
|
236
332
|
|
|
333
|
+
if (node.nodeKind === PostgresSchemaNodeKind.role) {
|
|
334
|
+
const roleName = issue.path[1];
|
|
335
|
+
const roleEntity =
|
|
336
|
+
roleName === undefined
|
|
337
|
+
? undefined
|
|
338
|
+
: entityAt<PostgresRole>(contract.storage, {
|
|
339
|
+
namespaceId: UNBOUND_NAMESPACE_ID,
|
|
340
|
+
entityKind: 'role',
|
|
341
|
+
entityName: roleName,
|
|
342
|
+
});
|
|
343
|
+
return {
|
|
344
|
+
namespaceId: UNBOUND_NAMESPACE_ID,
|
|
345
|
+
explicitNodeControlPolicy: roleEntity?.control ?? ROLE_DEFAULT_CONTROL_POLICY,
|
|
346
|
+
createsNewObject: false,
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const coordinate = postgresNodeStorageCoordinate(node);
|
|
351
|
+
if (coordinate !== undefined) {
|
|
352
|
+
const namespaceId = resolveNamespaceIdForEntity(contract, coordinate, issue.path[1]);
|
|
353
|
+
const entityControl = entityAt<{ readonly control?: ControlPolicy }>(contract.storage, {
|
|
354
|
+
namespaceId,
|
|
355
|
+
...coordinate,
|
|
356
|
+
})?.control;
|
|
357
|
+
return {
|
|
358
|
+
namespaceId,
|
|
359
|
+
...coordinate,
|
|
360
|
+
...ifDefined('column', issueColumnName(issue)),
|
|
361
|
+
...ifDefined('explicitNodeControlPolicy', entityControl),
|
|
362
|
+
createsNewObject: resolvePostgresNodeIssueCreationFactoryName(issue) !== undefined,
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
|
|
237
366
|
const tableName = issue.path[2];
|
|
238
367
|
if (tableName === undefined) return undefined;
|
|
239
368
|
const ddlSchemaName = issue.path[1];
|
|
@@ -243,15 +372,13 @@ export function resolvePostgresNodeIssueControlPolicySubject(
|
|
|
243
372
|
entityKind: 'table',
|
|
244
373
|
entityName: tableName,
|
|
245
374
|
});
|
|
246
|
-
const createsNewObject =
|
|
247
|
-
(node.nodeKind === PostgresSchemaNodeKind.table && issue.reason === 'not-found') ||
|
|
248
|
-
isEnablementCreationIssue(issue);
|
|
249
375
|
|
|
250
376
|
return {
|
|
251
377
|
namespaceId,
|
|
252
|
-
|
|
253
|
-
|
|
378
|
+
entityKind: 'table',
|
|
379
|
+
entityName: tableName,
|
|
254
380
|
...ifDefined('column', issueColumnName(issue)),
|
|
255
|
-
|
|
381
|
+
...ifDefined('explicitNodeControlPolicy', table?.control),
|
|
382
|
+
createsNewObject: resolvePostgresNodeIssueCreationFactoryName(issue) !== undefined,
|
|
256
383
|
};
|
|
257
384
|
}
|
|
@@ -3,8 +3,9 @@ import type { SqlSchemaDiffResult } from '@prisma-next/family-sql/control';
|
|
|
3
3
|
import { buildNativeTypeExpander } from '@prisma-next/family-sql/control';
|
|
4
4
|
import { classifyDiffSubjectGranularity } from '@prisma-next/family-sql/diff';
|
|
5
5
|
import type { TargetBoundComponentDescriptor } from '@prisma-next/framework-components/components';
|
|
6
|
-
import type { SchemaDiffIssue } from '@prisma-next/framework-components/control';
|
|
6
|
+
import type { DiffableNode, SchemaDiffIssue } from '@prisma-next/framework-components/control';
|
|
7
7
|
import { diffSchemas } from '@prisma-next/framework-components/control';
|
|
8
|
+
import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir';
|
|
8
9
|
import type { SqlStorage } from '@prisma-next/sql-contract/types';
|
|
9
10
|
import type { SqlSchemaIRNode } from '@prisma-next/sql-schema-ir/types';
|
|
10
11
|
import { blindCast } from '@prisma-next/utils/casts';
|
|
@@ -19,6 +20,31 @@ import {
|
|
|
19
20
|
import { contractToPostgresDatabaseSchemaNode } from './contract-to-postgres-database-schema-node';
|
|
20
21
|
import { resolvePostgresNodeIssueControlPolicySubject } from './control-policy';
|
|
21
22
|
|
|
23
|
+
/**
|
|
24
|
+
* Whether a diff issue's subject node is cluster-scoped — it carries its own
|
|
25
|
+
* `namespaceId` field (only role and policy diff nodes do) and that
|
|
26
|
+
* coordinate is the unbound sentinel, meaning the node is not owned by any
|
|
27
|
+
* schema/namespace. A role node is always cluster-scoped (roles are
|
|
28
|
+
* cluster-level objects); a policy node's `namespaceId` is always its
|
|
29
|
+
* table's resolved DDL schema, never the sentinel. Namespace-ownership
|
|
30
|
+
* scoping (which compares `issue.path[1]` against the set of DDL schemas
|
|
31
|
+
* the contract owns) makes no sense for a cluster-scoped subject — its path
|
|
32
|
+
* segment at that index is the object's own name, not a schema name — so
|
|
33
|
+
* such an issue bypasses that scoping entirely.
|
|
34
|
+
*/
|
|
35
|
+
function isClusterScopedIssue(issue: SchemaDiffIssue): boolean {
|
|
36
|
+
const node = issue.expected ?? issue.actual;
|
|
37
|
+
return node !== undefined && nodeNamespaceId(node) === UNBOUND_NAMESPACE_ID;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function nodeNamespaceId(node: DiffableNode): string | undefined {
|
|
41
|
+
if (!Object.hasOwn(node, 'namespaceId')) return undefined;
|
|
42
|
+
return blindCast<
|
|
43
|
+
{ namespaceId: string },
|
|
44
|
+
'presence of an own namespaceId field was just checked via Object.hasOwn'
|
|
45
|
+
>(node).namespaceId;
|
|
46
|
+
}
|
|
47
|
+
|
|
22
48
|
function ownedSchemaNames(expected: PostgresDatabaseSchemaNode): ReadonlySet<string> {
|
|
23
49
|
const policyNamespaces = Object.values(expected.namespaces).flatMap((ns) =>
|
|
24
50
|
Object.values(ns.tables).flatMap((t) => t.policies.map((p) => p.namespaceId)),
|
|
@@ -53,10 +79,17 @@ function pruneTableLessNamespaces(
|
|
|
53
79
|
}
|
|
54
80
|
|
|
55
81
|
/**
|
|
56
|
-
* Resolves a verdict-diff issue's subject
|
|
57
|
-
*
|
|
82
|
+
* Resolves a verdict-diff issue's subject's declared control policy directly
|
|
83
|
+
* from the contract, by delegating to the same node-typed resolver
|
|
58
84
|
* ({@link resolvePostgresNodeIssueControlPolicySubject}) the planner uses to
|
|
59
|
-
* gate DDL calls. `undefined` when the issue resolves to no contract
|
|
85
|
+
* gate DDL calls. `undefined` when the issue resolves to no contract subject.
|
|
86
|
+
*
|
|
87
|
+
* A role issue resolves through that same resolver to `external`
|
|
88
|
+
* unconditionally (see its role branch), regardless of the contract's own
|
|
89
|
+
* default policy: a role is referenced by the contract but not owned, and
|
|
90
|
+
* `external`'s existing semantics — a missing declared subject still fails,
|
|
91
|
+
* every extra is suppressed — are exactly the wanted asymmetric grading for
|
|
92
|
+
* a cluster object the framework does not own.
|
|
60
93
|
*/
|
|
61
94
|
function resolveControlPolicy(
|
|
62
95
|
issue: SchemaDiffIssue,
|
|
@@ -75,8 +108,9 @@ function resolveControlPolicy(
|
|
|
75
108
|
* the expected tree (resolved leaf values, expander threaded, FK schemas
|
|
76
109
|
* resolved, table-less namespaces pruned), run the generic
|
|
77
110
|
* differ over the trees as derived, and scope out `not-expected` findings under namespaces the
|
|
78
|
-
* contract does not own. Ownership
|
|
79
|
-
*
|
|
111
|
+
* contract does not own. Ownership scoping bypasses cluster-scoped subjects
|
|
112
|
+
* (roles today), mirroring the legacy decomposition: relational extras check
|
|
113
|
+
* the PRUNED owned set (the legacy
|
|
80
114
|
* per-namespace walk never visited a table-less namespace, so its live
|
|
81
115
|
* relational contents are invisible), while `structural` extras (RLS
|
|
82
116
|
* policies) check the FULL owned set (the legacy policy diff governed
|
|
@@ -84,7 +118,7 @@ function resolveControlPolicy(
|
|
|
84
118
|
* shrink because a namespace declares no tables). The codec `verifyType`
|
|
85
119
|
* hooks run once per contract namespace with tables against that
|
|
86
120
|
* namespace's paired actual node (the hooks read namespace-scoped state
|
|
87
|
-
* such as `
|
|
121
|
+
* such as `nativeEnums`).
|
|
88
122
|
*/
|
|
89
123
|
export function diffPostgresSchema(input: {
|
|
90
124
|
readonly contract: Contract<SqlStorage>;
|
|
@@ -107,9 +141,10 @@ export function diffPostgresSchema(input: {
|
|
|
107
141
|
const structuralOwned = ownedSchemaNames(fullExpected);
|
|
108
142
|
const issues = diffSchemas(expected, actual).filter((issue) => {
|
|
109
143
|
if (issue.reason !== 'not-expected') return true;
|
|
144
|
+
if (isClusterScopedIssue(issue)) return true;
|
|
145
|
+
const granularity = classifyDiffSubjectGranularity(issue, postgresDiffSubjectGranularity);
|
|
110
146
|
const namespaceSegment = issue.path[1];
|
|
111
147
|
if (namespaceSegment === undefined) return true;
|
|
112
|
-
const granularity = classifyDiffSubjectGranularity(issue, postgresDiffSubjectGranularity);
|
|
113
148
|
const owned = granularity === 'structural' ? structuralOwned : relationalOwned;
|
|
114
149
|
return owned.has(namespaceSegment);
|
|
115
150
|
});
|
|
@@ -146,7 +181,6 @@ function padActualNamespaces(
|
|
|
146
181
|
namespaces[schemaName] = new PostgresNamespaceSchemaNode({
|
|
147
182
|
schemaName,
|
|
148
183
|
tables: {},
|
|
149
|
-
nativeEnumTypeNames: [],
|
|
150
184
|
});
|
|
151
185
|
padded = true;
|
|
152
186
|
}
|
|
@@ -165,15 +199,15 @@ export interface PostgresPlanDiff {
|
|
|
165
199
|
readonly expected: PostgresDatabaseSchemaNode;
|
|
166
200
|
/** The live ("start") tree, padded with empty namespaces so a missing schema's tables pair. */
|
|
167
201
|
readonly actual: PostgresDatabaseSchemaNode;
|
|
168
|
-
/** The one node diff over the two trees: relational + policy drift,
|
|
202
|
+
/** The one node diff over the two trees: relational + policy drift, cluster-scope-aware ownership filtered. */
|
|
169
203
|
readonly issues: readonly SchemaDiffIssue<SqlSchemaDiffNode>[];
|
|
170
204
|
}
|
|
171
205
|
|
|
172
206
|
/**
|
|
173
207
|
* The Postgres planner's diff input: the SAME tree-building
|
|
174
208
|
* `diffPostgresSchema` uses (expander threaded, FK schemas resolved,
|
|
175
|
-
* table-less namespaces pruned,
|
|
176
|
-
* namespace padding (so a missing
|
|
209
|
+
* table-less namespaces pruned, cluster-scope-aware ownership filter) plus
|
|
210
|
+
* actual namespace padding (so a missing
|
|
177
211
|
* schema's tables surface as `not-found` instead of a swallowed namespace
|
|
178
212
|
* `not-found`). One differ drives both verify and plan; this is the
|
|
179
213
|
* plan-side derivation. The single issue list covers tables / columns /
|
|
@@ -207,9 +241,10 @@ export function buildPostgresPlanDiff(input: {
|
|
|
207
241
|
'both trees are PostgresDatabaseSchemaNodes, so every diff-issue node is a SqlSchemaDiffNode'
|
|
208
242
|
>(diffSchemas(expected, paddedActual)).filter((issue) => {
|
|
209
243
|
if (issue.reason !== 'not-expected') return true;
|
|
244
|
+
if (isClusterScopedIssue(issue)) return true;
|
|
245
|
+
const granularity = classifyDiffSubjectGranularity(issue, postgresDiffSubjectGranularity);
|
|
210
246
|
const namespaceSegment = issue.path[1];
|
|
211
247
|
if (namespaceSegment === undefined) return true;
|
|
212
|
-
const granularity = classifyDiffSubjectGranularity(issue, postgresDiffSubjectGranularity);
|
|
213
248
|
const owned = granularity === 'structural' ? structuralOwned : relationalOwned;
|
|
214
249
|
return owned.has(namespaceSegment);
|
|
215
250
|
});
|
|
@@ -45,6 +45,7 @@ import { ifDefined } from '@prisma-next/utils/defined';
|
|
|
45
45
|
import type { Result } from '@prisma-next/utils/result';
|
|
46
46
|
import { notOk, ok } from '@prisma-next/utils/result';
|
|
47
47
|
import type { PostgresNamespaceSchemaNode } from '../schema-ir/postgres-namespace-schema-node';
|
|
48
|
+
import type { PostgresNativeEnumSchemaNode } from '../schema-ir/postgres-native-enum-schema-node';
|
|
48
49
|
import type { PostgresTableSchemaNode } from '../schema-ir/postgres-table-schema-node';
|
|
49
50
|
import { PostgresSchemaNodeKind } from '../schema-ir/schema-node-kinds';
|
|
50
51
|
import { quoteIdentifier } from '../sql-utils';
|
|
@@ -61,6 +62,7 @@ import {
|
|
|
61
62
|
AddUniqueCall,
|
|
62
63
|
AlterColumnTypeCall,
|
|
63
64
|
CreateIndexCall,
|
|
65
|
+
CreateNativeEnumTypeCall,
|
|
64
66
|
CreateSchemaCall,
|
|
65
67
|
CreateTableCall,
|
|
66
68
|
DisableRowLevelSecurityCall,
|
|
@@ -69,6 +71,7 @@ import {
|
|
|
69
71
|
DropConstraintCall,
|
|
70
72
|
DropDefaultCall,
|
|
71
73
|
DropIndexCall,
|
|
74
|
+
DropNativeEnumTypeCall,
|
|
72
75
|
DropNotNullCall,
|
|
73
76
|
DropTableCall,
|
|
74
77
|
EnableRowLevelSecurityCall,
|
|
@@ -152,8 +155,10 @@ function classifyCall(call: PostgresOpFactoryCall): CallCategory {
|
|
|
152
155
|
switch (call.factoryName) {
|
|
153
156
|
case 'createExtension':
|
|
154
157
|
case 'createSchema':
|
|
158
|
+
case 'createNativeEnumType':
|
|
155
159
|
return 'dep';
|
|
156
160
|
case 'dropTable':
|
|
161
|
+
case 'dropNativeEnumType':
|
|
157
162
|
case 'dropColumn':
|
|
158
163
|
case 'dropConstraint':
|
|
159
164
|
case 'dropCheckConstraint':
|
|
@@ -250,17 +255,22 @@ function locationForCall(call: PostgresOpFactoryCall): SqlPlannerConflict['locat
|
|
|
250
255
|
typeName?: string;
|
|
251
256
|
};
|
|
252
257
|
const location: {
|
|
253
|
-
|
|
258
|
+
entityKind?: string;
|
|
259
|
+
entityName?: string;
|
|
254
260
|
column?: string;
|
|
255
261
|
index?: string;
|
|
256
262
|
constraint?: string;
|
|
257
|
-
type?: string;
|
|
258
263
|
} = {};
|
|
259
|
-
if (anyCall.tableName)
|
|
264
|
+
if (anyCall.tableName) {
|
|
265
|
+
location.entityKind = 'table';
|
|
266
|
+
location.entityName = anyCall.tableName;
|
|
267
|
+
} else if (anyCall.typeName) {
|
|
268
|
+
location.entityKind = 'native_enum';
|
|
269
|
+
location.entityName = anyCall.typeName;
|
|
270
|
+
}
|
|
260
271
|
if (anyCall.columnName) location.column = anyCall.columnName;
|
|
261
272
|
if (anyCall.indexName) location.index = anyCall.indexName;
|
|
262
273
|
if (anyCall.constraintName) location.constraint = anyCall.constraintName;
|
|
263
|
-
if (anyCall.typeName) location.type = anyCall.typeName;
|
|
264
274
|
return Object.keys(location).length > 0 ? (location as SqlPlannerConflictLocation) : undefined;
|
|
265
275
|
}
|
|
266
276
|
|
|
@@ -374,6 +384,11 @@ export function nodeIssueOrder(issue: SchemaDiffIssue): number {
|
|
|
374
384
|
switch (node.nodeKind) {
|
|
375
385
|
case PostgresSchemaNodeKind.namespace:
|
|
376
386
|
return 1;
|
|
387
|
+
case PostgresSchemaNodeKind.nativeEnum:
|
|
388
|
+
// Creates order right after namespace creates within the 'dep' bucket
|
|
389
|
+
// (CREATE SCHEMA before CREATE TYPE); drops order after table drops
|
|
390
|
+
// within the 'drop' bucket (DROP TYPE only after its dependents left).
|
|
391
|
+
return issue.reason === 'not-expected' ? 17 : 2;
|
|
377
392
|
case RelationalSchemaNodeKind.foreignKey:
|
|
378
393
|
return issue.reason === 'not-expected' ? 10 : 60;
|
|
379
394
|
case RelationalSchemaNodeKind.unique:
|
|
@@ -527,6 +542,60 @@ function nodeConflict(kind: SqlPlannerConflict['kind'], message: string): SqlPla
|
|
|
527
542
|
return issueConflict(kind, message);
|
|
528
543
|
}
|
|
529
544
|
|
|
545
|
+
/**
|
|
546
|
+
* Managed native-enum issue -> op lowering. A missing declared type creates
|
|
547
|
+
* it; an unclaimed live type drops it (ownership-scoped upstream by
|
|
548
|
+
* `retainUnownedExtras`, destructiveness gated by the operation-class
|
|
549
|
+
* policy); a paired member-value mismatch is a NAMED unsupported diagnostic
|
|
550
|
+
* — never a silent no-op and never a drop-and-recreate. Slice B replaces the
|
|
551
|
+
* diagnostic with order-aware `ALTER TYPE ... ADD VALUE` / refusal semantics.
|
|
552
|
+
*/
|
|
553
|
+
function mapNativeEnumNodeIssue(
|
|
554
|
+
issue: SchemaDiffIssue,
|
|
555
|
+
ctx: StrategyContext,
|
|
556
|
+
): Result<readonly PostgresOpFactoryCall[], SqlPlannerConflict> {
|
|
557
|
+
const ddlSchemaName = issueSchemaName(issue);
|
|
558
|
+
if (ddlSchemaName === undefined) {
|
|
559
|
+
return notOk(
|
|
560
|
+
nodeConflict(
|
|
561
|
+
'unsupportedOperation',
|
|
562
|
+
`Enum issue has no schema in its path: ${issue.path.join('/')}`,
|
|
563
|
+
),
|
|
564
|
+
);
|
|
565
|
+
}
|
|
566
|
+
const schemaName = emissionSchemaName(ctx, ddlSchemaName);
|
|
567
|
+
if (issue.reason === 'not-found') {
|
|
568
|
+
const expected = blindCast<
|
|
569
|
+
PostgresNativeEnumSchemaNode,
|
|
570
|
+
'a not-found native-enum issue always carries the expected PostgresNativeEnumSchemaNode'
|
|
571
|
+
>(issue.expected);
|
|
572
|
+
return ok([new CreateNativeEnumTypeCall(schemaName, expected.typeName, expected.members)]);
|
|
573
|
+
}
|
|
574
|
+
if (issue.reason === 'not-expected') {
|
|
575
|
+
const actual = blindCast<
|
|
576
|
+
PostgresNativeEnumSchemaNode,
|
|
577
|
+
'a not-expected native-enum issue always carries the actual PostgresNativeEnumSchemaNode'
|
|
578
|
+
>(issue.actual);
|
|
579
|
+
return ok([new DropNativeEnumTypeCall(schemaName, actual.typeName)]);
|
|
580
|
+
}
|
|
581
|
+
const expected = blindCast<
|
|
582
|
+
PostgresNativeEnumSchemaNode,
|
|
583
|
+
'a not-equal native-enum issue carries both sides; the expected node names the type'
|
|
584
|
+
>(issue.expected);
|
|
585
|
+
const actualMembers = blindCast<
|
|
586
|
+
PostgresNativeEnumSchemaNode,
|
|
587
|
+
'a not-equal native-enum issue carries both sides'
|
|
588
|
+
>(issue.actual).members;
|
|
589
|
+
return notOk(
|
|
590
|
+
nodeConflict(
|
|
591
|
+
'unsupportedOperation',
|
|
592
|
+
`Native enum type "${ddlSchemaName}"."${expected.typeName}" member values changed ` +
|
|
593
|
+
`(contract declares [${expected.members.join(', ')}], database has [${actualMembers.join(', ')}]); ` +
|
|
594
|
+
'enum value changes are not auto-migrated yet. Author the change manually with `migration new`.',
|
|
595
|
+
),
|
|
596
|
+
);
|
|
597
|
+
}
|
|
598
|
+
|
|
530
599
|
function mapTableNodeIssue(
|
|
531
600
|
issue: SchemaDiffIssue,
|
|
532
601
|
schemaName: string,
|
|
@@ -814,6 +883,10 @@ export function mapNodeIssueToCall(
|
|
|
814
883
|
return ok([new CreateSchemaCall(namespace.schemaName)]);
|
|
815
884
|
}
|
|
816
885
|
|
|
886
|
+
if (node.nodeKind === PostgresSchemaNodeKind.nativeEnum) {
|
|
887
|
+
return mapNativeEnumNodeIssue(issue, ctx);
|
|
888
|
+
}
|
|
889
|
+
|
|
817
890
|
const ddlSchemaName = issueSchemaName(issue);
|
|
818
891
|
const tableName = issueTableName(issue);
|
|
819
892
|
if (ddlSchemaName === undefined || tableName === undefined) {
|