@prisma-next/target-postgres 0.14.0-dev.4 → 0.14.0-dev.40
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-BX1WHIdN.mjs +220 -0
- package/dist/authoring-BX1WHIdN.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-types-DHCkwPKE.d.mts → codec-types-CueCoGD0.d.mts} +2 -2
- package/dist/{codec-types-DHCkwPKE.d.mts.map → codec-types-CueCoGD0.d.mts.map} +1 -1
- package/dist/codec-types.d.mts +1 -1
- package/dist/{codecs--0A5_4Bq.d.mts → codecs-Ca8upop6.d.mts} +12 -1
- package/dist/codecs-Ca8upop6.d.mts.map +1 -0
- package/dist/codecs.d.mts +1 -1
- package/dist/codecs.mjs +34 -1
- 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 +3 -3
- package/dist/control.d.mts +1 -1
- package/dist/control.d.mts.map +1 -1
- package/dist/control.mjs +639 -21
- 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-qKy1U5V9.d.mts} +2 -2
- package/dist/{data-transform-DDgWdB5o.d.mts.map → data-transform-qKy1U5V9.d.mts.map} +1 -1
- package/dist/data-transform.d.mts +1 -1
- package/dist/{ddl-QDyOSeLc.mjs → ddl-BmLIe1z_.mjs} +62 -5
- package/dist/ddl-BmLIe1z_.mjs.map +1 -0
- package/dist/ddl.d.mts +3 -2
- package/dist/ddl.mjs +2 -2
- package/dist/{default-normalizer-DyyCHQWs.mjs → default-normalizer-Dug8Fez9.mjs} +119 -2
- package/dist/default-normalizer-Dug8Fez9.mjs.map +1 -0
- package/dist/default-normalizer.d.mts +1 -1
- package/dist/default-normalizer.d.mts.map +1 -1
- package/dist/default-normalizer.mjs +1 -1
- package/dist/descriptor-meta-runtime-UwYdV8fD.mjs +12 -0
- package/dist/descriptor-meta-runtime-UwYdV8fD.mjs.map +1 -0
- package/dist/descriptor-meta-tjatyhtZ.mjs +18 -0
- package/dist/descriptor-meta-tjatyhtZ.mjs.map +1 -0
- package/dist/{issue-planner-DL6g3CmE.mjs → issue-planner-DbFvaAgu.mjs} +35 -10
- package/dist/issue-planner-DbFvaAgu.mjs.map +1 -0
- package/dist/issue-planner.d.mts +3 -3
- package/dist/issue-planner.d.mts.map +1 -1
- package/dist/issue-planner.mjs +1 -1
- package/dist/migration.d.mts +5 -5
- package/dist/migration.mjs +4 -4
- 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-DmQEc3XV.d.mts → op-factory-call-CJWUxyUW.d.mts} +44 -13
- package/dist/op-factory-call-CJWUxyUW.d.mts.map +1 -0
- package/dist/{op-factory-call-D_p5vxwt.mjs → op-factory-call-t65c4jyY.mjs} +201 -28
- package/dist/op-factory-call-t65c4jyY.mjs.map +1 -0
- package/dist/op-factory-call.d.mts +1 -1
- package/dist/op-factory-call.mjs +1 -1
- package/dist/pack.d.mts +74 -2
- package/dist/pack.d.mts.map +1 -1
- package/dist/pack.mjs +1 -1
- package/dist/planner-CG2pLHk-.mjs +640 -0
- package/dist/planner-CG2pLHk-.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 +2 -4
- package/dist/planner-ddl-builders.d.mts.map +1 -1
- package/dist/planner-ddl-builders.mjs +2 -2
- package/dist/{planner-produced-postgres-migration-Cji5vxUf.mjs → planner-produced-postgres-migration-BolD0Mvc.mjs} +3 -3
- package/dist/{planner-produced-postgres-migration-Cji5vxUf.mjs.map → planner-produced-postgres-migration-BolD0Mvc.mjs.map} +1 -1
- package/dist/{planner-produced-postgres-migration-QqHa2C2l.d.mts → planner-produced-postgres-migration-Cz9--XOT.d.mts} +4 -4
- package/dist/{planner-produced-postgres-migration-QqHa2C2l.d.mts.map → planner-produced-postgres-migration-Cz9--XOT.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-DE9c4519.mjs} +2 -2
- package/dist/{planner-sql-checks-jqUUGyQR.mjs.map → planner-sql-checks-DE9c4519.mjs.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 +82 -4
- package/dist/planner.d.mts.map +1 -1
- package/dist/planner.mjs +2 -2
- package/dist/{postgres-contract-serializer-k3TAcPMY.mjs → postgres-contract-view-BegduLr6.mjs} +50 -23
- package/dist/postgres-contract-view-BegduLr6.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-os8ogEap.d.mts +183 -0
- package/dist/postgres-database-schema-node-os8ogEap.d.mts.map +1 -0
- package/dist/{postgres-migration-B5jKrXv3.mjs → postgres-migration-BfMmK6tv.mjs} +21 -2
- package/dist/postgres-migration-BfMmK6tv.mjs.map +1 -0
- package/dist/{postgres-migration-Y4YBJqkS.d.mts → postgres-migration-D7kNVWVG.d.mts} +19 -6
- package/dist/postgres-migration-D7kNVWVG.d.mts.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-BLIHBEfX.mjs} +108 -5
- package/dist/postgres-schema-BLIHBEfX.mjs.map +1 -0
- package/dist/postgres-schema-BywoeKlt.d.mts +189 -0
- package/dist/postgres-schema-BywoeKlt.d.mts.map +1 -0
- package/dist/postgres-table-schema-node-1nNc8XDT.mjs +248 -0
- package/dist/postgres-table-schema-node-1nNc8XDT.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-CL1RR1mx.mjs +20 -0
- package/dist/resolve-ddl-schema-CL1RR1mx.mjs.map +1 -0
- package/dist/rls-canonicalize.d.mts +2 -0
- package/dist/rls-canonicalize.mjs +2 -0
- package/dist/runtime.d.mts +7 -6
- 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 +14 -0
- package/dist/schema-ir-annotations.d.mts.map +1 -0
- package/dist/schema-ir-annotations.mjs +2 -0
- package/dist/types.d.mts +4 -140
- package/dist/types.mjs +3 -2
- package/package.json +22 -17
- package/src/contract-free/checks.ts +73 -0
- package/src/contract-free/ddl.ts +45 -1
- package/src/core/authoring.ts +115 -1
- package/src/core/codecs.ts +35 -1
- package/src/core/ddl/nodes.ts +94 -2
- package/src/core/default-normalizer.ts +122 -2
- package/src/core/descriptor-meta.ts +4 -0
- package/src/core/entity-kinds.ts +16 -0
- package/src/core/migrations/bound-schema.ts +10 -0
- package/src/core/migrations/contract-to-postgres-database-schema-node.ts +125 -0
- package/src/core/migrations/control-policy.ts +3 -0
- package/src/core/migrations/diff-database-schema.ts +191 -0
- package/src/core/migrations/issue-planner.ts +44 -3
- package/src/core/migrations/op-factory-call.ts +109 -21
- package/src/core/migrations/operations/columns.ts +24 -12
- package/src/core/migrations/operations/data-transform.ts +6 -0
- 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 +15 -56
- package/src/core/migrations/planner-recipes.ts +31 -13
- package/src/core/migrations/planner-strategies.ts +3 -11
- package/src/core/migrations/planner-target-details.ts +3 -1
- package/src/core/migrations/planner.ts +165 -33
- 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 +24 -0
- package/src/core/migrations/runner.ts +13 -16
- package/src/core/migrations/verify-postgres-namespaces.ts +24 -19
- package/src/core/postgres-contract-serializer.ts +50 -33
- package/src/core/postgres-contract-view.ts +48 -0
- package/src/core/postgres-rls-policy.ts +64 -0
- package/src/core/postgres-role.ts +37 -0
- package/src/core/postgres-schema.ts +37 -13
- package/src/core/postgres-validators.ts +20 -0
- package/src/core/psl-infer/infer-psl-contract.ts +834 -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 +63 -0
- package/src/core/schema-ir/postgres-namespace-schema-node.ts +61 -0
- package/src/core/schema-ir/postgres-policy-schema-node.ts +93 -0
- package/src/core/schema-ir/postgres-role-schema-node.ts +66 -0
- package/src/core/schema-ir/postgres-table-schema-node.ts +107 -0
- package/src/core/schema-ir/schema-node-kinds.ts +28 -0
- package/src/exports/contract-free.ts +7 -1
- package/src/exports/control.ts +39 -12
- package/src/exports/ddl.ts +4 -0
- package/src/exports/migration.ts +1 -0
- package/src/exports/planner-ddl-builders.ts +0 -2
- package/src/exports/planner.ts +2 -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/codecs--0A5_4Bq.d.mts.map +0 -1
- package/dist/ddl-QDyOSeLc.mjs.map +0 -1
- 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.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
|
@@ -13,28 +13,39 @@ 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,
|
|
21
|
+
SchemaDiffIssue,
|
|
22
22
|
SchemaIssue,
|
|
23
23
|
} from '@prisma-next/framework-components/control';
|
|
24
24
|
import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir';
|
|
25
|
+
import type { SqlSchemaIR } from '@prisma-next/sql-schema-ir/types';
|
|
25
26
|
import { blindCast } from '@prisma-next/utils/casts';
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
27
|
+
import { ifDefined } from '@prisma-next/utils/defined';
|
|
28
|
+
import { PostgresRlsPolicy } from '../postgres-rls-policy';
|
|
29
|
+
import { PostgresDatabaseSchemaNode } from '../schema-ir/postgres-database-schema-node';
|
|
30
|
+
import { PostgresPolicySchemaNode } from '../schema-ir/postgres-policy-schema-node';
|
|
31
|
+
import type { SqlSchemaDiffNode } from '../schema-ir/schema-node-kinds';
|
|
28
32
|
import {
|
|
29
33
|
formatPostgresControlPolicySubjectLabel,
|
|
30
34
|
resolvePostgresCallControlPolicySubject,
|
|
31
35
|
resolvePostgresIssueControlPolicySubject,
|
|
32
36
|
resolvePostgresIssueCreationFactoryName,
|
|
33
37
|
} from './control-policy';
|
|
38
|
+
import { diffPostgresDatabaseSchema } from './diff-database-schema';
|
|
34
39
|
import { planIssues } from './issue-planner';
|
|
35
40
|
import type { PostgresOpFactoryCall } from './op-factory-call';
|
|
41
|
+
import {
|
|
42
|
+
CreatePostgresRlsPolicyCall,
|
|
43
|
+
DropPostgresRlsPolicyCall,
|
|
44
|
+
EnableRowLevelSecurityCall,
|
|
45
|
+
} from './op-factory-call';
|
|
36
46
|
import { TypeScriptRenderablePostgresMigration } from './planner-produced-postgres-migration';
|
|
37
47
|
import { postgresPlannerStrategies } from './planner-strategies';
|
|
48
|
+
import { resolveDdlSchemaForNamespaceStorage } from './resolve-ddl-schema';
|
|
38
49
|
import { verifyPostgresNamespacePresence } from './verify-postgres-namespaces';
|
|
39
50
|
|
|
40
51
|
type PlannerFrameworkComponents = SqlMigrationPlannerPlanOptions extends {
|
|
@@ -47,10 +58,6 @@ type PlannerOptionsWithComponents = SqlMigrationPlannerPlanOptions & {
|
|
|
47
58
|
readonly frameworkComponents: PlannerFrameworkComponents;
|
|
48
59
|
};
|
|
49
60
|
|
|
50
|
-
type VerifySqlSchemaOptionsWithComponents = Parameters<typeof verifySqlSchema>[0] & {
|
|
51
|
-
readonly frameworkComponents: PlannerFrameworkComponents;
|
|
52
|
-
};
|
|
53
|
-
|
|
54
61
|
export function createPostgresMigrationPlanner(
|
|
55
62
|
lowerer: ExecuteRequestLowerer,
|
|
56
63
|
): PostgresMigrationPlanner {
|
|
@@ -151,9 +158,33 @@ export class PostgresMigrationPlanner implements MigrationPlanner<'sql', 'postgr
|
|
|
151
158
|
return policyResult;
|
|
152
159
|
}
|
|
153
160
|
|
|
154
|
-
|
|
161
|
+
// One combined database-schema diff drives the whole plan: the relational
|
|
162
|
+
// findings (+ namespace presence) become structural DDL via `planIssues`,
|
|
163
|
+
// the policy findings become RLS ops via `planPostgresSchemaDiff`. Verify
|
|
164
|
+
// runs the same underlying comparison (via `verifyDatabaseSchema`) and
|
|
165
|
+
// rejects on non-empty. The caller-supplied `keepDiffIssue` predicate is
|
|
166
|
+
// applied blindly — any scoping (e.g. multi-space ownership) is the
|
|
167
|
+
// orchestration's, never worked out here.
|
|
168
|
+
PostgresDatabaseSchemaNode.assert(options.schema);
|
|
169
|
+
const rawDiff = diffPostgresDatabaseSchema({
|
|
170
|
+
contract: options.contract,
|
|
171
|
+
actualSchema: options.schema,
|
|
172
|
+
strict:
|
|
173
|
+
options.policy.allowedOperationClasses.includes('widening') ||
|
|
174
|
+
options.policy.allowedOperationClasses.includes('destructive'),
|
|
175
|
+
typeMetadataRegistry: new Map(),
|
|
176
|
+
frameworkComponents: options.frameworkComponents,
|
|
177
|
+
});
|
|
178
|
+
const databaseDiff = options.keepDiffIssue ? rawDiff.filter(options.keepDiffIssue) : rawDiff;
|
|
179
|
+
const schemaIssues = this.collectSchemaIssues(options, databaseDiff.issues);
|
|
155
180
|
const codecHooks = extractCodecControlHooks(options.frameworkComponents);
|
|
156
181
|
const storageTypes = options.contract.storage.types ?? {};
|
|
182
|
+
// The strategy layer reads the live schema by bare table name for existence
|
|
183
|
+
// checks (shared-temp-default safety, FK/unique probes), so it takes one
|
|
184
|
+
// per-schema namespace node — never the whole tree root, and never a flat
|
|
185
|
+
// merge of every namespace (which would collide same-named tables across
|
|
186
|
+
// schemas). Probing more than one namespace at once is future work.
|
|
187
|
+
const relationalSchema = relationalNamespaceNode(options.schema, schemaName);
|
|
157
188
|
|
|
158
189
|
// Input-side control-policy partition. `external` / `observed` subjects
|
|
159
190
|
// — and non-creation issues for `tolerated` subjects — are dropped from
|
|
@@ -184,7 +215,7 @@ export class PostgresMigrationPlanner implements MigrationPlanner<'sql', 'postgr
|
|
|
184
215
|
schemaName,
|
|
185
216
|
codecHooks,
|
|
186
217
|
storageTypes,
|
|
187
|
-
schema
|
|
218
|
+
...ifDefined('schema', relationalSchema),
|
|
188
219
|
policy: options.policy,
|
|
189
220
|
frameworkComponents: options.frameworkComponents,
|
|
190
221
|
strategies: postgresPlannerStrategies,
|
|
@@ -194,6 +225,17 @@ export class PostgresMigrationPlanner implements MigrationPlanner<'sql', 'postgr
|
|
|
194
225
|
return plannerFailure(result.failure);
|
|
195
226
|
}
|
|
196
227
|
|
|
228
|
+
const schemaDiffCalls = this.planPostgresSchemaDiff(options, databaseDiff.schemaDiffIssues);
|
|
229
|
+
const schemaDiffPartition = partitionCallsByControlPolicy({
|
|
230
|
+
calls: schemaDiffCalls,
|
|
231
|
+
contract: options.contract,
|
|
232
|
+
resolveControlPolicySubject: (call) =>
|
|
233
|
+
resolvePostgresCallControlPolicySubject(call, options.contract),
|
|
234
|
+
resolveFactoryName: (call) => call.factoryName,
|
|
235
|
+
formatSubjectLabel: (factoryName, subject) =>
|
|
236
|
+
formatPostgresControlPolicySubjectLabel(factoryName, subject, options.contract),
|
|
237
|
+
});
|
|
238
|
+
|
|
197
239
|
// Inline `onFieldEvent`-emitted ops after structural DDL. The fixed
|
|
198
240
|
// ordering is `structural → added → dropped → altered`, with
|
|
199
241
|
// within-group sorting by `(tableName, fieldName)` so re-emits are
|
|
@@ -219,9 +261,10 @@ export class PostgresMigrationPlanner implements MigrationPlanner<'sql', 'postgr
|
|
|
219
261
|
formatSubjectLabel: (factoryName, subject) =>
|
|
220
262
|
formatPostgresControlPolicySubjectLabel(factoryName, subject, options.contract),
|
|
221
263
|
});
|
|
222
|
-
const calls = [...result.value.calls, ...fieldEventPartition.kept];
|
|
264
|
+
const calls = [...result.value.calls, ...schemaDiffPartition.kept, ...fieldEventPartition.kept];
|
|
223
265
|
const warnings: SqlPlannerConflict[] = [
|
|
224
266
|
...issuePartition.warnings,
|
|
267
|
+
...schemaDiffPartition.warnings,
|
|
225
268
|
...fieldEventPartition.warnings,
|
|
226
269
|
];
|
|
227
270
|
|
|
@@ -240,6 +283,59 @@ export class PostgresMigrationPlanner implements MigrationPlanner<'sql', 'postgr
|
|
|
240
283
|
});
|
|
241
284
|
}
|
|
242
285
|
|
|
286
|
+
/**
|
|
287
|
+
* Maps the RLS policy presence findings of the shared
|
|
288
|
+
* `diffPostgresDatabaseSchema` (already ownership-filtered) into
|
|
289
|
+
* `ENABLE RLS` / `CREATE POLICY` / `DROP POLICY` ops. It no longer re-diffs —
|
|
290
|
+
* it consumes the `schemaDiffIssues` of the one combined diff.
|
|
291
|
+
*/
|
|
292
|
+
private planPostgresSchemaDiff(
|
|
293
|
+
options: PlannerOptionsWithComponents,
|
|
294
|
+
filteredDiffIssues: readonly SchemaDiffIssue<SqlSchemaDiffNode>[],
|
|
295
|
+
): readonly PostgresOpFactoryCall[] {
|
|
296
|
+
const allowsDestructive = options.policy.allowedOperationClasses.includes('destructive');
|
|
297
|
+
const calls: PostgresOpFactoryCall[] = [];
|
|
298
|
+
const seenEnableTables = new Set<string>();
|
|
299
|
+
|
|
300
|
+
for (const issue of filteredDiffIssues) {
|
|
301
|
+
// 'mismatch' is unreachable for content-addressed policies: the wire name
|
|
302
|
+
// encodes the body hash, so two policies sharing a local key (same name)
|
|
303
|
+
// are always equal and isEqualTo never returns false.
|
|
304
|
+
if (issue.outcome === 'missing') {
|
|
305
|
+
const expected = issue.expected;
|
|
306
|
+
PostgresPolicySchemaNode.assert(expected);
|
|
307
|
+
// expected.namespaceId is the DDL schema name (resolved during projection);
|
|
308
|
+
// this re-resolution is a no-op as long as PostgresSchema.ddlSchemaName() returns this.id.
|
|
309
|
+
const schemaForTable = resolveDdlSchemaForNamespaceStorage(
|
|
310
|
+
options.contract.storage,
|
|
311
|
+
expected.namespaceId,
|
|
312
|
+
);
|
|
313
|
+
const tableKey = `${schemaForTable}.${expected.tableName}`;
|
|
314
|
+
if (!seenEnableTables.has(tableKey)) {
|
|
315
|
+
seenEnableTables.add(tableKey);
|
|
316
|
+
calls.push(new EnableRowLevelSecurityCall(schemaForTable, expected.tableName));
|
|
317
|
+
}
|
|
318
|
+
calls.push(
|
|
319
|
+
new CreatePostgresRlsPolicyCall(
|
|
320
|
+
schemaForTable,
|
|
321
|
+
expected.tableName,
|
|
322
|
+
policyNodeToContractPolicy(expected),
|
|
323
|
+
),
|
|
324
|
+
);
|
|
325
|
+
} else if (issue.outcome === 'extra' && allowsDestructive) {
|
|
326
|
+
const actual = issue.actual;
|
|
327
|
+
PostgresPolicySchemaNode.assert(actual);
|
|
328
|
+
const schemaForTable = resolveDdlSchemaForNamespaceStorage(
|
|
329
|
+
options.contract.storage,
|
|
330
|
+
actual.namespaceId,
|
|
331
|
+
);
|
|
332
|
+
calls.push(new DropPostgresRlsPolicyCall(schemaForTable, actual.tableName, actual.name));
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
return calls;
|
|
337
|
+
}
|
|
338
|
+
|
|
243
339
|
private ensureAdditivePolicy(policy: MigrationOperationPolicy) {
|
|
244
340
|
if (!policy.allowedOperationClasses.includes('additive')) {
|
|
245
341
|
return plannerFailure([
|
|
@@ -253,34 +349,70 @@ export class PostgresMigrationPlanner implements MigrationPlanner<'sql', 'postgr
|
|
|
253
349
|
return null;
|
|
254
350
|
}
|
|
255
351
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
// Schema presence is a Postgres-specific concern (no equivalent in
|
|
273
|
-
// SQLite / Mongo), so the issue emission lives in the target layer
|
|
274
|
-
// rather than in the family verifier. Stitch it in here so a single
|
|
275
|
-
// `SchemaIssue[]` flows through `planIssues` and the planner emits
|
|
276
|
-
// CREATE SCHEMA in the dep bucket before any CreateTableCall.
|
|
352
|
+
/**
|
|
353
|
+
* The structural issue list `planIssues` consumes: the relational findings
|
|
354
|
+
* from the shared `diffPostgresDatabaseSchema` plus namespace presence.
|
|
355
|
+
*
|
|
356
|
+
* Schema presence (`missing_schema` → `CREATE SCHEMA`) is a planner-only
|
|
357
|
+
* op-generation concern, so it is stitched in here rather than inside the
|
|
358
|
+
* shared diff — verify never needs it (a missing schema already surfaces as
|
|
359
|
+
* `missing_table` in the relational findings). It reads `existingSchemas` off
|
|
360
|
+
* the database root, so it takes the whole tree. Policy drift is handled
|
|
361
|
+
* separately via `planPostgresSchemaDiff` from the same shared diff's
|
|
362
|
+
* `schemaDiffIssues`.
|
|
363
|
+
*/
|
|
364
|
+
private collectSchemaIssues(
|
|
365
|
+
options: PlannerOptionsWithComponents,
|
|
366
|
+
relationalIssues: readonly SchemaIssue[],
|
|
367
|
+
): readonly SchemaIssue[] {
|
|
277
368
|
const namespaceIssues = verifyPostgresNamespacePresence({
|
|
278
369
|
contract: options.contract,
|
|
279
370
|
schema: options.schema,
|
|
280
371
|
});
|
|
281
372
|
if (namespaceIssues.length === 0) {
|
|
282
|
-
return
|
|
373
|
+
return relationalIssues;
|
|
283
374
|
}
|
|
284
|
-
return [...namespaceIssues, ...
|
|
375
|
+
return [...namespaceIssues, ...relationalIssues];
|
|
285
376
|
}
|
|
286
377
|
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Returns the one namespace node whose tables the relational strategy layer
|
|
381
|
+
* probes for live-table existence — the node matching the planner's resolved
|
|
382
|
+
* schema name, or the first namespace when none matches. `undefined` when the
|
|
383
|
+
* tree has no namespaces, so the strategy context uses its empty-schema default.
|
|
384
|
+
*
|
|
385
|
+
* The relational strategies key tables by bare name, so they can only probe one
|
|
386
|
+
* namespace at a time; probing across every namespace at once is future work.
|
|
387
|
+
*/
|
|
388
|
+
function relationalNamespaceNode(
|
|
389
|
+
schema: PostgresDatabaseSchemaNode,
|
|
390
|
+
schemaName: string,
|
|
391
|
+
): SqlSchemaIR | undefined {
|
|
392
|
+
const namespaceNodes = Object.values(schema.namespaces);
|
|
393
|
+
const byName = namespaceNodes.find((node) => node.schemaName === schemaName);
|
|
394
|
+
return byName ?? namespaceNodes[0];
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Rebuilds the `PostgresRlsPolicy` contract entity `CreatePostgresRlsPolicyCall`
|
|
399
|
+
* carries (its `renderTypeScript`/`createRlsPolicy` paths serialize the whole
|
|
400
|
+
* entity, `namespaceId` included). This reconstructs rather than looking the
|
|
401
|
+
* original up in the contract on purpose: the diff node's `namespaceId` is the
|
|
402
|
+
* *resolved DDL schema* (set when the expected tree was built), which is the
|
|
403
|
+
* value the emitted op must carry; the contract-stored entity holds the raw,
|
|
404
|
+
* pre-resolution coordinate, so a lookup would change the migration output.
|
|
405
|
+
*/
|
|
406
|
+
function policyNodeToContractPolicy(node: PostgresPolicySchemaNode): PostgresRlsPolicy {
|
|
407
|
+
return new PostgresRlsPolicy({
|
|
408
|
+
name: node.name,
|
|
409
|
+
prefix: node.prefix,
|
|
410
|
+
tableName: node.tableName,
|
|
411
|
+
namespaceId: node.namespaceId,
|
|
412
|
+
operation: node.operation,
|
|
413
|
+
roles: [...node.roles],
|
|
414
|
+
...ifDefined('using', node.using),
|
|
415
|
+
...ifDefined('withCheck', node.withCheck),
|
|
416
|
+
permissive: node.permissive,
|
|
417
|
+
});
|
|
418
|
+
}
|
|
@@ -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,24 @@
|
|
|
1
|
+
import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir';
|
|
2
|
+
import type { SqlStorage } from '@prisma-next/sql-contract/types';
|
|
3
|
+
import { isPostgresSchema } from '../postgres-schema';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Given the contract's storage and a namespace id, returns the live Postgres
|
|
7
|
+
* DDL schema name that namespace maps to. A named Postgres namespace dispatches
|
|
8
|
+
* to its `ddlSchemaName(storage)`; the unbound sentinel resolves to `public`
|
|
9
|
+
* (the search-path default for offline planning); a bare object payload (used
|
|
10
|
+
* by some tests) falls back to the namespace id itself.
|
|
11
|
+
*/
|
|
12
|
+
export function resolveDdlSchemaForNamespaceStorage(
|
|
13
|
+
storage: SqlStorage,
|
|
14
|
+
namespaceId: string,
|
|
15
|
+
): string {
|
|
16
|
+
if (namespaceId === UNBOUND_NAMESPACE_ID) {
|
|
17
|
+
return 'public';
|
|
18
|
+
}
|
|
19
|
+
const namespace = storage.namespaces[namespaceId];
|
|
20
|
+
if (namespace && isPostgresSchema(namespace)) {
|
|
21
|
+
return namespace.ddlSchemaName(storage);
|
|
22
|
+
}
|
|
23
|
+
return namespaceId;
|
|
24
|
+
}
|
|
@@ -12,7 +12,6 @@ import type {
|
|
|
12
12
|
SqlMigrationRunnerSuccessValue,
|
|
13
13
|
} from '@prisma-next/family-sql/control';
|
|
14
14
|
import { runnerFailure, runnerSuccess } from '@prisma-next/family-sql/control';
|
|
15
|
-
import { verifySqlSchema } from '@prisma-next/family-sql/schema-verify';
|
|
16
15
|
import type { MigrationRunnerResult } from '@prisma-next/framework-components/control';
|
|
17
16
|
import { APP_SPACE_ID } from '@prisma-next/framework-components/control';
|
|
18
17
|
import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir';
|
|
@@ -23,8 +22,6 @@ import { blindCast } from '@prisma-next/utils/casts';
|
|
|
23
22
|
import { ifDefined } from '@prisma-next/utils/defined';
|
|
24
23
|
import type { Result } from '@prisma-next/utils/result';
|
|
25
24
|
import { notOk, ok, okVoid } from '@prisma-next/utils/result';
|
|
26
|
-
import { parsePostgresDefault } from '../default-normalizer';
|
|
27
|
-
import { normalizeSchemaNativeType } from '../native-type-normalizer';
|
|
28
25
|
import type { PostgresPlanTargetDetails } from './planner-target-details';
|
|
29
26
|
|
|
30
27
|
interface ApplyPlanSuccessValue {
|
|
@@ -124,30 +121,30 @@ class PostgresMigrationRunner implements SqlMigrationRunner<PostgresPlanTargetDe
|
|
|
124
121
|
applyValue = applyResult.value;
|
|
125
122
|
}
|
|
126
123
|
|
|
127
|
-
//
|
|
128
|
-
//
|
|
129
|
-
//
|
|
130
|
-
//
|
|
131
|
-
// an extension contract.
|
|
124
|
+
// Verify the schema on app-space only: extension spaces don't own
|
|
125
|
+
// user-facing tables, so verifying the destination contract against the
|
|
126
|
+
// database would flag every app-space table as "extra". Delegates to the
|
|
127
|
+
// family `verifySchema` — the same comparison the CLI verify runs.
|
|
132
128
|
if (space === APP_SPACE_ID) {
|
|
133
|
-
const
|
|
129
|
+
const schemaNode = await this.family.introspect({
|
|
134
130
|
driver,
|
|
135
131
|
contract: options.destinationContract,
|
|
136
132
|
});
|
|
137
|
-
const schemaVerifyResult =
|
|
133
|
+
const schemaVerifyResult = this.family.verifySchema({
|
|
138
134
|
contract: options.destinationContract,
|
|
139
|
-
schema:
|
|
135
|
+
schema: schemaNode,
|
|
140
136
|
strict: options.strictVerification ?? true,
|
|
141
|
-
context: options.context ?? {},
|
|
142
|
-
typeMetadataRegistry: this.family.typeMetadataRegistry,
|
|
143
137
|
frameworkComponents: options.frameworkComponents,
|
|
144
|
-
normalizeDefault: parsePostgresDefault,
|
|
145
|
-
normalizeNativeType: normalizeSchemaNativeType,
|
|
146
138
|
});
|
|
147
139
|
if (!schemaVerifyResult.ok) {
|
|
148
140
|
return runnerFailure('SCHEMA_VERIFY_FAILED', schemaVerifyResult.summary, {
|
|
149
141
|
why: 'The resulting database schema does not satisfy the destination contract.',
|
|
150
|
-
meta: {
|
|
142
|
+
meta: {
|
|
143
|
+
issues: [
|
|
144
|
+
...schemaVerifyResult.schema.issues,
|
|
145
|
+
...schemaVerifyResult.schema.schemaDiffIssues,
|
|
146
|
+
],
|
|
147
|
+
},
|
|
151
148
|
});
|
|
152
149
|
}
|
|
153
150
|
}
|
|
@@ -2,8 +2,9 @@ import type { Contract } from '@prisma-next/contract/types';
|
|
|
2
2
|
import type { SchemaIssue } from '@prisma-next/framework-components/control';
|
|
3
3
|
import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir';
|
|
4
4
|
import type { SqlStorage } from '@prisma-next/sql-contract/types';
|
|
5
|
-
import type {
|
|
5
|
+
import type { SqlSchemaIRNode } from '@prisma-next/sql-schema-ir/types';
|
|
6
6
|
import { isPostgresSchema } from '../postgres-schema';
|
|
7
|
+
import { PostgresDatabaseSchemaNode } from '../schema-ir/postgres-database-schema-node';
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Resolves the live-database schema name for a given namespace
|
|
@@ -23,32 +24,35 @@ function resolveDdlSchemaName(storage: SqlStorage, namespaceId: string): string
|
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
/**
|
|
26
|
-
* Reads the introspected list of schema names from the
|
|
27
|
-
*
|
|
28
|
-
* `
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
27
|
+
* Reads the introspected list of schema names from the database-root schema
|
|
28
|
+
* node. `existingSchemas` is database-level, so it lives on the
|
|
29
|
+
* `PostgresDatabaseSchemaNode` root — not on the per-schema namespace nodes.
|
|
30
|
+
*
|
|
31
|
+
* Defaults to the always-present `public` schema when the node is not the
|
|
32
|
+
* database root — a fresh Postgres database always carries `public` (unless an
|
|
33
|
+
* operator dropped it manually), so any verifier path that runs without an
|
|
34
|
+
* enriched introspection still suppresses the redundant `CREATE SCHEMA
|
|
35
|
+
* "public"`.
|
|
32
36
|
*
|
|
33
37
|
* Production introspection (`PostgresControlAdapter.introspect`) is the
|
|
34
|
-
* authoritative source: it queries `pg_namespace` and
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
* explicitly via the schema IR.
|
|
38
|
+
* authoritative source: it queries `pg_namespace` and sets `existingSchemas`
|
|
39
|
+
* on the returned root. Tests that want to assert against a richer initial
|
|
40
|
+
* state construct a `PostgresDatabaseSchemaNode` explicitly.
|
|
38
41
|
*/
|
|
39
|
-
function existingSchemasFromSchema(schema:
|
|
40
|
-
|
|
41
|
-
.
|
|
42
|
-
const slot = annotations?.pg?.existingSchemas;
|
|
43
|
-
if (Array.isArray(slot)) {
|
|
44
|
-
return slot.filter((s): s is string => typeof s === 'string');
|
|
42
|
+
function existingSchemasFromSchema(schema: SqlSchemaIRNode): readonly string[] {
|
|
43
|
+
if (PostgresDatabaseSchemaNode.is(schema)) {
|
|
44
|
+
return schema.existingSchemas;
|
|
45
45
|
}
|
|
46
46
|
return ['public'];
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
/**
|
|
50
50
|
* Emits a `missing_schema` issue for every contract-declared Postgres
|
|
51
|
-
* namespace whose live container does not yet exist.
|
|
51
|
+
* namespace whose live container does not yet exist. The planner's
|
|
52
|
+
* `collectSchemaIssues` prepends these to the relational findings so a
|
|
53
|
+
* multi-schema plan emits `CREATE SCHEMA` before the tables that need it —
|
|
54
|
+
* a planner-only concern (verify already rejects via the `missing_table` a
|
|
55
|
+
* missing schema produces), so this is not part of the shared diff.
|
|
52
56
|
*
|
|
53
57
|
* A namespace's live container is the schema returned by its
|
|
54
58
|
* polymorphic `ddlSchemaName(storage)` method — named schemas resolve
|
|
@@ -66,7 +70,7 @@ function existingSchemasFromSchema(schema: SqlSchemaIR): readonly string[] {
|
|
|
66
70
|
*/
|
|
67
71
|
export function verifyPostgresNamespacePresence(input: {
|
|
68
72
|
readonly contract: Contract<SqlStorage>;
|
|
69
|
-
readonly schema:
|
|
73
|
+
readonly schema: SqlSchemaIRNode;
|
|
70
74
|
}): readonly SchemaIssue[] {
|
|
71
75
|
const { contract, schema } = input;
|
|
72
76
|
const existing = new Set(existingSchemasFromSchema(schema));
|
|
@@ -79,6 +83,7 @@ export function verifyPostgresNamespacePresence(input: {
|
|
|
79
83
|
if (existing.has(ddlName)) continue;
|
|
80
84
|
issues.push({
|
|
81
85
|
kind: 'missing_schema',
|
|
86
|
+
reason: 'not-found',
|
|
82
87
|
namespaceId,
|
|
83
88
|
message: `Schema "${ddlName}" is missing from database`,
|
|
84
89
|
});
|