@prisma-next/target-postgres 0.14.0-dev.54 → 0.14.0-dev.56
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-Bd9Tt3E3.mjs → authoring-WkqG_MkU.mjs} +38 -6
- package/dist/authoring-WkqG_MkU.mjs.map +1 -0
- package/dist/contract-free.d.mts +27 -2
- package/dist/contract-free.d.mts.map +1 -1
- package/dist/contract-free.mjs +3 -3
- package/dist/{control-policy-CafkzSUC.mjs → control-policy-BtOZBVxL.mjs} +38 -13
- package/dist/control-policy-BtOZBVxL.mjs.map +1 -0
- package/dist/control.mjs +6 -6
- package/dist/{ddl-CZNFMoCp.mjs → ddl-B-krEXY1.mjs} +18 -4
- package/dist/{ddl-CZNFMoCp.mjs.map → ddl-B-krEXY1.mjs.map} +1 -1
- package/dist/ddl.d.mts +2 -2
- package/dist/ddl.mjs +2 -2
- package/dist/{descriptor-meta-_pn08mka.mjs → descriptor-meta-BOcKPaLE.mjs} +4 -3
- package/dist/descriptor-meta-BOcKPaLE.mjs.map +1 -0
- package/dist/{diff-database-schema-CUSoT1Dk.mjs → diff-database-schema-CWb-jeLy.mjs} +19 -11
- package/dist/diff-database-schema-CWb-jeLy.mjs.map +1 -0
- package/dist/diff-database-schema.d.mts +1 -1
- 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-Dh__NS58.mjs → nodes-B815pj_X.mjs} +34 -2
- package/dist/{nodes-Dh__NS58.mjs.map → nodes-B815pj_X.mjs.map} +1 -1
- package/dist/{nodes-CSkPX3rI.d.mts → nodes-BtQyZha6.d.mts} +29 -3
- package/dist/nodes-BtQyZha6.d.mts.map +1 -0
- package/dist/{op-factory-call-Dt-Jx5JS.d.mts → op-factory-call-D6_7-7Vc.d.mts} +24 -2
- package/dist/{op-factory-call-Dt-Jx5JS.d.mts.map → op-factory-call-D6_7-7Vc.d.mts.map} +1 -1
- package/dist/{op-factory-call-zo3HqJiO.mjs → op-factory-call-DxNNKUeZ.mjs} +99 -4
- package/dist/op-factory-call-DxNNKUeZ.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 +29 -2
- package/dist/pack.d.mts.map +1 -1
- package/dist/pack.mjs +1 -1
- package/dist/{planner-ChE2d2-k.mjs → planner-BryLk0Ie.mjs} +80 -24
- package/dist/planner-BryLk0Ie.mjs.map +1 -0
- package/dist/{planner-produced-postgres-migration-qTr4KVlt.mjs → planner-produced-postgres-migration-BLJH1zIc.mjs} +2 -2
- package/dist/{planner-produced-postgres-migration-qTr4KVlt.mjs.map → planner-produced-postgres-migration-BLJH1zIc.mjs.map} +1 -1
- package/dist/{planner-produced-postgres-migration-C0a7I1-e.d.mts → planner-produced-postgres-migration-CGifB6VH.d.mts} +2 -2
- package/dist/{planner-produced-postgres-migration-C0a7I1-e.d.mts.map → planner-produced-postgres-migration-CGifB6VH.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-q58sWl7M.mjs → planner-sql-checks-AXoxB1Jl.mjs} +2 -2
- package/dist/{planner-sql-checks-q58sWl7M.mjs.map → planner-sql-checks-AXoxB1Jl.mjs.map} +1 -1
- package/dist/planner-sql-checks.mjs +1 -1
- package/dist/planner.d.mts +23 -6
- package/dist/planner.d.mts.map +1 -1
- package/dist/planner.mjs +2 -2
- package/dist/{postgres-contract-view-BUjK3NeM.mjs → postgres-contract-view-Dg-N7KA7.mjs} +4 -3
- package/dist/postgres-contract-view-Dg-N7KA7.mjs.map +1 -0
- package/dist/{postgres-database-schema-node-UpzA0Ug5.d.mts → postgres-database-schema-node-CfdTY-Qs.d.mts} +18 -3
- package/dist/postgres-database-schema-node-CfdTY-Qs.d.mts.map +1 -0
- package/dist/{postgres-migration-DqGd8mAu.mjs → postgres-migration-BahMHTbj.mjs} +3 -3
- package/dist/{postgres-migration-DqGd8mAu.mjs.map → postgres-migration-BahMHTbj.mjs.map} +1 -1
- package/dist/{postgres-migration-DFaxuRyI.d.mts → postgres-migration-cyBe2Ee0.d.mts} +2 -2
- package/dist/{postgres-migration-DFaxuRyI.d.mts.map → postgres-migration-cyBe2Ee0.d.mts.map} +1 -1
- package/dist/{postgres-table-schema-node-BZWWEo7B.mjs → postgres-role-schema-node-BiSTCTNg.mjs} +4 -69
- package/dist/postgres-role-schema-node-BiSTCTNg.mjs.map +1 -0
- package/dist/{postgres-schema-9vFZLwGo.d.mts → postgres-schema-CU4gF6QZ.d.mts} +30 -2
- package/dist/postgres-schema-CU4gF6QZ.d.mts.map +1 -0
- package/dist/{postgres-schema-CsOBpCwV.mjs → postgres-schema-DTLA_aRi.mjs} +43 -13
- package/dist/postgres-schema-DTLA_aRi.mjs.map +1 -0
- package/dist/postgres-table-schema-node-9FCtCUz2.mjs +153 -0
- package/dist/postgres-table-schema-node-9FCtCUz2.mjs.map +1 -0
- package/dist/{resolve-ddl-schema-gmgUaHK3.mjs → resolve-ddl-schema-Ca88Tg6W.mjs} +2 -2
- package/dist/{resolve-ddl-schema-gmgUaHK3.mjs.map → resolve-ddl-schema-Ca88Tg6W.mjs.map} +1 -1
- package/dist/rls-canonicalize.d.mts +19 -1
- package/dist/rls-canonicalize.d.mts.map +1 -0
- package/dist/rls-canonicalize.mjs +2 -2
- package/dist/runtime.d.mts.map +1 -1
- package/dist/runtime.mjs +1 -1
- package/dist/schema-ir-annotations.mjs +1 -1
- package/dist/types.d.mts +3 -3
- package/dist/types.mjs +4 -3
- package/dist/{canonicalize-Bn3BM1Cn.mjs → wire-name-rHHtr1h0.mjs} +31 -2
- package/dist/wire-name-rHHtr1h0.mjs.map +1 -0
- package/package.json +20 -20
- package/src/contract-free/ddl.ts +26 -0
- package/src/core/authoring.ts +44 -1
- package/src/core/ddl/nodes.ts +48 -1
- package/src/core/descriptor-meta.ts +2 -0
- package/src/core/entity-kinds.ts +11 -0
- package/src/core/migrations/contract-to-postgres-database-schema-node.ts +9 -0
- package/src/core/migrations/control-policy.ts +31 -1
- package/src/core/migrations/diff-database-schema.ts +1 -0
- package/src/core/migrations/issue-planner.ts +49 -4
- package/src/core/migrations/op-factory-call.ts +81 -1
- package/src/core/migrations/operations/rls.ts +82 -1
- package/src/core/migrations/planner.ts +110 -26
- package/src/core/postgres-contract-serializer.ts +9 -4
- package/src/core/postgres-rls-enablement.ts +34 -0
- package/src/core/postgres-schema.ts +19 -21
- package/src/core/postgres-validators.ts +6 -0
- package/src/core/rls/wire-name.ts +33 -0
- package/src/core/schema-ir/postgres-table-schema-node.ts +25 -3
- package/src/exports/contract-free.ts +4 -0
- package/src/exports/ddl.ts +2 -0
- package/src/exports/rls-canonicalize.ts +1 -0
- package/src/exports/types.ts +4 -0
- package/dist/authoring-Bd9Tt3E3.mjs.map +0 -1
- package/dist/canonicalize-Bn3BM1Cn.mjs.map +0 -1
- package/dist/control-policy-CafkzSUC.mjs.map +0 -1
- package/dist/descriptor-meta-_pn08mka.mjs.map +0 -1
- package/dist/diff-database-schema-CUSoT1Dk.mjs.map +0 -1
- package/dist/nodes-CSkPX3rI.d.mts.map +0 -1
- package/dist/op-factory-call-zo3HqJiO.mjs.map +0 -1
- package/dist/planner-ChE2d2-k.mjs.map +0 -1
- package/dist/postgres-contract-view-BUjK3NeM.mjs.map +0 -1
- package/dist/postgres-database-schema-node-UpzA0Ug5.d.mts.map +0 -1
- package/dist/postgres-schema-9vFZLwGo.d.mts.map +0 -1
- package/dist/postgres-schema-CsOBpCwV.mjs.map +0 -1
- package/dist/postgres-table-schema-node-BZWWEo7B.mjs.map +0 -1
- package/dist/schema-node-kinds-ClScchhi.mjs +0 -76
- package/dist/schema-node-kinds-ClScchhi.mjs.map +0 -1
|
@@ -117,6 +117,9 @@ export function contractToPostgresDatabaseSchemaNode(
|
|
|
117
117
|
...ifDefined('annotations', sqlTable.annotations),
|
|
118
118
|
...ifDefined('checks', sqlTable.checks),
|
|
119
119
|
policies: policiesByTable.get(tableName) ?? [],
|
|
120
|
+
// Marker-driven, never derived from the policy set: the `rls` entry
|
|
121
|
+
// is the single authored source of enablement.
|
|
122
|
+
rlsEnabled: Object.hasOwn(ns.rls, tableName),
|
|
120
123
|
});
|
|
121
124
|
}
|
|
122
125
|
|
|
@@ -127,6 +130,12 @@ export function contractToPostgresDatabaseSchemaNode(
|
|
|
127
130
|
`contract-to-postgres-database-schema-node: policy "${policyName}" references table "${tableName}" not present in namespace "${ddlSchema}"`,
|
|
128
131
|
);
|
|
129
132
|
}
|
|
133
|
+
if (!Object.hasOwn(ns.rls, tableName)) {
|
|
134
|
+
const policyPrefix = tablePolicies[0]?.prefix ?? '(unknown)';
|
|
135
|
+
throw new Error(
|
|
136
|
+
`contract-to-postgres-database-schema-node: policy "${policyPrefix}" targets table "${tableName}" in namespace "${ddlSchema}", which is not RLS-controlled. Mark the model with @@rls (entries.rls["${tableName}"]) or remove the policy.`,
|
|
137
|
+
);
|
|
138
|
+
}
|
|
130
139
|
}
|
|
131
140
|
|
|
132
141
|
namespaces[ddlSchema] = new PostgresNamespaceSchemaNode({
|
|
@@ -7,6 +7,7 @@ import { blindCast } from '@prisma-next/utils/casts';
|
|
|
7
7
|
import { ifDefined } from '@prisma-next/utils/defined';
|
|
8
8
|
import { isPostgresSchema } from '../postgres-schema';
|
|
9
9
|
import type { PostgresNamespaceSchemaNode } from '../schema-ir/postgres-namespace-schema-node';
|
|
10
|
+
import { PostgresTableSchemaNode } from '../schema-ir/postgres-table-schema-node';
|
|
10
11
|
import type { SqlSchemaDiffNode } from '../schema-ir/schema-node-kinds';
|
|
11
12
|
import { PostgresSchemaNodeKind } from '../schema-ir/schema-node-kinds';
|
|
12
13
|
import { issueColumnName } from './issue-planner';
|
|
@@ -163,9 +164,37 @@ const POSTGRES_NODE_CREATION_FACTORY: Readonly<Record<string, string>> = Object.
|
|
|
163
164
|
[PostgresSchemaNodeKind.table]: 'createTable',
|
|
164
165
|
});
|
|
165
166
|
|
|
167
|
+
/**
|
|
168
|
+
* 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 per `OBJECT_CREATION_FACTORIES` ('enableRowLevelSecurity'
|
|
171
|
+
* is a member): enabling RLS establishes the fail-closed guard the declared
|
|
172
|
+
* policy set attaches to, the same grant `tolerated` extends to creating the
|
|
173
|
+
* policies themselves. The opposite direction (`DISABLE`) is a modification
|
|
174
|
+
* and stays managed-only. Keying on the actual delta (not just the expected
|
|
175
|
+
* bit) keeps this correct if a second table attribute ever joins
|
|
176
|
+
* `isEqualTo`: a not-equal with no `rlsEnabled` delta is not enablement, so
|
|
177
|
+
* it is not admitted as creation-class here.
|
|
178
|
+
*/
|
|
179
|
+
function isEnablementCreationIssue(issue: SchemaDiffIssue<SqlSchemaDiffNode>): boolean {
|
|
180
|
+
if (issue.reason !== 'not-equal') return false;
|
|
181
|
+
const { expected, actual } = issue;
|
|
182
|
+
return (
|
|
183
|
+
expected !== undefined &&
|
|
184
|
+
actual !== undefined &&
|
|
185
|
+
PostgresTableSchemaNode.is(expected) &&
|
|
186
|
+
PostgresTableSchemaNode.is(actual) &&
|
|
187
|
+
expected.rlsEnabled === true &&
|
|
188
|
+
actual.rlsEnabled === false
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
|
|
166
192
|
export function resolvePostgresNodeIssueCreationFactoryName(
|
|
167
193
|
issue: SchemaDiffIssue<SqlSchemaDiffNode>,
|
|
168
194
|
): string | undefined {
|
|
195
|
+
if (isEnablementCreationIssue(issue)) {
|
|
196
|
+
return 'enableRowLevelSecurity';
|
|
197
|
+
}
|
|
169
198
|
if (issue.reason !== 'not-found') return undefined;
|
|
170
199
|
const node = issue.expected ?? issue.actual;
|
|
171
200
|
if (node === undefined) return undefined;
|
|
@@ -215,7 +244,8 @@ export function resolvePostgresNodeIssueControlPolicySubject(
|
|
|
215
244
|
entityName: tableName,
|
|
216
245
|
});
|
|
217
246
|
const createsNewObject =
|
|
218
|
-
node.nodeKind === PostgresSchemaNodeKind.table && issue.reason === 'not-found'
|
|
247
|
+
(node.nodeKind === PostgresSchemaNodeKind.table && issue.reason === 'not-found') ||
|
|
248
|
+
isEnablementCreationIssue(issue);
|
|
219
249
|
|
|
220
250
|
return {
|
|
221
251
|
namespaceId,
|
|
@@ -70,6 +70,7 @@ export function normalizePostgresActualForDiff(
|
|
|
70
70
|
...ifDefined('annotations', actualTable.annotations),
|
|
71
71
|
...ifDefined('checks', actualTable.checks),
|
|
72
72
|
policies: [...actualTable.policies],
|
|
73
|
+
rlsEnabled: actualTable.rlsEnabled,
|
|
73
74
|
});
|
|
74
75
|
}
|
|
75
76
|
namespaces[nsId] = new PostgresNamespaceSchemaNode({
|
|
@@ -63,6 +63,7 @@ import {
|
|
|
63
63
|
CreateIndexCall,
|
|
64
64
|
CreateSchemaCall,
|
|
65
65
|
CreateTableCall,
|
|
66
|
+
DisableRowLevelSecurityCall,
|
|
66
67
|
DropCheckConstraintCall,
|
|
67
68
|
DropColumnCall,
|
|
68
69
|
DropConstraintCall,
|
|
@@ -70,6 +71,7 @@ import {
|
|
|
70
71
|
DropIndexCall,
|
|
71
72
|
DropNotNullCall,
|
|
72
73
|
DropTableCall,
|
|
74
|
+
EnableRowLevelSecurityCall,
|
|
73
75
|
type PostgresOpFactoryCall,
|
|
74
76
|
SetDefaultCall,
|
|
75
77
|
SetNotNullCall,
|
|
@@ -163,6 +165,7 @@ function classifyCall(call: PostgresOpFactoryCall): CallCategory {
|
|
|
163
165
|
case 'createTable':
|
|
164
166
|
return 'table';
|
|
165
167
|
case 'enableRowLevelSecurity':
|
|
168
|
+
case 'disableRowLevelSecurity':
|
|
166
169
|
return 'rlsEnable';
|
|
167
170
|
case 'createRlsPolicy':
|
|
168
171
|
return 'rlsPolicy';
|
|
@@ -465,6 +468,7 @@ function fkSpecFromNode(fk: SqlForeignKeyIR, tableName: string): ForeignKeySpec
|
|
|
465
468
|
*/
|
|
466
469
|
function buildCreateTableCallsFromNode(
|
|
467
470
|
schemaName: string,
|
|
471
|
+
ddlSchemaName: string,
|
|
468
472
|
table: PostgresTableSchemaNode,
|
|
469
473
|
codecHooks: ReadonlyMap<string, CodecControlHooks>,
|
|
470
474
|
): PostgresOpFactoryCall[] {
|
|
@@ -509,6 +513,13 @@ function buildCreateTableCallsFromNode(
|
|
|
509
513
|
const constraintName = unique.name ?? `${table.name}_${unique.columns.join('_')}_key`;
|
|
510
514
|
calls.push(new AddUniqueCall(schemaName, table.name, constraintName, [...unique.columns]));
|
|
511
515
|
}
|
|
516
|
+
// Marker-driven: a newly-created table that is RLS-controlled enables RLS
|
|
517
|
+
// as part of its creation bundle. The policy set never decides this. The
|
|
518
|
+
// resolved schema (not the emission sentinel) binds into the op's
|
|
519
|
+
// relrowsecurity checks.
|
|
520
|
+
if (table.rlsEnabled) {
|
|
521
|
+
calls.push(new EnableRowLevelSecurityCall(ddlSchemaName, table.name));
|
|
522
|
+
}
|
|
512
523
|
return calls;
|
|
513
524
|
}
|
|
514
525
|
|
|
@@ -519,6 +530,11 @@ function nodeConflict(kind: SqlPlannerConflict['kind'], message: string): SqlPla
|
|
|
519
530
|
function mapTableNodeIssue(
|
|
520
531
|
issue: SchemaDiffIssue,
|
|
521
532
|
schemaName: string,
|
|
533
|
+
// The diff tree's RESOLVED physical schema. Enablement ops bind it into
|
|
534
|
+
// their `pg_class`/`pg_namespace` checks (the emission sentinel would
|
|
535
|
+
// never match a live nspname), mirroring the schema the retired
|
|
536
|
+
// policy-half enable resolved via `resolveDdlSchemaForNamespaceStorage`.
|
|
537
|
+
ddlSchemaName: string,
|
|
522
538
|
codecHooks: ReadonlyMap<string, CodecControlHooks>,
|
|
523
539
|
): Result<readonly PostgresOpFactoryCall[], SqlPlannerConflict> {
|
|
524
540
|
if (issue.reason === 'not-found') {
|
|
@@ -526,7 +542,7 @@ function mapTableNodeIssue(
|
|
|
526
542
|
PostgresTableSchemaNode,
|
|
527
543
|
'a not-found table issue always carries the expected PostgresTableSchemaNode'
|
|
528
544
|
>(issue.expected);
|
|
529
|
-
return ok(buildCreateTableCallsFromNode(schemaName, table, codecHooks));
|
|
545
|
+
return ok(buildCreateTableCallsFromNode(schemaName, ddlSchemaName, table, codecHooks));
|
|
530
546
|
}
|
|
531
547
|
if (issue.reason === 'not-expected') {
|
|
532
548
|
const table = blindCast<
|
|
@@ -535,8 +551,33 @@ function mapTableNodeIssue(
|
|
|
535
551
|
>(issue.actual);
|
|
536
552
|
return ok([new DropTableCall(schemaName, table.name)]);
|
|
537
553
|
}
|
|
538
|
-
//
|
|
539
|
-
|
|
554
|
+
// A paired table `not-equal` means enablement drift TODAY, because
|
|
555
|
+
// `isEqualTo` compares only name + `rlsEnabled`. Key on the actual
|
|
556
|
+
// expected-vs-actual `rlsEnabled` delta rather than assuming it: emit ENABLE
|
|
557
|
+
// when it flipped on, DISABLE when it flipped off, and fail loud when
|
|
558
|
+
// `rlsEnabled` matches on both sides — that means a second table attribute
|
|
559
|
+
// drifted into `isEqualTo` and this mapper does not yet handle it. The
|
|
560
|
+
// expected side is authoritative (marker-driven, never the policy set).
|
|
561
|
+
const expected = blindCast<
|
|
562
|
+
PostgresTableSchemaNode,
|
|
563
|
+
'a not-equal table issue always carries the expected PostgresTableSchemaNode'
|
|
564
|
+
>(issue.expected);
|
|
565
|
+
const actual = blindCast<
|
|
566
|
+
PostgresTableSchemaNode,
|
|
567
|
+
'a not-equal table issue always carries the actual PostgresTableSchemaNode'
|
|
568
|
+
>(issue.actual);
|
|
569
|
+
if (expected.rlsEnabled && !actual.rlsEnabled) {
|
|
570
|
+
return ok([new EnableRowLevelSecurityCall(ddlSchemaName, expected.name)]);
|
|
571
|
+
}
|
|
572
|
+
if (!expected.rlsEnabled && actual.rlsEnabled) {
|
|
573
|
+
return ok([new DisableRowLevelSecurityCall(ddlSchemaName, expected.name)]);
|
|
574
|
+
}
|
|
575
|
+
return notOk(
|
|
576
|
+
nodeConflict(
|
|
577
|
+
'unsupportedOperation',
|
|
578
|
+
`unhandled table-attribute drift on "${expected.name}": table not-equal with no rlsEnabled delta (a second table attribute drifted)`,
|
|
579
|
+
),
|
|
580
|
+
);
|
|
540
581
|
}
|
|
541
582
|
|
|
542
583
|
function mapColumnNodeIssue(
|
|
@@ -787,7 +828,7 @@ export function mapNodeIssueToCall(
|
|
|
787
828
|
|
|
788
829
|
switch (node.nodeKind) {
|
|
789
830
|
case PostgresSchemaNodeKind.table:
|
|
790
|
-
return mapTableNodeIssue(issue, schemaName, ctx.codecHooks);
|
|
831
|
+
return mapTableNodeIssue(issue, schemaName, ddlSchemaName, ctx.codecHooks);
|
|
791
832
|
case RelationalSchemaNodeKind.column:
|
|
792
833
|
return mapColumnNodeIssue(issue, schemaName, tableName, ctx.codecHooks);
|
|
793
834
|
case RelationalSchemaNodeKind.columnDefault: {
|
|
@@ -952,6 +993,10 @@ export function planIssues(
|
|
|
952
993
|
...byCategory('unique'),
|
|
953
994
|
...byCategory('index'),
|
|
954
995
|
...byCategory('foreignKey'),
|
|
996
|
+
// Enablement changes run after all relational DDL (the table must exist)
|
|
997
|
+
// and before the policy calls the planner appends after `planIssues` —
|
|
998
|
+
// the same position the retired imperative enable-on-first-policy used.
|
|
999
|
+
...byCategory('rlsEnable'),
|
|
955
1000
|
];
|
|
956
1001
|
|
|
957
1002
|
return ok({ calls });
|
|
@@ -63,7 +63,13 @@ import {
|
|
|
63
63
|
} from './operations/constraints';
|
|
64
64
|
import { createExtension } from './operations/dependencies';
|
|
65
65
|
import { createIndex, dropIndex } from './operations/indexes';
|
|
66
|
-
import {
|
|
66
|
+
import {
|
|
67
|
+
createRlsPolicy,
|
|
68
|
+
disableRowLevelSecurity,
|
|
69
|
+
dropRlsPolicy,
|
|
70
|
+
enableRowLevelSecurity,
|
|
71
|
+
renameRlsPolicy,
|
|
72
|
+
} from './operations/rls';
|
|
67
73
|
import type { ForeignKeySpec } from './operations/shared';
|
|
68
74
|
import { step, targetDetails } from './operations/shared';
|
|
69
75
|
import { dropTable } from './operations/tables';
|
|
@@ -1461,6 +1467,78 @@ export class EnableRowLevelSecurityCall extends PostgresOpFactoryCallNode {
|
|
|
1461
1467
|
}
|
|
1462
1468
|
}
|
|
1463
1469
|
|
|
1470
|
+
export class DisableRowLevelSecurityCall extends PostgresOpFactoryCallNode {
|
|
1471
|
+
readonly factoryName = 'disableRowLevelSecurity' as const;
|
|
1472
|
+
readonly operationClass = 'destructive' as const;
|
|
1473
|
+
readonly schemaName: string;
|
|
1474
|
+
readonly tableName: string;
|
|
1475
|
+
readonly label: string;
|
|
1476
|
+
|
|
1477
|
+
constructor(schemaName: string, tableName: string) {
|
|
1478
|
+
super();
|
|
1479
|
+
this.schemaName = schemaName;
|
|
1480
|
+
this.tableName = tableName;
|
|
1481
|
+
this.label = `Disable row-level security on "${tableName}"`;
|
|
1482
|
+
this.freeze();
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
async toOp(lowerer?: ExecuteRequestLowerer): Promise<Op> {
|
|
1486
|
+
if (lowerer === undefined) {
|
|
1487
|
+
throw new Error(
|
|
1488
|
+
`DisableRowLevelSecurityCall.toOp: a lowerer is required on the Postgres planner path (table "${this.tableName}"). Pass the control adapter to createPostgresMigrationPlanner.`,
|
|
1489
|
+
);
|
|
1490
|
+
}
|
|
1491
|
+
return disableRowLevelSecurity(this.schemaName, this.tableName, lowerer);
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
renderTypeScript(): string {
|
|
1495
|
+
return `disableRowLevelSecurity(${jsonToTsSource(this.schemaName)}, ${jsonToTsSource(this.tableName)})`;
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
export class RenamePostgresRlsPolicyCall extends PostgresOpFactoryCallNode {
|
|
1500
|
+
readonly factoryName = 'renameRlsPolicy' as const;
|
|
1501
|
+
// `widening` is chosen so the rename plans under every allowance set except
|
|
1502
|
+
// additive-only init — a rename is neither additive-creation nor
|
|
1503
|
+
// destructive, and the class vocabulary has no neutral middle class. It is
|
|
1504
|
+
// NOT that a rename widens anything; this is the accepted typology tradeoff.
|
|
1505
|
+
readonly operationClass = 'widening' as const;
|
|
1506
|
+
readonly schemaName: string;
|
|
1507
|
+
readonly tableName: string;
|
|
1508
|
+
readonly oldPolicyName: string;
|
|
1509
|
+
readonly newPolicyName: string;
|
|
1510
|
+
readonly label: string;
|
|
1511
|
+
|
|
1512
|
+
constructor(schemaName: string, tableName: string, oldPolicyName: string, newPolicyName: string) {
|
|
1513
|
+
super();
|
|
1514
|
+
this.schemaName = schemaName;
|
|
1515
|
+
this.tableName = tableName;
|
|
1516
|
+
this.oldPolicyName = oldPolicyName;
|
|
1517
|
+
this.newPolicyName = newPolicyName;
|
|
1518
|
+
this.label = `Rename RLS policy "${oldPolicyName}" to "${newPolicyName}" on "${tableName}"`;
|
|
1519
|
+
this.freeze();
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1522
|
+
async toOp(lowerer?: ExecuteRequestLowerer): Promise<Op> {
|
|
1523
|
+
if (lowerer === undefined) {
|
|
1524
|
+
throw new Error(
|
|
1525
|
+
`RenamePostgresRlsPolicyCall.toOp: a lowerer is required on the Postgres planner path (policy "${this.oldPolicyName}" on table "${this.tableName}"). Pass the control adapter to createPostgresMigrationPlanner.`,
|
|
1526
|
+
);
|
|
1527
|
+
}
|
|
1528
|
+
return renameRlsPolicy(
|
|
1529
|
+
this.schemaName,
|
|
1530
|
+
this.tableName,
|
|
1531
|
+
this.oldPolicyName,
|
|
1532
|
+
this.newPolicyName,
|
|
1533
|
+
lowerer,
|
|
1534
|
+
);
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1537
|
+
renderTypeScript(): string {
|
|
1538
|
+
return `renameRlsPolicy(${jsonToTsSource(this.schemaName)}, ${jsonToTsSource(this.tableName)}, ${jsonToTsSource(this.oldPolicyName)}, ${jsonToTsSource(this.newPolicyName)})`;
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1464
1542
|
export type PostgresOpFactoryCall =
|
|
1465
1543
|
| CreateTableCall
|
|
1466
1544
|
| DropTableCall
|
|
@@ -1487,4 +1565,6 @@ export type PostgresOpFactoryCall =
|
|
|
1487
1565
|
| CreatePostgresRlsPolicyCall
|
|
1488
1566
|
| DropPostgresRlsPolicyCall
|
|
1489
1567
|
| EnableRowLevelSecurityCall
|
|
1568
|
+
| DisableRowLevelSecurityCall
|
|
1569
|
+
| RenamePostgresRlsPolicyCall
|
|
1490
1570
|
| DataTransformCall;
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import type { ExecuteRequestLowerer } from '@prisma-next/family-sql/control-adapter';
|
|
2
2
|
import { ifDefined } from '@prisma-next/utils/defined';
|
|
3
3
|
import { rlsEnabledAst, rlsPolicyExistsAst } from '../../../contract-free/checks';
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
alterPolicyRename,
|
|
6
|
+
createPolicy,
|
|
7
|
+
disableRowLevelSecurity as disableRowLevelSecurityDdl,
|
|
8
|
+
dropPolicy,
|
|
9
|
+
} from '../../../contract-free/ddl';
|
|
5
10
|
import type { PostgresRlsPolicy } from '../../postgres-rls-policy';
|
|
6
11
|
import { qualifyTableName } from '../planner-sql-checks';
|
|
7
12
|
import { type Op, step, targetDetails } from './shared';
|
|
@@ -104,3 +109,79 @@ export async function enableRowLevelSecurity(
|
|
|
104
109
|
],
|
|
105
110
|
};
|
|
106
111
|
}
|
|
112
|
+
|
|
113
|
+
export async function disableRowLevelSecurity(
|
|
114
|
+
schemaName: string,
|
|
115
|
+
tableName: string,
|
|
116
|
+
lowerer: ExecuteRequestLowerer,
|
|
117
|
+
): Promise<Op> {
|
|
118
|
+
const ddlNode = disableRowLevelSecurityDdl({ schema: schemaName, table: tableName });
|
|
119
|
+
const checks = rlsEnabledAst(schemaName, tableName);
|
|
120
|
+
const enabled = await lowerer.lowerToExecuteRequest(checks.rlsEnabled());
|
|
121
|
+
const execute = await lowerer.lowerToExecuteRequest(ddlNode);
|
|
122
|
+
const disabled = await lowerer.lowerToExecuteRequest(checks.rlsDisabled());
|
|
123
|
+
return {
|
|
124
|
+
id: `rowLevelSecurity.${schemaName}.${tableName}.disable`,
|
|
125
|
+
label: `Disable row-level security on "${tableName}"`,
|
|
126
|
+
operationClass: 'destructive',
|
|
127
|
+
target: targetDetails('rowLevelSecurity', tableName, schemaName),
|
|
128
|
+
precheck: [
|
|
129
|
+
step(`check RLS is currently enabled on "${tableName}"`, enabled.sql, enabled.params),
|
|
130
|
+
],
|
|
131
|
+
execute: [step(`disable row-level security on "${tableName}"`, execute.sql, execute.params)],
|
|
132
|
+
postcheck: [
|
|
133
|
+
step(
|
|
134
|
+
`verify row-level security is disabled on "${tableName}"`,
|
|
135
|
+
disabled.sql,
|
|
136
|
+
disabled.params,
|
|
137
|
+
),
|
|
138
|
+
],
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export async function renameRlsPolicy(
|
|
143
|
+
schemaName: string,
|
|
144
|
+
tableName: string,
|
|
145
|
+
oldPolicyName: string,
|
|
146
|
+
newPolicyName: string,
|
|
147
|
+
lowerer: ExecuteRequestLowerer,
|
|
148
|
+
): Promise<Op> {
|
|
149
|
+
const ddlNode = alterPolicyRename({
|
|
150
|
+
schema: schemaName,
|
|
151
|
+
table: tableName,
|
|
152
|
+
name: oldPolicyName,
|
|
153
|
+
newName: newPolicyName,
|
|
154
|
+
});
|
|
155
|
+
const oldChecks = rlsPolicyExistsAst({
|
|
156
|
+
schema: schemaName,
|
|
157
|
+
table: tableName,
|
|
158
|
+
policyName: oldPolicyName,
|
|
159
|
+
});
|
|
160
|
+
const newChecks = rlsPolicyExistsAst({
|
|
161
|
+
schema: schemaName,
|
|
162
|
+
table: tableName,
|
|
163
|
+
policyName: newPolicyName,
|
|
164
|
+
});
|
|
165
|
+
const oldPresent = await lowerer.lowerToExecuteRequest(oldChecks.policyPresent());
|
|
166
|
+
const execute = await lowerer.lowerToExecuteRequest(ddlNode);
|
|
167
|
+
const newPresent = await lowerer.lowerToExecuteRequest(newChecks.policyPresent());
|
|
168
|
+
return {
|
|
169
|
+
id: `rlsPolicy.${schemaName}.${tableName}.${oldPolicyName}.rename`,
|
|
170
|
+
label: `Rename RLS policy "${oldPolicyName}" to "${newPolicyName}" on "${tableName}"`,
|
|
171
|
+
operationClass: 'widening',
|
|
172
|
+
target: targetDetails('rlsPolicy', newPolicyName, schemaName, tableName),
|
|
173
|
+
precheck: [
|
|
174
|
+
step(`ensure RLS policy "${oldPolicyName}" exists`, oldPresent.sql, oldPresent.params),
|
|
175
|
+
],
|
|
176
|
+
execute: [
|
|
177
|
+
step(
|
|
178
|
+
`rename RLS policy "${oldPolicyName}" to "${newPolicyName}"`,
|
|
179
|
+
execute.sql,
|
|
180
|
+
execute.params,
|
|
181
|
+
),
|
|
182
|
+
],
|
|
183
|
+
postcheck: [
|
|
184
|
+
step(`verify RLS policy "${newPolicyName}" exists`, newPresent.sql, newPresent.params),
|
|
185
|
+
],
|
|
186
|
+
};
|
|
187
|
+
}
|
|
@@ -27,6 +27,7 @@ import type { SqlSchemaIR } from '@prisma-next/sql-schema-ir/types';
|
|
|
27
27
|
import { blindCast } from '@prisma-next/utils/casts';
|
|
28
28
|
import { ifDefined } from '@prisma-next/utils/defined';
|
|
29
29
|
import { PostgresRlsPolicy } from '../postgres-rls-policy';
|
|
30
|
+
import { parseRlsPolicyWireName } from '../rls/wire-name';
|
|
30
31
|
import { PostgresDatabaseSchemaNode } from '../schema-ir/postgres-database-schema-node';
|
|
31
32
|
import { PostgresPolicySchemaNode } from '../schema-ir/postgres-policy-schema-node';
|
|
32
33
|
import { PostgresSchemaNodeKind, type SqlSchemaDiffNode } from '../schema-ir/schema-node-kinds';
|
|
@@ -49,7 +50,7 @@ import type { PostgresOpFactoryCall } from './op-factory-call';
|
|
|
49
50
|
import {
|
|
50
51
|
CreatePostgresRlsPolicyCall,
|
|
51
52
|
DropPostgresRlsPolicyCall,
|
|
52
|
-
|
|
53
|
+
RenamePostgresRlsPolicyCall,
|
|
53
54
|
} from './op-factory-call';
|
|
54
55
|
import { TypeScriptRenderablePostgresMigration } from './planner-produced-postgres-migration';
|
|
55
56
|
import { postgresPlannerStrategies } from './planner-strategies';
|
|
@@ -335,17 +336,40 @@ export class PostgresMigrationPlanner implements MigrationPlanner<'sql', 'postgr
|
|
|
335
336
|
|
|
336
337
|
/**
|
|
337
338
|
* Maps the RLS policy presence findings of the one combined tree diff
|
|
338
|
-
* (`buildPostgresPlanDiff`, already ownership-filtered) into
|
|
339
|
-
*
|
|
340
|
-
* exactly the policy-node subset of the
|
|
339
|
+
* (`buildPostgresPlanDiff`, already ownership-filtered) into
|
|
340
|
+
* `CREATE POLICY` / `DROP POLICY` / `ALTER POLICY … RENAME TO` ops. It
|
|
341
|
+
* does not re-diff — it consumes exactly the policy-node subset of the
|
|
342
|
+
* shared diff's issues. Enablement is NOT decided here: `ENABLE`/`DISABLE
|
|
343
|
+
* ROW LEVEL SECURITY` derive from the table's marker-driven `rlsEnabled`
|
|
344
|
+
* attribute diff on the relational side.
|
|
345
|
+
*
|
|
346
|
+
* Rename post-pass: a `not-found` and a `not-expected` policy on the SAME
|
|
347
|
+
* table whose wire-name content hashes match but prefixes differ are one
|
|
348
|
+
* prefix-only rename, collapsed into a single non-destructive
|
|
349
|
+
* `RenamePostgresRlsPolicyCall`. Multi-candidate hash groups pair
|
|
350
|
+
* deterministically by sorted wire name; leftovers proceed as
|
|
351
|
+
* create/drop. Unparseable wire names never pair.
|
|
352
|
+
*
|
|
353
|
+
* The pairing runs only when the policy allows `widening` (rename's
|
|
354
|
+
* class). Without it (db-init's additive-only set), pairing degrades
|
|
355
|
+
* deliberately to the additive half: the new name is CREATEd and the old
|
|
356
|
+
* policy survives live until a widening/destructive-allowed plan runs —
|
|
357
|
+
* emitting an ungated widening rename would only fail at the runner's
|
|
358
|
+
* class re-enforcement.
|
|
341
359
|
*/
|
|
342
360
|
private planPostgresSchemaDiff(
|
|
343
361
|
options: PlannerOptionsWithComponents,
|
|
344
362
|
filteredDiffIssues: readonly SchemaDiffIssue<SqlSchemaDiffNode>[],
|
|
345
363
|
): readonly PostgresOpFactoryCall[] {
|
|
346
364
|
const allowsDestructive = options.policy.allowedOperationClasses.includes('destructive');
|
|
347
|
-
const
|
|
348
|
-
|
|
365
|
+
const allowsWidening = options.policy.allowedOperationClasses.includes('widening');
|
|
366
|
+
|
|
367
|
+
interface PolicyFinding {
|
|
368
|
+
readonly node: PostgresPolicySchemaNode;
|
|
369
|
+
readonly schemaForTable: string;
|
|
370
|
+
}
|
|
371
|
+
const missing: PolicyFinding[] = [];
|
|
372
|
+
const extra: PolicyFinding[] = [];
|
|
349
373
|
|
|
350
374
|
for (const issue of filteredDiffIssues) {
|
|
351
375
|
// 'not-equal' is unreachable for content-addressed policies: the wire name
|
|
@@ -356,30 +380,90 @@ export class PostgresMigrationPlanner implements MigrationPlanner<'sql', 'postgr
|
|
|
356
380
|
PostgresPolicySchemaNode.assert(expected);
|
|
357
381
|
// expected.namespaceId is the DDL schema name (resolved during projection);
|
|
358
382
|
// this re-resolution is a no-op as long as PostgresSchema.ddlSchemaName() returns this.id.
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
if (!seenEnableTables.has(tableKey)) {
|
|
365
|
-
seenEnableTables.add(tableKey);
|
|
366
|
-
calls.push(new EnableRowLevelSecurityCall(schemaForTable, expected.tableName));
|
|
367
|
-
}
|
|
368
|
-
calls.push(
|
|
369
|
-
new CreatePostgresRlsPolicyCall(
|
|
370
|
-
schemaForTable,
|
|
371
|
-
expected.tableName,
|
|
372
|
-
policyNodeToContractPolicy(expected),
|
|
383
|
+
missing.push({
|
|
384
|
+
node: expected,
|
|
385
|
+
schemaForTable: resolveDdlSchemaForNamespaceStorage(
|
|
386
|
+
options.contract.storage,
|
|
387
|
+
expected.namespaceId,
|
|
373
388
|
),
|
|
374
|
-
);
|
|
375
|
-
} else if (issue.reason === 'not-expected'
|
|
389
|
+
});
|
|
390
|
+
} else if (issue.reason === 'not-expected') {
|
|
376
391
|
const actual = issue.actual;
|
|
377
392
|
PostgresPolicySchemaNode.assert(actual);
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
393
|
+
extra.push({
|
|
394
|
+
node: actual,
|
|
395
|
+
schemaForTable: resolveDdlSchemaForNamespaceStorage(
|
|
396
|
+
options.contract.storage,
|
|
397
|
+
actual.namespaceId,
|
|
398
|
+
),
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
const calls: PostgresOpFactoryCall[] = [];
|
|
404
|
+
const renamedExtras = new Set<PolicyFinding>();
|
|
405
|
+
const renamedMissing = new Set<PolicyFinding>();
|
|
406
|
+
const pairingKey = (finding: PolicyFinding, hash: string): string =>
|
|
407
|
+
JSON.stringify([finding.schemaForTable, finding.node.tableName, hash]);
|
|
408
|
+
|
|
409
|
+
if (allowsWidening) {
|
|
410
|
+
const extrasByGroup = new Map<string, PolicyFinding[]>();
|
|
411
|
+
for (const finding of extra) {
|
|
412
|
+
const parsed = parseRlsPolicyWireName(finding.node.name);
|
|
413
|
+
if (parsed === undefined) continue;
|
|
414
|
+
const key = pairingKey(finding, parsed.hash);
|
|
415
|
+
const group = extrasByGroup.get(key) ?? [];
|
|
416
|
+
group.push(finding);
|
|
417
|
+
extrasByGroup.set(key, group);
|
|
418
|
+
}
|
|
419
|
+
for (const group of extrasByGroup.values()) {
|
|
420
|
+
group.sort((a, b) => (a.node.name < b.node.name ? -1 : a.node.name > b.node.name ? 1 : 0));
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
const sortedMissing = [...missing].sort((a, b) =>
|
|
424
|
+
a.node.name < b.node.name ? -1 : a.node.name > b.node.name ? 1 : 0,
|
|
425
|
+
);
|
|
426
|
+
for (const missingFinding of sortedMissing) {
|
|
427
|
+
const parsed = parseRlsPolicyWireName(missingFinding.node.name);
|
|
428
|
+
if (parsed === undefined) continue;
|
|
429
|
+
const candidates = extrasByGroup.get(pairingKey(missingFinding, parsed.hash));
|
|
430
|
+
const candidate = candidates?.shift();
|
|
431
|
+
if (candidate === undefined) continue;
|
|
432
|
+
// Same name would never surface as missing+extra (the differ pairs by
|
|
433
|
+
// name), so a matched candidate always differs in prefix only.
|
|
434
|
+
renamedExtras.add(candidate);
|
|
435
|
+
renamedMissing.add(missingFinding);
|
|
436
|
+
calls.push(
|
|
437
|
+
new RenamePostgresRlsPolicyCall(
|
|
438
|
+
missingFinding.schemaForTable,
|
|
439
|
+
missingFinding.node.tableName,
|
|
440
|
+
candidate.node.name,
|
|
441
|
+
missingFinding.node.name,
|
|
442
|
+
),
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
for (const finding of missing) {
|
|
448
|
+
if (renamedMissing.has(finding)) continue;
|
|
449
|
+
calls.push(
|
|
450
|
+
new CreatePostgresRlsPolicyCall(
|
|
451
|
+
finding.schemaForTable,
|
|
452
|
+
finding.node.tableName,
|
|
453
|
+
policyNodeToContractPolicy(finding.node),
|
|
454
|
+
),
|
|
455
|
+
);
|
|
456
|
+
}
|
|
457
|
+
if (allowsDestructive) {
|
|
458
|
+
for (const finding of extra) {
|
|
459
|
+
if (renamedExtras.has(finding)) continue;
|
|
460
|
+
calls.push(
|
|
461
|
+
new DropPostgresRlsPolicyCall(
|
|
462
|
+
finding.schemaForTable,
|
|
463
|
+
finding.node.tableName,
|
|
464
|
+
finding.node.name,
|
|
465
|
+
),
|
|
381
466
|
);
|
|
382
|
-
calls.push(new DropPostgresRlsPolicyCall(schemaForTable, actual.tableName, actual.name));
|
|
383
467
|
}
|
|
384
468
|
}
|
|
385
469
|
|
|
@@ -19,7 +19,12 @@ import { blindCast } from '@prisma-next/utils/casts';
|
|
|
19
19
|
import type { JsonObject } from '@prisma-next/utils/json';
|
|
20
20
|
import { postgresAuthoringEntityTypes } from './authoring';
|
|
21
21
|
import { PG_INT_CODEC_ID, PG_TEXT_CODEC_ID } from './codec-ids';
|
|
22
|
-
import {
|
|
22
|
+
import {
|
|
23
|
+
nativeEnumEntityKind,
|
|
24
|
+
policyEntityKind,
|
|
25
|
+
rlsEnablementEntityKind,
|
|
26
|
+
roleEntityKind,
|
|
27
|
+
} from './entity-kinds';
|
|
23
28
|
import { PostgresSchema } from './postgres-schema';
|
|
24
29
|
|
|
25
30
|
const POSTGRES_AUTHORING_CTX: AuthoringEntityContext = {
|
|
@@ -73,6 +78,7 @@ export class PostgresContractSerializer extends SqlContractSerializerBase<Contra
|
|
|
73
78
|
super(storageTypesHydrators, [
|
|
74
79
|
policyEntityKind,
|
|
75
80
|
roleEntityKind,
|
|
81
|
+
rlsEnablementEntityKind,
|
|
76
82
|
nativeEnumEntityKind,
|
|
77
83
|
...extraPackEntityKinds,
|
|
78
84
|
]);
|
|
@@ -110,9 +116,8 @@ export class PostgresContractSerializer extends SqlContractSerializerBase<Contra
|
|
|
110
116
|
const { storage, ...rest } = contract;
|
|
111
117
|
const namespacesJson: Record<string, JsonObject> = {};
|
|
112
118
|
// Each namespace serializes to its id, its schema-kind tag, and the
|
|
113
|
-
// base's generic entries walk
|
|
114
|
-
//
|
|
115
|
-
// never sees them.
|
|
119
|
+
// base's generic entries walk — every enumerable kind on
|
|
120
|
+
// `PostgresSchema.entries`, including `native_enum`.
|
|
116
121
|
for (const [nsId, ns] of Object.entries(storage.namespaces)) {
|
|
117
122
|
const isUnboundSlot = ns.id === UNBOUND_NAMESPACE_ID;
|
|
118
123
|
namespacesJson[nsId] = {
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { freezeNode } from '@prisma-next/framework-components/ir';
|
|
2
|
+
import { SqlNode } from '@prisma-next/sql-contract/types';
|
|
3
|
+
|
|
4
|
+
export interface PostgresRlsEnablementInput {
|
|
5
|
+
/** Name of the table this marker declares RLS-controlled. */
|
|
6
|
+
readonly tableName: string;
|
|
7
|
+
/** Namespace coordinate (schema name). Markers are schema-scoped like policies. */
|
|
8
|
+
readonly namespaceId: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Postgres contract-IR marker for `@@rls` on a model: the table named here is
|
|
13
|
+
* RLS-controlled (`ENABLE ROW LEVEL SECURITY` is driven by this marker, never
|
|
14
|
+
* by the policy set).
|
|
15
|
+
*
|
|
16
|
+
* This is an authored, serialized Contract-IR entity — it is registered as an
|
|
17
|
+
* entity kind, extends `SqlNode`, and is stored in `contract.json` under
|
|
18
|
+
* `entries.rls[tableName]`. Target-only concept — no SQL-family abstract.
|
|
19
|
+
* Frozen at construction via `freezeNode(this)`. The `kind: 'rls'`
|
|
20
|
+
* discriminant is enumerable so it survives JSON serialization; the literal
|
|
21
|
+
* matches the entries key (one-string rule).
|
|
22
|
+
*/
|
|
23
|
+
export class PostgresRlsEnablement extends SqlNode {
|
|
24
|
+
override readonly kind = 'rls' as const;
|
|
25
|
+
readonly tableName: string;
|
|
26
|
+
readonly namespaceId: string;
|
|
27
|
+
|
|
28
|
+
constructor(input: PostgresRlsEnablementInput) {
|
|
29
|
+
super();
|
|
30
|
+
this.tableName = input.tableName;
|
|
31
|
+
this.namespaceId = input.namespaceId;
|
|
32
|
+
freezeNode(this);
|
|
33
|
+
}
|
|
34
|
+
}
|