@prisma-next/target-postgres 0.14.0-dev.3 → 0.14.0-dev.30
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/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 +11 -10
- 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-CGf6UABz.mjs} +62 -5
- package/dist/ddl-CGf6UABz.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-CzV8-QSu.mjs} +58 -2
- package/dist/default-normalizer-CzV8-QSu.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-Dvvpu_jU.mjs +17 -0
- package/dist/descriptor-meta-Dvvpu_jU.mjs.map +1 -0
- package/dist/descriptor-meta-runtime-D5zHFKUz.mjs +228 -0
- package/dist/descriptor-meta-runtime-D5zHFKUz.mjs.map +1 -0
- package/dist/{issue-planner-DL6g3CmE.mjs → issue-planner-D5H1VuVP.mjs} +11 -9
- package/dist/issue-planner-D5H1VuVP.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 +3 -3
- package/dist/migration.mjs +2 -2
- package/dist/nodes-CSkPX3rI.d.mts +120 -0
- package/dist/nodes-CSkPX3rI.d.mts.map +1 -0
- package/dist/{nodes-Bbhs2rwj.mjs → nodes-Dh__NS58.mjs} +56 -2
- package/dist/nodes-Dh__NS58.mjs.map +1 -0
- package/dist/{op-factory-call-D_p5vxwt.mjs → op-factory-call-DIdBDwmO.mjs} +199 -28
- package/dist/op-factory-call-DIdBDwmO.mjs.map +1 -0
- package/dist/{op-factory-call-DmQEc3XV.d.mts → op-factory-call-DPJR7tJc.d.mts} +44 -13
- package/dist/op-factory-call-DPJR7tJc.d.mts.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-Bs_baQax.mjs → planner-BnNEtGYM.mjs} +156 -24
- package/dist/planner-BnNEtGYM.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-QqHa2C2l.d.mts → planner-produced-postgres-migration-BO-frvvN.d.mts} +3 -3
- package/dist/{planner-produced-postgres-migration-QqHa2C2l.d.mts.map → planner-produced-postgres-migration-BO-frvvN.d.mts.map} +1 -1
- package/dist/{planner-produced-postgres-migration-Cji5vxUf.mjs → planner-produced-postgres-migration-DzvdQ-7K.mjs} +2 -2
- package/dist/{planner-produced-postgres-migration-Cji5vxUf.mjs.map → planner-produced-postgres-migration-DzvdQ-7K.mjs.map} +1 -1
- package/dist/planner-produced-postgres-migration.d.mts +1 -1
- package/dist/planner-produced-postgres-migration.mjs +1 -1
- package/dist/{planner-sql-checks-jqUUGyQR.mjs → planner-sql-checks-CyjaoKhm.mjs} +2 -2
- package/dist/{planner-sql-checks-jqUUGyQR.mjs.map → planner-sql-checks-CyjaoKhm.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 +31 -4
- package/dist/planner.d.mts.map +1 -1
- package/dist/planner.mjs +2 -2
- package/dist/{postgres-contract-serializer-k3TAcPMY.mjs → postgres-contract-serializer-tGMiJcl4.mjs} +30 -21
- package/dist/postgres-contract-serializer-tGMiJcl4.mjs.map +1 -0
- package/dist/{postgres-migration-B5jKrXv3.mjs → postgres-migration-C8JZyTSJ.mjs} +2 -2
- package/dist/{postgres-migration-B5jKrXv3.mjs.map → postgres-migration-C8JZyTSJ.mjs.map} +1 -1
- package/dist/{postgres-migration-Y4YBJqkS.d.mts → postgres-migration-Cs7zMOe1.d.mts} +5 -5
- package/dist/{postgres-migration-Y4YBJqkS.d.mts.map → postgres-migration-Cs7zMOe1.d.mts.map} +1 -1
- package/dist/postgres-rls-policy-12wAEQhs.d.mts +60 -0
- package/dist/postgres-rls-policy-12wAEQhs.d.mts.map +1 -0
- package/dist/postgres-schema-DJUEkWg-.d.mts +190 -0
- package/dist/postgres-schema-DJUEkWg-.d.mts.map +1 -0
- package/dist/{postgres-schema-COGZ1ark.mjs → postgres-schema-DLcc3W2-.mjs} +138 -5
- package/dist/postgres-schema-DLcc3W2-.mjs.map +1 -0
- package/dist/postgres-schema-ir-BJl_a3J7.d.mts +102 -0
- package/dist/postgres-schema-ir-BJl_a3J7.d.mts.map +1 -0
- package/dist/postgres-schema-ir-I_FiUndw.mjs +133 -0
- package/dist/postgres-schema-ir-I_FiUndw.mjs.map +1 -0
- package/dist/postgres-schema-ir-annotations-BbDSiwx2.mjs +14 -0
- package/dist/postgres-schema-ir-annotations-BbDSiwx2.mjs.map +1 -0
- package/dist/render-ops.d.mts +1 -1
- package/dist/rls-canonicalize.d.mts +2 -0
- package/dist/rls-canonicalize.mjs +2 -0
- package/dist/runtime.d.mts +5 -5
- package/dist/runtime.d.mts.map +1 -1
- package/dist/runtime.mjs +2 -2
- package/dist/schema-ir-annotations.d.mts +8 -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 +21 -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 +60 -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-schema-ir.ts +91 -0
- package/src/core/migrations/control-policy.ts +3 -0
- package/src/core/migrations/diff-postgres-schema.ts +55 -0
- package/src/core/migrations/issue-planner.ts +8 -0
- package/src/core/migrations/op-factory-call.ts +106 -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 +84 -1
- package/src/core/migrations/verify-postgres-namespaces.ts +12 -15
- package/src/core/postgres-contract-serializer.ts +48 -33
- package/src/core/postgres-schema.ts +37 -13
- package/src/core/postgres-validators.ts +20 -0
- package/src/core/rls/canonicalize.ts +48 -0
- package/src/core/schema-ir/postgres-rls-policy.ts +101 -0
- package/src/core/schema-ir/postgres-role.ts +52 -0
- package/src/core/schema-ir/postgres-schema-ir-annotations.ts +22 -0
- package/src/core/schema-ir/postgres-schema-ir.ts +138 -0
- package/src/core/schema-ir/postgres-table-ir.ts +96 -0
- package/src/exports/contract-free.ts +7 -1
- package/src/exports/control.ts +11 -12
- package/src/exports/ddl.ts +4 -0
- package/src/exports/planner-ddl-builders.ts +0 -2
- package/src/exports/planner.ts +5 -0
- package/src/exports/rls-canonicalize.ts +6 -0
- package/src/exports/schema-ir-annotations.ts +1 -0
- package/src/exports/types.ts +20 -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.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-schema-COGZ1ark.mjs.map +0 -1
- package/dist/types.d.mts.map +0 -1
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { ExecuteRequestLowerer } from '@prisma-next/family-sql/control-adapter';
|
|
2
|
+
import type { DdlColumn } from '@prisma-next/sql-relational-core/ast';
|
|
3
|
+
import { ifDefined } from '@prisma-next/utils/defined';
|
|
2
4
|
import {
|
|
3
5
|
columnDefaultAst,
|
|
4
6
|
columnExistsAst,
|
|
@@ -7,7 +9,9 @@ import {
|
|
|
7
9
|
noNullValuesAst,
|
|
8
10
|
tableIsEmptyAst,
|
|
9
11
|
} from '../../../contract-free/checks';
|
|
12
|
+
import * as contractFreeDdl from '../../../contract-free/ddl';
|
|
10
13
|
import { quoteIdentifier } from '../../sql-utils';
|
|
14
|
+
import { boundSchema } from '../bound-schema';
|
|
11
15
|
import { qualifyTableName } from '../planner-sql-checks';
|
|
12
16
|
import { type Op, step, targetDetails } from './shared';
|
|
13
17
|
|
|
@@ -242,12 +246,18 @@ export async function dropDefault(
|
|
|
242
246
|
columnName: string,
|
|
243
247
|
lowerer: ExecuteRequestLowerer,
|
|
244
248
|
): Promise<Op> {
|
|
245
|
-
const qualified = qualifyTableName(schemaName, tableName);
|
|
246
249
|
const { present } = await columnExistsSteps(lowerer, {
|
|
247
250
|
schema: schemaName,
|
|
248
251
|
table: tableName,
|
|
249
252
|
column: columnName,
|
|
250
253
|
});
|
|
254
|
+
const dropDefaultExec = await lowerer.lowerToExecuteRequest(
|
|
255
|
+
contractFreeDdl.alterTable({
|
|
256
|
+
...ifDefined('schema', boundSchema(schemaName)),
|
|
257
|
+
table: tableName,
|
|
258
|
+
actions: [contractFreeDdl.dropDefaultAction(columnName)],
|
|
259
|
+
}),
|
|
260
|
+
);
|
|
251
261
|
const noDefault = await lowerer.lowerToExecuteRequest(
|
|
252
262
|
columnDefaultAst({ schema: schemaName, table: tableName, column: columnName }).defaultAbsent(),
|
|
253
263
|
);
|
|
@@ -257,12 +267,7 @@ export async function dropDefault(
|
|
|
257
267
|
operationClass: 'destructive',
|
|
258
268
|
target: targetDetails('column', columnName, schemaName, tableName),
|
|
259
269
|
precheck: [step(`ensure column "${columnName}" exists`, present.sql, present.params)],
|
|
260
|
-
execute: [
|
|
261
|
-
step(
|
|
262
|
-
`drop default on "${columnName}"`,
|
|
263
|
-
`ALTER TABLE ${qualified} ALTER COLUMN ${quoteIdentifier(columnName)} DROP DEFAULT`,
|
|
264
|
-
),
|
|
265
|
-
],
|
|
270
|
+
execute: [step(`drop default on "${columnName}"`, dropDefaultExec.sql)],
|
|
266
271
|
postcheck: [
|
|
267
272
|
step(`verify column "${columnName}" has no default`, noDefault.sql, noDefault.params),
|
|
268
273
|
],
|
|
@@ -272,16 +277,23 @@ export async function dropDefault(
|
|
|
272
277
|
/**
|
|
273
278
|
* Builds the op for adding a NOT NULL column (no contract default) to a
|
|
274
279
|
* non-empty table. Prechecks assert the column is absent and the table is
|
|
275
|
-
* empty; the execute step
|
|
276
|
-
*
|
|
280
|
+
* empty; the execute step lowers the typed `AddColumn` DDL node through the
|
|
281
|
+
* adapter; postchecks assert the column exists and is NOT NULL.
|
|
277
282
|
*/
|
|
278
283
|
export async function addNotNullColumnDirect(
|
|
279
284
|
schemaName: string,
|
|
280
285
|
tableName: string,
|
|
281
|
-
|
|
282
|
-
executeStepSql: string,
|
|
286
|
+
column: DdlColumn,
|
|
283
287
|
lowerer: ExecuteRequestLowerer,
|
|
284
288
|
): Promise<Op> {
|
|
289
|
+
const columnName = column.name;
|
|
290
|
+
const addColumn = await lowerer.lowerToExecuteRequest(
|
|
291
|
+
contractFreeDdl.alterTable({
|
|
292
|
+
...ifDefined('schema', boundSchema(schemaName)),
|
|
293
|
+
table: tableName,
|
|
294
|
+
actions: [contractFreeDdl.addColumnAction(column)],
|
|
295
|
+
}),
|
|
296
|
+
);
|
|
285
297
|
const absent = await lowerer.lowerToExecuteRequest(
|
|
286
298
|
columnExistsAst({ schema: schemaName, table: tableName, column: columnName }).columnAbsent(),
|
|
287
299
|
);
|
|
@@ -311,7 +323,7 @@ export async function addNotNullColumnDirect(
|
|
|
311
323
|
tableEmpty.params,
|
|
312
324
|
),
|
|
313
325
|
],
|
|
314
|
-
execute: [step(`add column "${columnName}"`,
|
|
326
|
+
execute: [step(`add column "${columnName}"`, addColumn.sql)],
|
|
315
327
|
postcheck: [
|
|
316
328
|
step(`verify column "${columnName}" exists`, present.sql, present.params),
|
|
317
329
|
step(`verify column "${columnName}" is NOT NULL`, notNullable.sql, notNullable.params),
|
|
@@ -113,6 +113,12 @@ export async function dataTransform<TContract extends Contract<SqlStorage>>(
|
|
|
113
113
|
runClosures.map((closure) => invokeAndLower(closure, contract, adapter, name)),
|
|
114
114
|
);
|
|
115
115
|
|
|
116
|
+
// Raw remnant: the factory lowered the user's `check` plan above before
|
|
117
|
+
// we get a chance to wrap it, so the EXISTS lifting only has text by the
|
|
118
|
+
// time it runs. The fix is to rewrite the factory to defer lowering until
|
|
119
|
+
// after wrapping — so `cfExpr.exists(checkPlan.ast)` can produce the
|
|
120
|
+
// EXISTS query, then lower once at the boundary — not to grow new AST
|
|
121
|
+
// substrate. Out of scope for the slice that introduced this comment.
|
|
116
122
|
const precheck: readonly SqlMigrationPlanOperationStep[] = checkPlan
|
|
117
123
|
? [
|
|
118
124
|
{
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import type { ExecuteRequestLowerer } from '@prisma-next/family-sql/control-adapter';
|
|
2
|
+
import { ifDefined } from '@prisma-next/utils/defined';
|
|
3
|
+
import { rlsEnabledAst, rlsPolicyExistsAst } from '../../../contract-free/checks';
|
|
4
|
+
import { createPolicy, dropPolicy } from '../../../contract-free/ddl';
|
|
5
|
+
import type { PostgresRlsPolicy } from '../../schema-ir/postgres-rls-policy';
|
|
6
|
+
import { qualifyTableName } from '../planner-sql-checks';
|
|
7
|
+
import { type Op, step, targetDetails } from './shared';
|
|
8
|
+
|
|
9
|
+
const PLAIN_IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_$]*$/;
|
|
10
|
+
|
|
11
|
+
function validateRoleName(role: string): string {
|
|
12
|
+
if (!PLAIN_IDENTIFIER.test(role)) {
|
|
13
|
+
throw new Error(
|
|
14
|
+
`Invalid role name ${JSON.stringify(role)}: role names must be plain SQL identifiers matching ^[A-Za-z_][A-Za-z0-9_$]*$`,
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
return role;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function createRlsPolicy(
|
|
21
|
+
schemaName: string,
|
|
22
|
+
tableName: string,
|
|
23
|
+
policy: PostgresRlsPolicy,
|
|
24
|
+
lowerer: ExecuteRequestLowerer,
|
|
25
|
+
): Promise<Op> {
|
|
26
|
+
const validatedRoles = policy.roles.map(validateRoleName);
|
|
27
|
+
const ddlNode = createPolicy({
|
|
28
|
+
schema: schemaName,
|
|
29
|
+
table: tableName,
|
|
30
|
+
name: policy.name,
|
|
31
|
+
permissive: policy.permissive,
|
|
32
|
+
operation: policy.operation,
|
|
33
|
+
roles: validatedRoles,
|
|
34
|
+
...ifDefined('using', policy.using),
|
|
35
|
+
...ifDefined('withCheck', policy.withCheck),
|
|
36
|
+
});
|
|
37
|
+
const checks = rlsPolicyExistsAst({
|
|
38
|
+
schema: schemaName,
|
|
39
|
+
table: tableName,
|
|
40
|
+
policyName: policy.name,
|
|
41
|
+
});
|
|
42
|
+
const absent = await lowerer.lowerToExecuteRequest(checks.policyAbsent());
|
|
43
|
+
const execute = await lowerer.lowerToExecuteRequest(ddlNode);
|
|
44
|
+
const present = await lowerer.lowerToExecuteRequest(checks.policyPresent());
|
|
45
|
+
return {
|
|
46
|
+
id: `rlsPolicy.${schemaName}.${tableName}.${policy.name}`,
|
|
47
|
+
label: `Create RLS policy "${policy.name}" on "${tableName}"`,
|
|
48
|
+
operationClass: 'additive',
|
|
49
|
+
target: targetDetails('rlsPolicy', policy.name, schemaName, tableName),
|
|
50
|
+
precheck: [
|
|
51
|
+
step(`ensure RLS policy "${policy.name}" does not exist`, absent.sql, absent.params),
|
|
52
|
+
],
|
|
53
|
+
execute: [step(`create RLS policy "${policy.name}"`, execute.sql, execute.params)],
|
|
54
|
+
postcheck: [step(`verify RLS policy "${policy.name}" exists`, present.sql, present.params)],
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export async function dropRlsPolicy(
|
|
59
|
+
schemaName: string,
|
|
60
|
+
tableName: string,
|
|
61
|
+
policyName: string,
|
|
62
|
+
lowerer: ExecuteRequestLowerer,
|
|
63
|
+
): Promise<Op> {
|
|
64
|
+
const ddlNode = dropPolicy({ schema: schemaName, table: tableName, name: policyName });
|
|
65
|
+
const checks = rlsPolicyExistsAst({ schema: schemaName, table: tableName, policyName });
|
|
66
|
+
const present = await lowerer.lowerToExecuteRequest(checks.policyPresent());
|
|
67
|
+
const execute = await lowerer.lowerToExecuteRequest(ddlNode);
|
|
68
|
+
const absent = await lowerer.lowerToExecuteRequest(checks.policyAbsent());
|
|
69
|
+
return {
|
|
70
|
+
id: `rlsPolicy.${schemaName}.${tableName}.${policyName}.drop`,
|
|
71
|
+
label: `Drop RLS policy "${policyName}" on "${tableName}"`,
|
|
72
|
+
operationClass: 'destructive',
|
|
73
|
+
target: targetDetails('rlsPolicy', policyName, schemaName, tableName),
|
|
74
|
+
precheck: [step(`ensure RLS policy "${policyName}" exists`, present.sql, present.params)],
|
|
75
|
+
execute: [step(`drop RLS policy "${policyName}"`, execute.sql, execute.params)],
|
|
76
|
+
postcheck: [step(`verify RLS policy "${policyName}" is absent`, absent.sql, absent.params)],
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export async function enableRowLevelSecurity(
|
|
81
|
+
schemaName: string,
|
|
82
|
+
tableName: string,
|
|
83
|
+
lowerer: ExecuteRequestLowerer,
|
|
84
|
+
): Promise<Op> {
|
|
85
|
+
const checks = rlsEnabledAst(schemaName, tableName);
|
|
86
|
+
const disabled = await lowerer.lowerToExecuteRequest(checks.rlsDisabled());
|
|
87
|
+
const enabled = await lowerer.lowerToExecuteRequest(checks.rlsEnabled());
|
|
88
|
+
return {
|
|
89
|
+
id: `rowLevelSecurity.${schemaName}.${tableName}`,
|
|
90
|
+
label: `Enable row-level security on "${tableName}"`,
|
|
91
|
+
operationClass: 'additive',
|
|
92
|
+
target: targetDetails('rowLevelSecurity', tableName, schemaName),
|
|
93
|
+
precheck: [
|
|
94
|
+
step(`check RLS is not already enabled on "${tableName}"`, disabled.sql, disabled.params),
|
|
95
|
+
],
|
|
96
|
+
execute: [
|
|
97
|
+
step(
|
|
98
|
+
`enable row-level security on "${tableName}"`,
|
|
99
|
+
`ALTER TABLE ${qualifyTableName(schemaName, tableName)} ENABLE ROW LEVEL SECURITY`,
|
|
100
|
+
),
|
|
101
|
+
],
|
|
102
|
+
postcheck: [
|
|
103
|
+
step(`verify row-level security is enabled on "${tableName}"`, enabled.sql, enabled.params),
|
|
104
|
+
],
|
|
105
|
+
};
|
|
106
|
+
}
|
|
@@ -1,30 +1,10 @@
|
|
|
1
1
|
import type { SqlMigrationPlanOperation } from '@prisma-next/family-sql/control';
|
|
2
2
|
import type { ReferentialAction } from '@prisma-next/sql-contract/types';
|
|
3
3
|
import { ifDefined } from '@prisma-next/utils/defined';
|
|
4
|
-
import { quoteIdentifier } from '../../sql-utils';
|
|
5
4
|
import type { OperationClass, PostgresPlanTargetDetails } from '../planner-target-details';
|
|
6
5
|
|
|
7
6
|
export type Op = SqlMigrationPlanOperation<PostgresPlanTargetDetails>;
|
|
8
7
|
|
|
9
|
-
/**
|
|
10
|
-
* Literal-args shape for a column definition consumed by `addColumn` and
|
|
11
|
-
* similar operations. Fully materialized: codec expansion and default
|
|
12
|
-
* rendering have already happened in the wrapper.
|
|
13
|
-
*
|
|
14
|
-
* - `typeSql` is the column's DDL type string (e.g. `"integer"`, `"SERIAL"`,
|
|
15
|
-
* `"varchar(100)"`), already produced by `buildColumnTypeSql` in the
|
|
16
|
-
* call-factory wrapper.
|
|
17
|
-
* - `defaultSql` is the full `DEFAULT …` clause (e.g. `"DEFAULT 42"`) or an
|
|
18
|
-
* empty string when the column has no default, matching
|
|
19
|
-
* `buildColumnDefaultSql`'s output.
|
|
20
|
-
*/
|
|
21
|
-
export interface ColumnSpec {
|
|
22
|
-
readonly name: string;
|
|
23
|
-
readonly typeSql: string;
|
|
24
|
-
readonly defaultSql: string;
|
|
25
|
-
readonly nullable: boolean;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
8
|
/**
|
|
29
9
|
* Literal-args shape for a foreign key definition. `references.schema`
|
|
30
10
|
* carries the target table's namespace (schema) coordinate so the rendered
|
|
@@ -57,13 +37,3 @@ export function targetDetails(
|
|
|
57
37
|
details: { schema, objectType, name, ...ifDefined('table', table) },
|
|
58
38
|
};
|
|
59
39
|
}
|
|
60
|
-
|
|
61
|
-
export function renderColumnDefinition(column: ColumnSpec): string {
|
|
62
|
-
const parts = [
|
|
63
|
-
quoteIdentifier(column.name),
|
|
64
|
-
column.typeSql,
|
|
65
|
-
column.defaultSql,
|
|
66
|
-
column.nullable ? '' : 'NOT NULL',
|
|
67
|
-
].filter(Boolean);
|
|
68
|
-
return parts.join(' ');
|
|
69
|
-
}
|
|
@@ -1,43 +1,10 @@
|
|
|
1
1
|
import type { CodecControlHooks } from '@prisma-next/family-sql/control';
|
|
2
|
-
import type {
|
|
3
|
-
StorageColumn,
|
|
4
|
-
StorageTable,
|
|
5
|
-
StorageTypeInstance,
|
|
6
|
-
} from '@prisma-next/sql-contract/types';
|
|
2
|
+
import type { StorageColumn, StorageTypeInstance } from '@prisma-next/sql-contract/types';
|
|
7
3
|
import { ifDefined } from '@prisma-next/utils/defined';
|
|
8
4
|
import { escapeLiteral, quoteIdentifier } from '../sql-utils';
|
|
9
5
|
import type { PostgresColumnDefault } from '../types';
|
|
10
6
|
import { resolveColumnTypeMetadata } from './planner-type-resolution';
|
|
11
7
|
|
|
12
|
-
export function buildCreateTableSql(
|
|
13
|
-
qualifiedTableName: string,
|
|
14
|
-
table: StorageTable,
|
|
15
|
-
codecHooks: ReadonlyMap<string, CodecControlHooks>,
|
|
16
|
-
storageTypes: Record<string, StorageTypeInstance> = {},
|
|
17
|
-
): string {
|
|
18
|
-
const columnDefinitions = Object.entries(table.columns).map(
|
|
19
|
-
([columnName, column]: [string, StorageColumn]) => {
|
|
20
|
-
const parts = [
|
|
21
|
-
quoteIdentifier(columnName),
|
|
22
|
-
buildColumnTypeSql(column, codecHooks, storageTypes),
|
|
23
|
-
buildColumnDefaultSql(column.default, column),
|
|
24
|
-
column.nullable ? '' : 'NOT NULL',
|
|
25
|
-
].filter(Boolean);
|
|
26
|
-
return parts.join(' ');
|
|
27
|
-
},
|
|
28
|
-
);
|
|
29
|
-
|
|
30
|
-
const constraintDefinitions: string[] = [];
|
|
31
|
-
if (table.primaryKey) {
|
|
32
|
-
constraintDefinitions.push(
|
|
33
|
-
`PRIMARY KEY (${table.primaryKey.columns.map(quoteIdentifier).join(', ')})`,
|
|
34
|
-
);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const allDefinitions = [...columnDefinitions, ...constraintDefinitions];
|
|
38
|
-
return `CREATE TABLE ${qualifiedTableName} (\n ${allDefinitions.join(',\n ')}\n)`;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
8
|
/**
|
|
42
9
|
* Pattern for safe PostgreSQL type names.
|
|
43
10
|
* Allows letters, digits, underscores, spaces (for "double precision", "character varying"),
|
|
@@ -100,15 +67,16 @@ export function buildColumnTypeSql(
|
|
|
100
67
|
|
|
101
68
|
const expanded = expandParameterizedTypeSql(resolved, codecHooks);
|
|
102
69
|
if (expanded !== null) {
|
|
103
|
-
return expanded;
|
|
70
|
+
return column.many ? `${expanded}[]` : expanded;
|
|
104
71
|
}
|
|
105
72
|
|
|
106
73
|
if (column.typeRef) {
|
|
107
|
-
|
|
74
|
+
const base = quoteIdentifier(resolved.nativeType);
|
|
75
|
+
return column.many ? `${base}[]` : base;
|
|
108
76
|
}
|
|
109
77
|
|
|
110
78
|
assertSafeNativeType(resolved.nativeType);
|
|
111
|
-
return resolved.nativeType;
|
|
79
|
+
return column.many ? `${resolved.nativeType}[]` : resolved.nativeType;
|
|
112
80
|
}
|
|
113
81
|
|
|
114
82
|
function expandParameterizedTypeSql(
|
|
@@ -174,6 +142,10 @@ export function buildColumnDefaultSql(
|
|
|
174
142
|
export function renderDefaultLiteral(value: unknown, column?: StorageColumn): string {
|
|
175
143
|
const isJsonColumn = column?.nativeType === 'json' || column?.nativeType === 'jsonb';
|
|
176
144
|
|
|
145
|
+
if (column?.many && Array.isArray(value)) {
|
|
146
|
+
return renderArrayLiteralDefault(value);
|
|
147
|
+
}
|
|
148
|
+
|
|
177
149
|
if (value instanceof Date) {
|
|
178
150
|
return `'${escapeLiteral(value.toISOString())}'`;
|
|
179
151
|
}
|
|
@@ -193,23 +165,10 @@ export function renderDefaultLiteral(value: unknown, column?: StorageColumn): st
|
|
|
193
165
|
return `'${escapeLiteral(json)}'`;
|
|
194
166
|
}
|
|
195
167
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
storageTypes: Record<string, StorageTypeInstance> = {},
|
|
203
|
-
): string {
|
|
204
|
-
const typeSql = buildColumnTypeSql(column, codecHooks, storageTypes);
|
|
205
|
-
const defaultSql =
|
|
206
|
-
buildColumnDefaultSql(column.default, column) ||
|
|
207
|
-
(temporaryDefault ? `DEFAULT ${temporaryDefault}` : '');
|
|
208
|
-
const parts = [
|
|
209
|
-
`ALTER TABLE ${qualifiedTableName}`,
|
|
210
|
-
`ADD COLUMN ${quoteIdentifier(columnName)} ${typeSql}`,
|
|
211
|
-
defaultSql,
|
|
212
|
-
column.nullable ? '' : 'NOT NULL',
|
|
213
|
-
].filter(Boolean);
|
|
214
|
-
return parts.join(' ');
|
|
168
|
+
function renderArrayLiteralDefault(elements: unknown[]): string {
|
|
169
|
+
if (elements.length === 0) {
|
|
170
|
+
return "'{}'";
|
|
171
|
+
}
|
|
172
|
+
const rendered = elements.map((el) => renderDefaultLiteral(el)).join(', ');
|
|
173
|
+
return `ARRAY[${rendered}]`;
|
|
215
174
|
}
|
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
import type { CodecControlHooks, SqlMigrationPlanOperation } from '@prisma-next/family-sql/control';
|
|
2
2
|
import type { ExecuteRequestLowerer } from '@prisma-next/family-sql/control-adapter';
|
|
3
3
|
import type { StorageColumn, StorageTypeInstance } from '@prisma-next/sql-contract/types';
|
|
4
|
+
import { FunctionColumnDefault } from '@prisma-next/sql-relational-core/ast';
|
|
5
|
+
import { col } from '@prisma-next/sql-relational-core/contract-free';
|
|
6
|
+
import { ifDefined } from '@prisma-next/utils/defined';
|
|
4
7
|
import {
|
|
5
8
|
columnDefaultAst,
|
|
6
9
|
columnExistsAst,
|
|
7
10
|
columnNullabilityAst,
|
|
8
11
|
} from '../../contract-free/checks';
|
|
9
|
-
import
|
|
12
|
+
import * as contractFreeDdl from '../../contract-free/ddl';
|
|
13
|
+
import { boundSchema } from './bound-schema';
|
|
10
14
|
import { step } from './operations/shared';
|
|
11
|
-
import {
|
|
12
|
-
import { qualifyTableName } from './planner-sql-checks';
|
|
15
|
+
import { buildColumnTypeSql } from './planner-ddl-builders';
|
|
13
16
|
import { buildTargetDetails, type PostgresPlanTargetDetails } from './planner-target-details';
|
|
14
17
|
|
|
15
18
|
export function buildAddColumnOperationIdentity(
|
|
@@ -51,7 +54,29 @@ export async function buildAddNotNullColumnWithTemporaryDefaultOperation(options
|
|
|
51
54
|
temporaryDefault,
|
|
52
55
|
lowerer,
|
|
53
56
|
} = options;
|
|
54
|
-
|
|
57
|
+
|
|
58
|
+
// The recipe handles NOT NULL columns that carry no contract default, so the
|
|
59
|
+
// temporary backfill value is the only default. It is a pre-rendered SQL
|
|
60
|
+
// fragment (e.g. `''`, `0`, `'{}'::jsonb`), carried verbatim as a
|
|
61
|
+
// `FunctionColumnDefault` so the adapter emits it as a `DEFAULT (...)` clause.
|
|
62
|
+
const ddlColumn = col(columnName, buildColumnTypeSql(column, codecHooks, storageTypes), {
|
|
63
|
+
notNull: true,
|
|
64
|
+
default: new FunctionColumnDefault(temporaryDefault),
|
|
65
|
+
});
|
|
66
|
+
const addColumn = await lowerer.lowerToExecuteRequest(
|
|
67
|
+
contractFreeDdl.alterTable({
|
|
68
|
+
...ifDefined('schema', boundSchema(schema)),
|
|
69
|
+
table: tableName,
|
|
70
|
+
actions: [contractFreeDdl.addColumnAction(ddlColumn)],
|
|
71
|
+
}),
|
|
72
|
+
);
|
|
73
|
+
const dropTempDefault = await lowerer.lowerToExecuteRequest(
|
|
74
|
+
contractFreeDdl.alterTable({
|
|
75
|
+
...ifDefined('schema', boundSchema(schema)),
|
|
76
|
+
table: tableName,
|
|
77
|
+
actions: [contractFreeDdl.dropDefaultAction(columnName)],
|
|
78
|
+
}),
|
|
79
|
+
);
|
|
55
80
|
|
|
56
81
|
const absent = await lowerer.lowerToExecuteRequest(
|
|
57
82
|
columnExistsAst({ schema, table: tableName, column: columnName }).columnAbsent(),
|
|
@@ -73,18 +98,11 @@ export async function buildAddNotNullColumnWithTemporaryDefaultOperation(options
|
|
|
73
98
|
execute: [
|
|
74
99
|
{
|
|
75
100
|
description: `add column "${columnName}"`,
|
|
76
|
-
sql:
|
|
77
|
-
qualified,
|
|
78
|
-
columnName,
|
|
79
|
-
column,
|
|
80
|
-
codecHooks,
|
|
81
|
-
temporaryDefault,
|
|
82
|
-
storageTypes,
|
|
83
|
-
),
|
|
101
|
+
sql: addColumn.sql,
|
|
84
102
|
},
|
|
85
103
|
{
|
|
86
104
|
description: `drop temporary default from column "${columnName}"`,
|
|
87
|
-
sql:
|
|
105
|
+
sql: dropTempDefault.sql,
|
|
88
106
|
},
|
|
89
107
|
],
|
|
90
108
|
postcheck: [
|
|
@@ -53,10 +53,10 @@ import {
|
|
|
53
53
|
RawSqlCall,
|
|
54
54
|
SetNotNullCall,
|
|
55
55
|
} from './op-factory-call';
|
|
56
|
-
import {
|
|
56
|
+
import { buildColumnTypeSql } from './planner-ddl-builders';
|
|
57
57
|
import { resolveIdentityValue } from './planner-identity-values';
|
|
58
58
|
import { buildSchemaLookupMap, hasForeignKey, hasUniqueConstraint } from './planner-schema-lookup';
|
|
59
|
-
import { buildExpectedFormatType
|
|
59
|
+
import { buildExpectedFormatType } from './planner-sql-checks';
|
|
60
60
|
import { buildTargetDetails, type PostgresPlanTargetDetails } from './planner-target-details';
|
|
61
61
|
import { resolveColumnTypeMetadata } from './planner-type-resolution';
|
|
62
62
|
|
|
@@ -600,20 +600,12 @@ export const notNullAddColumnCallStrategy: CallMigrationStrategy = (issues, ctx)
|
|
|
600
600
|
continue;
|
|
601
601
|
}
|
|
602
602
|
|
|
603
|
-
const qualified = qualifyTableName(schemaForTable, issue.table);
|
|
604
603
|
calls.push(
|
|
605
604
|
new AddNotNullColumnDirectCall(
|
|
606
605
|
schemaForTable,
|
|
607
606
|
issue.table,
|
|
608
607
|
issue.column,
|
|
609
|
-
|
|
610
|
-
qualified,
|
|
611
|
-
issue.column,
|
|
612
|
-
column,
|
|
613
|
-
mutableCodecHooks,
|
|
614
|
-
undefined,
|
|
615
|
-
mutableStorageTypes,
|
|
616
|
-
),
|
|
608
|
+
buildColumnSpec(namespaceId, issue.table, issue.column, ctx),
|
|
617
609
|
),
|
|
618
610
|
);
|
|
619
611
|
}
|
|
@@ -25,14 +25,25 @@ import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir';
|
|
|
25
25
|
import { blindCast } from '@prisma-next/utils/casts';
|
|
26
26
|
import { parsePostgresDefault } from '../default-normalizer';
|
|
27
27
|
import { normalizeSchemaNativeType } from '../native-type-normalizer';
|
|
28
|
+
import type { PostgresContract } from '../postgres-schema';
|
|
29
|
+
import { assertPostgresRlsPolicy } from '../schema-ir/postgres-rls-policy';
|
|
30
|
+
import { assertPostgresSchemaIR, ensurePostgresSchemaIR } from '../schema-ir/postgres-schema-ir';
|
|
31
|
+
import { resolveDdlSchemaForNamespaceStorage } from '../schema-ir/postgres-schema-ir-annotations';
|
|
32
|
+
import { contractToPostgresSchemaIR } from './contract-to-postgres-schema-ir';
|
|
28
33
|
import {
|
|
29
34
|
formatPostgresControlPolicySubjectLabel,
|
|
30
35
|
resolvePostgresCallControlPolicySubject,
|
|
31
36
|
resolvePostgresIssueControlPolicySubject,
|
|
32
37
|
resolvePostgresIssueCreationFactoryName,
|
|
33
38
|
} from './control-policy';
|
|
39
|
+
import { diffPostgresSchema, filterIssuesByOwnership } from './diff-postgres-schema';
|
|
34
40
|
import { planIssues } from './issue-planner';
|
|
35
41
|
import type { PostgresOpFactoryCall } from './op-factory-call';
|
|
42
|
+
import {
|
|
43
|
+
CreatePostgresRlsPolicyCall,
|
|
44
|
+
DropPostgresRlsPolicyCall,
|
|
45
|
+
EnableRowLevelSecurityCall,
|
|
46
|
+
} from './op-factory-call';
|
|
36
47
|
import { TypeScriptRenderablePostgresMigration } from './planner-produced-postgres-migration';
|
|
37
48
|
import { postgresPlannerStrategies } from './planner-strategies';
|
|
38
49
|
import { verifyPostgresNamespacePresence } from './verify-postgres-namespaces';
|
|
@@ -194,6 +205,17 @@ export class PostgresMigrationPlanner implements MigrationPlanner<'sql', 'postgr
|
|
|
194
205
|
return plannerFailure(result.failure);
|
|
195
206
|
}
|
|
196
207
|
|
|
208
|
+
const schemaDiffCalls = this.planPostgresSchemaDiff(options);
|
|
209
|
+
const schemaDiffPartition = partitionCallsByControlPolicy({
|
|
210
|
+
calls: schemaDiffCalls,
|
|
211
|
+
contract: options.contract,
|
|
212
|
+
resolveControlPolicySubject: (call) =>
|
|
213
|
+
resolvePostgresCallControlPolicySubject(call, options.contract),
|
|
214
|
+
resolveFactoryName: (call) => call.factoryName,
|
|
215
|
+
formatSubjectLabel: (factoryName, subject) =>
|
|
216
|
+
formatPostgresControlPolicySubjectLabel(factoryName, subject, options.contract),
|
|
217
|
+
});
|
|
218
|
+
|
|
197
219
|
// Inline `onFieldEvent`-emitted ops after structural DDL. The fixed
|
|
198
220
|
// ordering is `structural → added → dropped → altered`, with
|
|
199
221
|
// within-group sorting by `(tableName, fieldName)` so re-emits are
|
|
@@ -219,9 +241,10 @@ export class PostgresMigrationPlanner implements MigrationPlanner<'sql', 'postgr
|
|
|
219
241
|
formatSubjectLabel: (factoryName, subject) =>
|
|
220
242
|
formatPostgresControlPolicySubjectLabel(factoryName, subject, options.contract),
|
|
221
243
|
});
|
|
222
|
-
const calls = [...result.value.calls, ...fieldEventPartition.kept];
|
|
244
|
+
const calls = [...result.value.calls, ...schemaDiffPartition.kept, ...fieldEventPartition.kept];
|
|
223
245
|
const warnings: SqlPlannerConflict[] = [
|
|
224
246
|
...issuePartition.warnings,
|
|
247
|
+
...schemaDiffPartition.warnings,
|
|
225
248
|
...fieldEventPartition.warnings,
|
|
226
249
|
];
|
|
227
250
|
|
|
@@ -240,6 +263,65 @@ export class PostgresMigrationPlanner implements MigrationPlanner<'sql', 'postgr
|
|
|
240
263
|
});
|
|
241
264
|
}
|
|
242
265
|
|
|
266
|
+
private planPostgresSchemaDiff(
|
|
267
|
+
options: PlannerOptionsWithComponents,
|
|
268
|
+
): readonly PostgresOpFactoryCall[] {
|
|
269
|
+
assertPostgresSchemaIR(options.schema);
|
|
270
|
+
const expected = contractToPostgresSchemaIR(
|
|
271
|
+
blindCast<PostgresContract, 'planPostgresSchemaDiff is only called with a postgres contract'>(
|
|
272
|
+
options.contract,
|
|
273
|
+
),
|
|
274
|
+
{ annotationNamespace: 'pg' },
|
|
275
|
+
);
|
|
276
|
+
const actual = ensurePostgresSchemaIR(options.schema);
|
|
277
|
+
const rawIssues = diffPostgresSchema(expected, actual);
|
|
278
|
+
const ownedSchemaNames = new Set([
|
|
279
|
+
...expected.rlsPolicies.map((p) => p.namespaceId),
|
|
280
|
+
...expected.existingSchemas,
|
|
281
|
+
]);
|
|
282
|
+
const filteredDiffIssues = filterIssuesByOwnership(rawIssues, ownedSchemaNames);
|
|
283
|
+
|
|
284
|
+
const allowsDestructive = options.policy.allowedOperationClasses.includes('destructive');
|
|
285
|
+
const calls: PostgresOpFactoryCall[] = [];
|
|
286
|
+
const seenEnableTables = new Set<string>();
|
|
287
|
+
|
|
288
|
+
for (const issue of filteredDiffIssues) {
|
|
289
|
+
// 'mismatch' is unreachable for content-addressed policies: the wire name
|
|
290
|
+
// encodes the body hash, so two policies sharing a local key (same name)
|
|
291
|
+
// are always equal and isEqualTo never returns false.
|
|
292
|
+
if (issue.outcome === 'missing') {
|
|
293
|
+
assertPostgresRlsPolicy(issue.expected);
|
|
294
|
+
// issue.expected.namespaceId is the DDL schema name (resolved during projection);
|
|
295
|
+
// this re-resolution is a no-op as long as PostgresSchema.ddlSchemaName() returns this.id.
|
|
296
|
+
const schemaForTable = resolveDdlSchemaForNamespaceStorage(
|
|
297
|
+
options.contract.storage,
|
|
298
|
+
issue.expected.namespaceId,
|
|
299
|
+
options.schema,
|
|
300
|
+
);
|
|
301
|
+
const tableKey = `${schemaForTable}.${issue.expected.tableName}`;
|
|
302
|
+
if (!seenEnableTables.has(tableKey)) {
|
|
303
|
+
seenEnableTables.add(tableKey);
|
|
304
|
+
calls.push(new EnableRowLevelSecurityCall(schemaForTable, issue.expected.tableName));
|
|
305
|
+
}
|
|
306
|
+
calls.push(
|
|
307
|
+
new CreatePostgresRlsPolicyCall(schemaForTable, issue.expected.tableName, issue.expected),
|
|
308
|
+
);
|
|
309
|
+
} else if (issue.outcome === 'extra' && allowsDestructive) {
|
|
310
|
+
assertPostgresRlsPolicy(issue.actual);
|
|
311
|
+
const schemaForTable = resolveDdlSchemaForNamespaceStorage(
|
|
312
|
+
options.contract.storage,
|
|
313
|
+
issue.actual.namespaceId,
|
|
314
|
+
options.schema,
|
|
315
|
+
);
|
|
316
|
+
calls.push(
|
|
317
|
+
new DropPostgresRlsPolicyCall(schemaForTable, issue.actual.tableName, issue.actual.name),
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
return calls;
|
|
323
|
+
}
|
|
324
|
+
|
|
243
325
|
private ensureAdditivePolicy(policy: MigrationOperationPolicy) {
|
|
244
326
|
if (!policy.allowedOperationClasses.includes('additive')) {
|
|
245
327
|
return plannerFailure([
|
|
@@ -274,6 +356,7 @@ export class PostgresMigrationPlanner implements MigrationPlanner<'sql', 'postgr
|
|
|
274
356
|
// rather than in the family verifier. Stitch it in here so a single
|
|
275
357
|
// `SchemaIssue[]` flows through `planIssues` and the planner emits
|
|
276
358
|
// CREATE SCHEMA in the dep bucket before any CreateTableCall.
|
|
359
|
+
// Schema drift is handled separately via diffPostgresSchema → planPostgresSchemaDiff.
|
|
277
360
|
const namespaceIssues = verifyPostgresNamespacePresence({
|
|
278
361
|
contract: options.contract,
|
|
279
362
|
schema: options.schema,
|
|
@@ -4,6 +4,7 @@ import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir';
|
|
|
4
4
|
import type { SqlStorage } from '@prisma-next/sql-contract/types';
|
|
5
5
|
import type { SqlSchemaIR } from '@prisma-next/sql-schema-ir/types';
|
|
6
6
|
import { isPostgresSchema } from '../postgres-schema';
|
|
7
|
+
import { isPostgresSchemaIR } from '../schema-ir/postgres-schema-ir';
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Resolves the live-database schema name for a given namespace
|
|
@@ -23,25 +24,21 @@ function resolveDdlSchemaName(storage: SqlStorage, namespaceId: string): string
|
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
/**
|
|
26
|
-
* Reads the introspected list of schema names from
|
|
27
|
-
*
|
|
28
|
-
* `
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
27
|
+
* Reads the introspected list of schema names from a `PostgresSchemaIR`.
|
|
28
|
+
* Defaults to the always-present `public` schema when the schema IR is not a
|
|
29
|
+
* `PostgresSchemaIR` — a fresh Postgres database always carries `public`
|
|
30
|
+
* (unless an operator dropped it manually), so any verifier path that runs
|
|
31
|
+
* without an enriched introspection still suppresses the redundant
|
|
32
|
+
* `CREATE SCHEMA "public"`.
|
|
32
33
|
*
|
|
33
34
|
* Production introspection (`PostgresControlAdapter.introspect`) is the
|
|
34
|
-
* authoritative source: it queries `pg_namespace` and
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
* explicitly via the schema IR.
|
|
35
|
+
* authoritative source: it queries `pg_namespace` and sets `existingSchemas`
|
|
36
|
+
* on the returned `PostgresSchemaIR`. Tests that want to assert against a
|
|
37
|
+
* richer initial state construct a `PostgresSchemaIR` explicitly.
|
|
38
38
|
*/
|
|
39
39
|
function existingSchemasFromSchema(schema: SqlSchemaIR): readonly string[] {
|
|
40
|
-
|
|
41
|
-
.
|
|
42
|
-
const slot = annotations?.pg?.existingSchemas;
|
|
43
|
-
if (Array.isArray(slot)) {
|
|
44
|
-
return slot.filter((s): s is string => typeof s === 'string');
|
|
40
|
+
if (isPostgresSchemaIR(schema)) {
|
|
41
|
+
return schema.existingSchemas;
|
|
45
42
|
}
|
|
46
43
|
return ['public'];
|
|
47
44
|
}
|