@supabase/pg-delta 1.0.0-alpha.17 → 1.0.0-alpha.19
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/core/expand-replace-dependencies.js +69 -0
- package/dist/core/objects/index/index.model.js +12 -2
- package/dist/core/objects/procedure/procedure.diff.js +33 -20
- package/dist/core/objects/rls-policy/changes/rls-policy.create.js +23 -0
- package/dist/core/objects/rls-policy/rls-policy.model.d.ts +49 -0
- package/dist/core/objects/rls-policy/rls-policy.model.js +122 -1
- package/dist/core/objects/table/table.diff.js +1 -0
- package/dist/core/objects/table/table.model.d.ts +4 -0
- package/dist/core/objects/table/table.model.js +2 -0
- package/dist/core/plan/sql-format/fixtures.js +8 -0
- package/dist/core/post-diff-cycle-breaking.d.ts +7 -0
- package/dist/core/post-diff-cycle-breaking.js +69 -3
- package/package.json +1 -1
- package/src/core/catalog.snapshot.test.ts +2 -0
- package/src/core/expand-replace-dependencies.test.ts +118 -0
- package/src/core/expand-replace-dependencies.ts +78 -0
- package/src/core/objects/index/index.model.test.ts +83 -0
- package/src/core/objects/index/index.model.ts +13 -4
- package/src/core/objects/procedure/procedure.diff.test.ts +100 -2
- package/src/core/objects/procedure/procedure.diff.ts +39 -21
- package/src/core/objects/rls-policy/changes/rls-policy.alter.test.ts +16 -0
- package/src/core/objects/rls-policy/changes/rls-policy.create.test.ts +128 -0
- package/src/core/objects/rls-policy/changes/rls-policy.create.ts +27 -0
- package/src/core/objects/rls-policy/changes/rls-policy.drop.test.ts +2 -0
- package/src/core/objects/rls-policy/rls-policy.diff.test.ts +2 -0
- package/src/core/objects/rls-policy/rls-policy.model.ts +134 -1
- package/src/core/objects/table/changes/table.alter.test.ts +1 -0
- package/src/core/objects/table/table.diff.test.ts +102 -0
- package/src/core/objects/table/table.diff.ts +1 -0
- package/src/core/objects/table/table.model.ts +2 -0
- package/src/core/plan/sql-format/fixtures.ts +8 -0
- package/src/core/post-diff-cycle-breaking.test.ts +142 -0
- package/src/core/post-diff-cycle-breaking.ts +83 -2
|
@@ -49,8 +49,7 @@ export function diffProcedures(
|
|
|
49
49
|
|
|
50
50
|
const changes: ProcedureChange[] = [];
|
|
51
51
|
|
|
52
|
-
|
|
53
|
-
const proc = branch[procedureId];
|
|
52
|
+
const appendCreateProcedureChanges = (proc: Procedure) => {
|
|
54
53
|
changes.push(new CreateProcedure({ procedure: proc }));
|
|
55
54
|
|
|
56
55
|
// OWNER: If the procedure should be owned by someone other than the current user,
|
|
@@ -112,6 +111,10 @@ export function diffProcedures(
|
|
|
112
111
|
ctx.version,
|
|
113
112
|
) as ProcedureChange[]),
|
|
114
113
|
);
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
for (const procedureId of created) {
|
|
117
|
+
appendCreateProcedureChanges(branch[procedureId]);
|
|
115
118
|
}
|
|
116
119
|
|
|
117
120
|
for (const procedureId of dropped) {
|
|
@@ -122,22 +125,18 @@ export function diffProcedures(
|
|
|
122
125
|
const mainProcedure = main[procedureId];
|
|
123
126
|
const branchProcedure = branch[procedureId];
|
|
124
127
|
|
|
125
|
-
//
|
|
126
|
-
//
|
|
127
|
-
|
|
128
|
+
// Fields that are part of the function's identity/signature. PostgreSQL
|
|
129
|
+
// rejects `CREATE OR REPLACE FUNCTION` for any of these changes with
|
|
130
|
+
// errors such as:
|
|
131
|
+
// - cannot change return type of existing function
|
|
132
|
+
// - cannot change name of input parameter "..."
|
|
133
|
+
// - cannot change whether a procedure has output parameters
|
|
134
|
+
// - cannot remove parameter defaults from existing function
|
|
135
|
+
// These require `DROP FUNCTION` followed by `CREATE FUNCTION`.
|
|
136
|
+
const SIGNATURE_BREAKING_FIELDS: Array<keyof Procedure> = [
|
|
128
137
|
"kind",
|
|
129
138
|
"return_type",
|
|
130
139
|
"return_type_schema",
|
|
131
|
-
"language",
|
|
132
|
-
// The following properties are alterable in SQL, but our generator may choose
|
|
133
|
-
// to replace on changes not covered by explicit ALTER actions. Keep them out here
|
|
134
|
-
// to allow ALTER for those we implement below.
|
|
135
|
-
// security_definer,
|
|
136
|
-
// volatility,
|
|
137
|
-
// parallel_safety,
|
|
138
|
-
// is_strict,
|
|
139
|
-
// leakproof,
|
|
140
|
-
// Returns-set is part of the signature and not alterable
|
|
141
140
|
"returns_set",
|
|
142
141
|
"argument_count",
|
|
143
142
|
"argument_default_count",
|
|
@@ -146,26 +145,45 @@ export function diffProcedures(
|
|
|
146
145
|
"all_argument_types",
|
|
147
146
|
"argument_modes",
|
|
148
147
|
"argument_defaults",
|
|
148
|
+
];
|
|
149
|
+
// Fields where `CREATE OR REPLACE` is sufficient - body replacement only.
|
|
150
|
+
// Other fields (security_definer, volatility, parallel_safety, is_strict,
|
|
151
|
+
// leakproof, config) are alterable via dedicated ALTER actions below.
|
|
152
|
+
const OR_REPLACEABLE_NON_ALTERABLE_FIELDS: Array<keyof Procedure> = [
|
|
153
|
+
"language",
|
|
149
154
|
"source_code",
|
|
150
155
|
"binary_path",
|
|
151
156
|
"sql_body",
|
|
152
|
-
// config is alterable via SET/RESET
|
|
153
157
|
];
|
|
154
|
-
const
|
|
158
|
+
const signatureChanged = hasNonAlterableChanges(
|
|
155
159
|
mainProcedure,
|
|
156
160
|
branchProcedure,
|
|
157
|
-
|
|
161
|
+
SIGNATURE_BREAKING_FIELDS,
|
|
158
162
|
{
|
|
159
163
|
argument_names: deepEqual,
|
|
160
164
|
argument_types: deepEqual,
|
|
161
165
|
all_argument_types: deepEqual,
|
|
162
166
|
argument_modes: deepEqual,
|
|
163
|
-
config: deepEqual,
|
|
164
167
|
},
|
|
165
168
|
);
|
|
169
|
+
const nonAlterablePropsChanged =
|
|
170
|
+
signatureChanged ||
|
|
171
|
+
hasNonAlterableChanges(
|
|
172
|
+
mainProcedure,
|
|
173
|
+
branchProcedure,
|
|
174
|
+
OR_REPLACEABLE_NON_ALTERABLE_FIELDS,
|
|
175
|
+
);
|
|
166
176
|
|
|
167
|
-
if (
|
|
168
|
-
//
|
|
177
|
+
if (signatureChanged) {
|
|
178
|
+
// PostgreSQL cannot change an existing function's signature via
|
|
179
|
+
// `CREATE OR REPLACE`. Drop the old signature, then recreate.
|
|
180
|
+
// `expandReplaceDependencies` will cascade the replacement to dependent
|
|
181
|
+
// objects (views, triggers, column defaults) via pg_depend edges.
|
|
182
|
+
changes.push(new DropProcedure({ procedure: mainProcedure }));
|
|
183
|
+
appendCreateProcedureChanges(branchProcedure);
|
|
184
|
+
} else if (nonAlterablePropsChanged) {
|
|
185
|
+
// Body-only non-alterable change - `CREATE OR REPLACE` preserves the
|
|
186
|
+
// function OID and keeps dependent objects attached.
|
|
169
187
|
changes.push(
|
|
170
188
|
new CreateProcedure({ procedure: branchProcedure, orReplace: true }),
|
|
171
189
|
);
|
|
@@ -23,6 +23,8 @@ describe.concurrent("rls-policy", () => {
|
|
|
23
23
|
with_check_expression: null,
|
|
24
24
|
owner: "owner",
|
|
25
25
|
comment: null,
|
|
26
|
+
referenced_relations: [],
|
|
27
|
+
referenced_procedures: [],
|
|
26
28
|
};
|
|
27
29
|
const policy = new RlsPolicy({
|
|
28
30
|
...props,
|
|
@@ -52,6 +54,8 @@ describe.concurrent("rls-policy", () => {
|
|
|
52
54
|
with_check_expression: null,
|
|
53
55
|
owner: "owner",
|
|
54
56
|
comment: null,
|
|
57
|
+
referenced_relations: [],
|
|
58
|
+
referenced_procedures: [],
|
|
55
59
|
};
|
|
56
60
|
const policy = new RlsPolicy({
|
|
57
61
|
...props,
|
|
@@ -81,6 +85,8 @@ describe.concurrent("rls-policy", () => {
|
|
|
81
85
|
with_check_expression: null,
|
|
82
86
|
owner: "owner",
|
|
83
87
|
comment: null,
|
|
88
|
+
referenced_relations: [],
|
|
89
|
+
referenced_procedures: [],
|
|
84
90
|
};
|
|
85
91
|
const main = new RlsPolicy({
|
|
86
92
|
...props,
|
|
@@ -120,6 +126,8 @@ describe.concurrent("rls-policy", () => {
|
|
|
120
126
|
with_check_expression: null,
|
|
121
127
|
owner: "owner",
|
|
122
128
|
comment: null,
|
|
129
|
+
referenced_relations: [],
|
|
130
|
+
referenced_procedures: [],
|
|
123
131
|
};
|
|
124
132
|
const main = new RlsPolicy({
|
|
125
133
|
...props,
|
|
@@ -159,6 +167,8 @@ describe.concurrent("rls-policy", () => {
|
|
|
159
167
|
with_check_expression: null,
|
|
160
168
|
owner: "test",
|
|
161
169
|
comment: null,
|
|
170
|
+
referenced_relations: [],
|
|
171
|
+
referenced_procedures: [],
|
|
162
172
|
};
|
|
163
173
|
const policy = new RlsPolicy({
|
|
164
174
|
...props,
|
|
@@ -188,6 +198,8 @@ describe.concurrent("rls-policy", () => {
|
|
|
188
198
|
with_check_expression: null,
|
|
189
199
|
owner: "test",
|
|
190
200
|
comment: null,
|
|
201
|
+
referenced_relations: [],
|
|
202
|
+
referenced_procedures: [],
|
|
191
203
|
};
|
|
192
204
|
const policy = new RlsPolicy({
|
|
193
205
|
...props,
|
|
@@ -217,6 +229,8 @@ describe.concurrent("rls-policy", () => {
|
|
|
217
229
|
using_expression: "expr",
|
|
218
230
|
owner: "test",
|
|
219
231
|
comment: null,
|
|
232
|
+
referenced_relations: [],
|
|
233
|
+
referenced_procedures: [],
|
|
220
234
|
};
|
|
221
235
|
const policy = new RlsPolicy({
|
|
222
236
|
...props,
|
|
@@ -246,6 +260,8 @@ describe.concurrent("rls-policy", () => {
|
|
|
246
260
|
using_expression: "expr",
|
|
247
261
|
owner: "test",
|
|
248
262
|
comment: null,
|
|
263
|
+
referenced_relations: [],
|
|
264
|
+
referenced_procedures: [],
|
|
249
265
|
};
|
|
250
266
|
const policy = new RlsPolicy({
|
|
251
267
|
...props,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
2
|
import { assertValidSql } from "../../../test-utils/assert-valid-sql.ts";
|
|
3
|
+
import { stableId } from "../../utils.ts";
|
|
3
4
|
import { RlsPolicy } from "../rls-policy.model.ts";
|
|
4
5
|
import { CreateRlsPolicy } from "./rls-policy.create.ts";
|
|
5
6
|
|
|
@@ -16,6 +17,8 @@ describe("rls-policy", () => {
|
|
|
16
17
|
with_check_expression: null,
|
|
17
18
|
owner: "test",
|
|
18
19
|
comment: null,
|
|
20
|
+
referenced_relations: [],
|
|
21
|
+
referenced_procedures: [],
|
|
19
22
|
});
|
|
20
23
|
|
|
21
24
|
const change = new CreateRlsPolicy({
|
|
@@ -41,6 +44,8 @@ describe("rls-policy", () => {
|
|
|
41
44
|
with_check_expression: null,
|
|
42
45
|
owner: "test",
|
|
43
46
|
comment: null,
|
|
47
|
+
referenced_relations: [],
|
|
48
|
+
referenced_procedures: [],
|
|
44
49
|
});
|
|
45
50
|
|
|
46
51
|
const change = new CreateRlsPolicy({
|
|
@@ -66,6 +71,8 @@ describe("rls-policy", () => {
|
|
|
66
71
|
with_check_expression: "expr2",
|
|
67
72
|
owner: "test",
|
|
68
73
|
comment: null,
|
|
74
|
+
referenced_relations: [],
|
|
75
|
+
referenced_procedures: [],
|
|
69
76
|
});
|
|
70
77
|
|
|
71
78
|
const change = new CreateRlsPolicy({
|
|
@@ -78,4 +85,125 @@ describe("rls-policy", () => {
|
|
|
78
85
|
"CREATE POLICY test_policy_all ON public.test_table AS RESTRICTIVE FOR UPDATE TO role1, role2 USING (expr1) WITH CHECK (expr2)",
|
|
79
86
|
);
|
|
80
87
|
});
|
|
88
|
+
|
|
89
|
+
test("requires referenced relations reported by pg_depend", () => {
|
|
90
|
+
const policy = new RlsPolicy({
|
|
91
|
+
schema: "app",
|
|
92
|
+
name: "cross_relation_policy",
|
|
93
|
+
table_name: "accounts",
|
|
94
|
+
command: "r",
|
|
95
|
+
permissive: true,
|
|
96
|
+
roles: ["public"],
|
|
97
|
+
using_expression:
|
|
98
|
+
"(EXISTS (SELECT 1 FROM app.users) AND EXISTS (SELECT 1 FROM app.active_accounts))",
|
|
99
|
+
with_check_expression:
|
|
100
|
+
"(id IN (SELECT account_id FROM app.memberships WHERE active))",
|
|
101
|
+
owner: "test",
|
|
102
|
+
comment: null,
|
|
103
|
+
referenced_relations: [
|
|
104
|
+
{ kind: "table", schema: "app", name: "users" },
|
|
105
|
+
{ kind: "table", schema: "app", name: "memberships" },
|
|
106
|
+
{ kind: "view", schema: "app", name: "active_accounts" },
|
|
107
|
+
{ kind: "materialized_view", schema: "app", name: "account_stats" },
|
|
108
|
+
{ kind: "foreign_table", schema: "app", name: "remote_profiles" },
|
|
109
|
+
],
|
|
110
|
+
referenced_procedures: [],
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
const change = new CreateRlsPolicy({ policy });
|
|
114
|
+
|
|
115
|
+
expect(change.requires).toContain(stableId.table("app", "users"));
|
|
116
|
+
expect(change.requires).toContain(stableId.table("app", "memberships"));
|
|
117
|
+
expect(change.requires).toContain(stableId.view("app", "active_accounts"));
|
|
118
|
+
expect(change.requires).toContain(
|
|
119
|
+
stableId.materializedView("app", "account_stats"),
|
|
120
|
+
);
|
|
121
|
+
expect(change.requires).toContain(
|
|
122
|
+
stableId.foreignTable("app", "remote_profiles"),
|
|
123
|
+
);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test("requires referenced procedures reported by pg_depend", () => {
|
|
127
|
+
const policy = new RlsPolicy({
|
|
128
|
+
schema: "app",
|
|
129
|
+
name: "function_guarded_policy",
|
|
130
|
+
table_name: "accounts",
|
|
131
|
+
command: "r",
|
|
132
|
+
permissive: true,
|
|
133
|
+
roles: ["public"],
|
|
134
|
+
using_expression: "public.is_admin()",
|
|
135
|
+
with_check_expression: null,
|
|
136
|
+
owner: "test",
|
|
137
|
+
comment: null,
|
|
138
|
+
referenced_relations: [],
|
|
139
|
+
referenced_procedures: [
|
|
140
|
+
{ schema: "public", name: "is_admin", argument_types: [] },
|
|
141
|
+
{
|
|
142
|
+
schema: "public",
|
|
143
|
+
name: "has_role",
|
|
144
|
+
argument_types: ["text", "integer"],
|
|
145
|
+
},
|
|
146
|
+
],
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
const change = new CreateRlsPolicy({ policy });
|
|
150
|
+
|
|
151
|
+
expect(change.requires).toContain(stableId.procedure("public", "is_admin"));
|
|
152
|
+
expect(change.requires).toContain(
|
|
153
|
+
stableId.procedure("public", "has_role", "text,integer"),
|
|
154
|
+
);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test("does not require additional objects when referenced lists are empty", () => {
|
|
158
|
+
const policy = new RlsPolicy({
|
|
159
|
+
schema: "app",
|
|
160
|
+
name: "simple_policy",
|
|
161
|
+
table_name: "accounts",
|
|
162
|
+
command: "*",
|
|
163
|
+
permissive: true,
|
|
164
|
+
roles: [],
|
|
165
|
+
using_expression: null,
|
|
166
|
+
with_check_expression: null,
|
|
167
|
+
owner: "test",
|
|
168
|
+
comment: null,
|
|
169
|
+
referenced_relations: [],
|
|
170
|
+
referenced_procedures: [],
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
const change = new CreateRlsPolicy({ policy });
|
|
174
|
+
|
|
175
|
+
expect(change.requires).toEqual([
|
|
176
|
+
stableId.schema("app"),
|
|
177
|
+
stableId.table("app", "accounts"),
|
|
178
|
+
stableId.role("test"),
|
|
179
|
+
]);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
// Sequences referenced via nextval() are a known gap. pg_depend only
|
|
183
|
+
// records the sequence edge when the argument is written as a regclass
|
|
184
|
+
// literal (e.g. `nextval('app.seq'::regclass)`); bare string literals
|
|
185
|
+
// produce no pg_depend row. Tracked in
|
|
186
|
+
// https://github.com/supabase/pg-toolbelt/issues/220.
|
|
187
|
+
test.skip("requires referenced sequences (follow-up)", () => {
|
|
188
|
+
const policy = new RlsPolicy({
|
|
189
|
+
schema: "app",
|
|
190
|
+
name: "sequence_policy",
|
|
191
|
+
table_name: "accounts",
|
|
192
|
+
command: "r",
|
|
193
|
+
permissive: true,
|
|
194
|
+
roles: ["public"],
|
|
195
|
+
using_expression: "id < nextval('app.next_id'::regclass)",
|
|
196
|
+
with_check_expression: null,
|
|
197
|
+
owner: "test",
|
|
198
|
+
comment: null,
|
|
199
|
+
referenced_relations: [],
|
|
200
|
+
referenced_procedures: [],
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
const change = new CreateRlsPolicy({ policy });
|
|
204
|
+
|
|
205
|
+
// Expected once the gap is closed:
|
|
206
|
+
// expect(change.requires).toContain(stableId.sequence("app", "next_id"));
|
|
207
|
+
expect(change.requires.length).toBeGreaterThan(0);
|
|
208
|
+
});
|
|
81
209
|
});
|
|
@@ -45,6 +45,33 @@ export class CreateRlsPolicy extends CreateRlsPolicyChange {
|
|
|
45
45
|
// Owner dependency
|
|
46
46
|
dependencies.add(stableId.role(this.policy.owner));
|
|
47
47
|
|
|
48
|
+
// Relations and functions referenced inside USING / WITH CHECK
|
|
49
|
+
// expressions must exist before the policy is created. These come from
|
|
50
|
+
// pg_depend (populated by PostgreSQL's recordDependencyOnExpr at policy
|
|
51
|
+
// creation), not from re-parsing the expression text.
|
|
52
|
+
for (const ref of this.policy.referenced_relations) {
|
|
53
|
+
switch (ref.kind) {
|
|
54
|
+
case "table":
|
|
55
|
+
dependencies.add(stableId.table(ref.schema, ref.name));
|
|
56
|
+
break;
|
|
57
|
+
case "view":
|
|
58
|
+
dependencies.add(stableId.view(ref.schema, ref.name));
|
|
59
|
+
break;
|
|
60
|
+
case "materialized_view":
|
|
61
|
+
dependencies.add(stableId.materializedView(ref.schema, ref.name));
|
|
62
|
+
break;
|
|
63
|
+
case "foreign_table":
|
|
64
|
+
dependencies.add(stableId.foreignTable(ref.schema, ref.name));
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
for (const ref of this.policy.referenced_procedures) {
|
|
70
|
+
dependencies.add(
|
|
71
|
+
stableId.procedure(ref.schema, ref.name, ref.argument_types.join(",")),
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
48
75
|
return Array.from(dependencies);
|
|
49
76
|
}
|
|
50
77
|
|
|
@@ -11,6 +11,33 @@ const RlsPolicyCommandSchema = z.enum([
|
|
|
11
11
|
"*", // ALL commands
|
|
12
12
|
]);
|
|
13
13
|
|
|
14
|
+
const RlsPolicyReferencedRelationKindSchema = z.enum([
|
|
15
|
+
"table",
|
|
16
|
+
"view",
|
|
17
|
+
"materialized_view",
|
|
18
|
+
"foreign_table",
|
|
19
|
+
]);
|
|
20
|
+
|
|
21
|
+
const rlsPolicyReferencedRelationSchema = z.object({
|
|
22
|
+
kind: RlsPolicyReferencedRelationKindSchema,
|
|
23
|
+
schema: z.string(),
|
|
24
|
+
name: z.string(),
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
export type RlsPolicyReferencedRelation = z.infer<
|
|
28
|
+
typeof rlsPolicyReferencedRelationSchema
|
|
29
|
+
>;
|
|
30
|
+
|
|
31
|
+
const rlsPolicyReferencedProcedureSchema = z.object({
|
|
32
|
+
schema: z.string(),
|
|
33
|
+
name: z.string(),
|
|
34
|
+
argument_types: z.array(z.string()),
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
export type RlsPolicyReferencedProcedure = z.infer<
|
|
38
|
+
typeof rlsPolicyReferencedProcedureSchema
|
|
39
|
+
>;
|
|
40
|
+
|
|
14
41
|
const rlsPolicyPropsSchema = z.object({
|
|
15
42
|
schema: z.string(),
|
|
16
43
|
name: z.string(),
|
|
@@ -22,6 +49,8 @@ const rlsPolicyPropsSchema = z.object({
|
|
|
22
49
|
with_check_expression: z.string().nullable(),
|
|
23
50
|
owner: z.string(),
|
|
24
51
|
comment: z.string().nullable(),
|
|
52
|
+
referenced_relations: z.array(rlsPolicyReferencedRelationSchema),
|
|
53
|
+
referenced_procedures: z.array(rlsPolicyReferencedProcedureSchema),
|
|
25
54
|
});
|
|
26
55
|
|
|
27
56
|
export type RlsPolicyProps = z.infer<typeof rlsPolicyPropsSchema>;
|
|
@@ -37,6 +66,23 @@ export class RlsPolicy extends BasePgModel {
|
|
|
37
66
|
public readonly with_check_expression: RlsPolicyProps["with_check_expression"];
|
|
38
67
|
public readonly owner: RlsPolicyProps["owner"];
|
|
39
68
|
public readonly comment: RlsPolicyProps["comment"];
|
|
69
|
+
/**
|
|
70
|
+
* Tables / views / materialized views / foreign tables that
|
|
71
|
+
* `using_expression` / `with_check_expression` reference, sourced from
|
|
72
|
+
* `pg_depend` (`recordDependencyOnExpr` at policy creation). Drives
|
|
73
|
+
* ordering dependencies in `CreateRlsPolicy.requires`. Intentionally
|
|
74
|
+
* excluded from `dataFields` — it's derived from the expression text
|
|
75
|
+
* and changes lockstep with it.
|
|
76
|
+
*/
|
|
77
|
+
public readonly referenced_relations: RlsPolicyProps["referenced_relations"];
|
|
78
|
+
/**
|
|
79
|
+
* Functions / procedures that `using_expression` / `with_check_expression`
|
|
80
|
+
* reference, sourced from `pg_depend` (refclassid = `pg_proc`). The
|
|
81
|
+
* argument-type signature comes straight from `pg_proc.proargtypes` via
|
|
82
|
+
* `format_type`, so it matches the signature the procedure extractor
|
|
83
|
+
* embeds in `stableId.procedure(...)`. Not part of `dataFields`.
|
|
84
|
+
*/
|
|
85
|
+
public readonly referenced_procedures: RlsPolicyProps["referenced_procedures"];
|
|
40
86
|
|
|
41
87
|
constructor(props: RlsPolicyProps) {
|
|
42
88
|
super();
|
|
@@ -54,6 +100,10 @@ export class RlsPolicy extends BasePgModel {
|
|
|
54
100
|
this.with_check_expression = props.with_check_expression;
|
|
55
101
|
this.owner = props.owner;
|
|
56
102
|
this.comment = props.comment;
|
|
103
|
+
|
|
104
|
+
// Derived metadata (not part of equality)
|
|
105
|
+
this.referenced_relations = props.referenced_relations;
|
|
106
|
+
this.referenced_procedures = props.referenced_procedures;
|
|
57
107
|
}
|
|
58
108
|
|
|
59
109
|
get stableId(): `rlsPolicy:${string}` {
|
|
@@ -101,6 +151,59 @@ extension_table_oids as (
|
|
|
101
151
|
d.refclassid = 'pg_extension'::regclass
|
|
102
152
|
and d.classid = 'pg_class'::regclass
|
|
103
153
|
and d.deptype = 'e'
|
|
154
|
+
),
|
|
155
|
+
policy_relation_deps as (
|
|
156
|
+
-- Relations referenced inside polqual / polwithcheck. PostgreSQL records
|
|
157
|
+
-- these via recordDependencyOnExpr(..., DEPENDENCY_NORMAL = 'n') at
|
|
158
|
+
-- CREATE POLICY time, so pg_depend is authoritative and we don't need to
|
|
159
|
+
-- re-parse the expression text. Covers regular tables, partitioned
|
|
160
|
+
-- tables, views, materialized views, and foreign tables — any relation
|
|
161
|
+
-- kind the policy can reference in a subquery.
|
|
162
|
+
select distinct
|
|
163
|
+
d.objid as policy_oid,
|
|
164
|
+
case ref_c.relkind
|
|
165
|
+
when 'r' then 'table'
|
|
166
|
+
when 'p' then 'table'
|
|
167
|
+
when 'v' then 'view'
|
|
168
|
+
when 'm' then 'materialized_view'
|
|
169
|
+
when 'f' then 'foreign_table'
|
|
170
|
+
end as ref_kind,
|
|
171
|
+
ref_ns.nspname as ref_schema,
|
|
172
|
+
ref_c.relname as ref_name
|
|
173
|
+
from
|
|
174
|
+
pg_depend d
|
|
175
|
+
join pg_policy p on p.oid = d.objid
|
|
176
|
+
join pg_class ref_c on ref_c.oid = d.refobjid
|
|
177
|
+
join pg_namespace ref_ns on ref_ns.oid = ref_c.relnamespace
|
|
178
|
+
where
|
|
179
|
+
d.classid = 'pg_policy'::regclass
|
|
180
|
+
and d.refclassid = 'pg_class'::regclass
|
|
181
|
+
and d.deptype = 'n'
|
|
182
|
+
and ref_c.relkind in ('r', 'p', 'v', 'm', 'f')
|
|
183
|
+
and d.refobjid <> p.polrelid
|
|
184
|
+
),
|
|
185
|
+
policy_procedure_deps as (
|
|
186
|
+
-- Functions / procedures referenced inside polqual / polwithcheck. Same
|
|
187
|
+
-- pg_depend mechanism as above, just refclassid = pg_proc. proargtypes
|
|
188
|
+
-- formatted via format_type(oid, null) matches the signature produced by
|
|
189
|
+
-- the procedure extractor (see procedure.model.ts), so stableId.procedure
|
|
190
|
+
-- on both sides of the diff lines up exactly.
|
|
191
|
+
select distinct
|
|
192
|
+
d.objid as policy_oid,
|
|
193
|
+
ref_ns.nspname as ref_schema,
|
|
194
|
+
ref_p.proname as ref_name,
|
|
195
|
+
array(
|
|
196
|
+
select format_type(oid, null)
|
|
197
|
+
from unnest(ref_p.proargtypes) as oid
|
|
198
|
+
) as ref_argument_types
|
|
199
|
+
from
|
|
200
|
+
pg_depend d
|
|
201
|
+
join pg_proc ref_p on ref_p.oid = d.refobjid
|
|
202
|
+
join pg_namespace ref_ns on ref_ns.oid = ref_p.pronamespace
|
|
203
|
+
where
|
|
204
|
+
d.classid = 'pg_policy'::regclass
|
|
205
|
+
and d.refclassid = 'pg_proc'::regclass
|
|
206
|
+
and d.deptype = 'n'
|
|
104
207
|
)
|
|
105
208
|
select
|
|
106
209
|
tc.relnamespace::regnamespace::text as schema,
|
|
@@ -120,7 +223,37 @@ select
|
|
|
120
223
|
pg_get_expr(p.polqual, p.polrelid) as using_expression,
|
|
121
224
|
pg_get_expr(p.polwithcheck, p.polrelid) as with_check_expression,
|
|
122
225
|
tc.relowner::regrole::text as owner,
|
|
123
|
-
obj_description(p.oid, 'pg_policy') as comment
|
|
226
|
+
obj_description(p.oid, 'pg_policy') as comment,
|
|
227
|
+
coalesce(
|
|
228
|
+
(
|
|
229
|
+
select json_agg(
|
|
230
|
+
json_build_object(
|
|
231
|
+
'kind', prd.ref_kind,
|
|
232
|
+
'schema', prd.ref_schema,
|
|
233
|
+
'name', prd.ref_name
|
|
234
|
+
)
|
|
235
|
+
order by prd.ref_schema, prd.ref_name
|
|
236
|
+
)
|
|
237
|
+
from policy_relation_deps prd
|
|
238
|
+
where prd.policy_oid = p.oid
|
|
239
|
+
),
|
|
240
|
+
'[]'
|
|
241
|
+
) as referenced_relations,
|
|
242
|
+
coalesce(
|
|
243
|
+
(
|
|
244
|
+
select json_agg(
|
|
245
|
+
json_build_object(
|
|
246
|
+
'schema', ppd.ref_schema,
|
|
247
|
+
'name', ppd.ref_name,
|
|
248
|
+
'argument_types', ppd.ref_argument_types
|
|
249
|
+
)
|
|
250
|
+
order by ppd.ref_schema, ppd.ref_name, ppd.ref_argument_types
|
|
251
|
+
)
|
|
252
|
+
from policy_procedure_deps ppd
|
|
253
|
+
where ppd.policy_oid = p.oid
|
|
254
|
+
),
|
|
255
|
+
'[]'
|
|
256
|
+
) as referenced_procedures
|
|
124
257
|
from
|
|
125
258
|
pg_catalog.pg_policy p
|
|
126
259
|
inner join pg_catalog.pg_class tc on tc.oid = p.polrelid
|