@supabase/pg-delta 1.0.0-alpha.27 → 1.0.0-alpha.29
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/catalog.diff.js +22 -3
- package/dist/core/expand-replace-dependencies.d.ts +3 -1
- package/dist/core/expand-replace-dependencies.js +117 -7
- package/dist/core/objects/base.change.d.ts +12 -0
- package/dist/core/objects/base.change.js +14 -0
- package/dist/core/objects/materialized-view/materialized-view.diff.d.ts +1 -0
- package/dist/core/objects/materialized-view/materialized-view.diff.js +59 -59
- package/dist/core/objects/table/changes/table.alter.d.ts +1 -0
- package/dist/core/objects/table/changes/table.alter.js +8 -0
- package/dist/core/objects/view/view.diff.d.ts +1 -0
- package/dist/core/objects/view/view.diff.js +35 -34
- package/dist/core/sort/cycle-breakers.js +8 -3
- package/dist/core/sort/graph-builder.js +6 -0
- package/package.json +2 -2
- package/src/core/catalog.diff.test.ts +173 -0
- package/src/core/catalog.diff.ts +24 -3
- package/src/core/expand-replace-dependencies.test.ts +282 -0
- package/src/core/expand-replace-dependencies.ts +165 -7
- package/src/core/objects/base.change.ts +15 -0
- package/src/core/objects/materialized-view/materialized-view.diff.test.ts +3 -2
- package/src/core/objects/materialized-view/materialized-view.diff.ts +99 -92
- package/src/core/objects/table/changes/table.alter.ts +9 -0
- package/src/core/objects/view/view.diff.ts +67 -60
- package/src/core/sort/cycle-breakers.test.ts +126 -0
- package/src/core/sort/cycle-breakers.ts +12 -2
- package/src/core/sort/graph-builder.ts +6 -0
- package/src/core/sort/sort-changes.test.ts +73 -1
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { diffCatalogs } from "./catalog.diff.ts";
|
|
3
|
+
import { Catalog, createEmptyCatalog } from "./catalog.model.ts";
|
|
4
|
+
import { Role, type RoleProps } from "./objects/role/role.model.ts";
|
|
5
|
+
import {
|
|
6
|
+
GrantViewPrivileges,
|
|
7
|
+
RevokeViewPrivileges,
|
|
8
|
+
} from "./objects/view/changes/view.privilege.ts";
|
|
9
|
+
import { View, type ViewProps } from "./objects/view/view.model.ts";
|
|
10
|
+
|
|
11
|
+
const idColumn: ViewProps["columns"][number] = {
|
|
12
|
+
name: "id",
|
|
13
|
+
position: 1,
|
|
14
|
+
data_type: "integer",
|
|
15
|
+
data_type_str: "integer",
|
|
16
|
+
is_custom_type: false,
|
|
17
|
+
custom_type_type: null,
|
|
18
|
+
custom_type_category: null,
|
|
19
|
+
custom_type_schema: null,
|
|
20
|
+
custom_type_name: null,
|
|
21
|
+
not_null: false,
|
|
22
|
+
is_identity: false,
|
|
23
|
+
is_identity_always: false,
|
|
24
|
+
is_generated: false,
|
|
25
|
+
collation: null,
|
|
26
|
+
default: null,
|
|
27
|
+
comment: null,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const nameColumn: ViewProps["columns"][number] = {
|
|
31
|
+
name: "name",
|
|
32
|
+
position: 2,
|
|
33
|
+
data_type: "text",
|
|
34
|
+
data_type_str: "text",
|
|
35
|
+
is_custom_type: false,
|
|
36
|
+
custom_type_type: null,
|
|
37
|
+
custom_type_category: null,
|
|
38
|
+
custom_type_schema: null,
|
|
39
|
+
custom_type_name: null,
|
|
40
|
+
not_null: false,
|
|
41
|
+
is_identity: false,
|
|
42
|
+
is_identity_always: false,
|
|
43
|
+
is_generated: false,
|
|
44
|
+
collation: null,
|
|
45
|
+
default: null,
|
|
46
|
+
comment: null,
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const baseView: ViewProps = {
|
|
50
|
+
schema: "public",
|
|
51
|
+
name: "replaced_view",
|
|
52
|
+
definition: "SELECT id FROM source_table",
|
|
53
|
+
row_security: false,
|
|
54
|
+
force_row_security: false,
|
|
55
|
+
has_indexes: false,
|
|
56
|
+
has_rules: false,
|
|
57
|
+
has_triggers: false,
|
|
58
|
+
has_subclasses: false,
|
|
59
|
+
is_populated: true,
|
|
60
|
+
replica_identity: "d",
|
|
61
|
+
is_partition: false,
|
|
62
|
+
options: null,
|
|
63
|
+
partition_bound: null,
|
|
64
|
+
owner: "postgres",
|
|
65
|
+
comment: null,
|
|
66
|
+
columns: [idColumn],
|
|
67
|
+
privileges: [],
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const makeView = (override: Partial<ViewProps> = {}) =>
|
|
71
|
+
new View({
|
|
72
|
+
...baseView,
|
|
73
|
+
...override,
|
|
74
|
+
columns: override.columns ?? [...baseView.columns],
|
|
75
|
+
privileges: override.privileges ?? [...baseView.privileges],
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const makeRole = (name: string, override: Partial<RoleProps> = {}) =>
|
|
79
|
+
new Role({
|
|
80
|
+
name,
|
|
81
|
+
is_superuser: false,
|
|
82
|
+
can_inherit: true,
|
|
83
|
+
can_create_roles: false,
|
|
84
|
+
can_create_databases: false,
|
|
85
|
+
can_login: true,
|
|
86
|
+
can_replicate: false,
|
|
87
|
+
connection_limit: null,
|
|
88
|
+
can_bypass_rls: false,
|
|
89
|
+
config: null,
|
|
90
|
+
comment: null,
|
|
91
|
+
members: [],
|
|
92
|
+
default_privileges: [],
|
|
93
|
+
security_labels: [],
|
|
94
|
+
...override,
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
describe("catalog.diff", () => {
|
|
98
|
+
test("keeps replacement-created view grants through dropped-target privilege filtering", async () => {
|
|
99
|
+
const baseline = await createEmptyCatalog(170000, "postgres");
|
|
100
|
+
const mainView = makeView();
|
|
101
|
+
const branchView = makeView({
|
|
102
|
+
definition: "SELECT id, name FROM source_table",
|
|
103
|
+
columns: [...mainView.columns, nameColumn],
|
|
104
|
+
privileges: [
|
|
105
|
+
{ grantee: "view_reader", privilege: "SELECT", grantable: false },
|
|
106
|
+
],
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
const main = new Catalog({
|
|
110
|
+
// oxlint-disable-next-line typescript/no-misused-spread
|
|
111
|
+
...baseline,
|
|
112
|
+
views: { [mainView.stableId]: mainView },
|
|
113
|
+
});
|
|
114
|
+
const branch = new Catalog({
|
|
115
|
+
// oxlint-disable-next-line typescript/no-misused-spread
|
|
116
|
+
...baseline,
|
|
117
|
+
views: { [branchView.stableId]: branchView },
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
const changes = diffCatalogs(main, branch);
|
|
121
|
+
|
|
122
|
+
expect(
|
|
123
|
+
changes.some((change) => change instanceof GrantViewPrivileges),
|
|
124
|
+
).toBe(true);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test("keeps replacement-created view revokes through dropped-target privilege filtering", async () => {
|
|
128
|
+
const baseline = await createEmptyCatalog(170000, "postgres");
|
|
129
|
+
const mainView = makeView();
|
|
130
|
+
const branchView = makeView({
|
|
131
|
+
definition: "SELECT id, name FROM source_table",
|
|
132
|
+
columns: [...mainView.columns, nameColumn],
|
|
133
|
+
});
|
|
134
|
+
const postgres = makeRole("postgres", {
|
|
135
|
+
default_privileges: [
|
|
136
|
+
{
|
|
137
|
+
in_schema: "public",
|
|
138
|
+
objtype: "r",
|
|
139
|
+
grantee: "view_reader",
|
|
140
|
+
privileges: [{ privilege: "SELECT", grantable: false }],
|
|
141
|
+
is_implicit: false,
|
|
142
|
+
},
|
|
143
|
+
],
|
|
144
|
+
});
|
|
145
|
+
const viewReader = makeRole("view_reader");
|
|
146
|
+
|
|
147
|
+
const roles = {
|
|
148
|
+
[postgres.stableId]: postgres,
|
|
149
|
+
[viewReader.stableId]: viewReader,
|
|
150
|
+
};
|
|
151
|
+
const main = new Catalog({
|
|
152
|
+
// oxlint-disable-next-line typescript/no-misused-spread
|
|
153
|
+
...baseline,
|
|
154
|
+
roles,
|
|
155
|
+
views: { [mainView.stableId]: mainView },
|
|
156
|
+
});
|
|
157
|
+
const branch = new Catalog({
|
|
158
|
+
// oxlint-disable-next-line typescript/no-misused-spread
|
|
159
|
+
...baseline,
|
|
160
|
+
roles,
|
|
161
|
+
views: { [branchView.stableId]: branchView },
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
// The recreated view inherits SELECT from default privileges, but the
|
|
165
|
+
// branch model wants no explicit reader ACL. The replacement filter must
|
|
166
|
+
// keep the generated REVOKE even though the old view stable id is dropped.
|
|
167
|
+
const changes = diffCatalogs(main, branch);
|
|
168
|
+
|
|
169
|
+
expect(
|
|
170
|
+
changes.some((change) => change instanceof RevokeViewPrivileges),
|
|
171
|
+
).toBe(true);
|
|
172
|
+
});
|
|
173
|
+
});
|
package/src/core/catalog.diff.ts
CHANGED
|
@@ -217,19 +217,39 @@ export function diffCatalogs(
|
|
|
217
217
|
...diffForeignTables(diffContext, main.foreignTables, branch.foreignTables),
|
|
218
218
|
);
|
|
219
219
|
|
|
220
|
-
// Filter privilege
|
|
221
|
-
// Avoid emitting redundant
|
|
220
|
+
// Filter privilege changes for objects that are only being dropped.
|
|
221
|
+
// Avoid emitting redundant ACL statements for targets that will no longer exist.
|
|
222
222
|
const droppedObjectStableIds = new Set<string>();
|
|
223
|
+
const createdStableIds = new Set<string>();
|
|
223
224
|
for (const change of changes) {
|
|
224
225
|
if (change.operation === "drop" && change.scope === "object") {
|
|
225
226
|
for (const dep of change.requires) {
|
|
226
227
|
droppedObjectStableIds.add(dep);
|
|
227
228
|
}
|
|
228
229
|
}
|
|
230
|
+
if (change.operation === "create" && change.scope === "object") {
|
|
231
|
+
for (const dep of change.creates) {
|
|
232
|
+
createdStableIds.add(dep);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
229
235
|
}
|
|
236
|
+
// A pure DROP does not need ACL cleanup: the target object is going away.
|
|
237
|
+
// A replacement is different: it has both DROP and CREATE for the same stable
|
|
238
|
+
// id, and its privilege ALTERs describe the ACL state of the newly created
|
|
239
|
+
// object. Keep all of them, including REVOKE/REVOKE GRANT OPTION generated to
|
|
240
|
+
// subtract privileges inherited from ALTER DEFAULT PRIVILEGES at create time.
|
|
241
|
+
const replacementStableIds = new Set(
|
|
242
|
+
[...droppedObjectStableIds].filter((id) => createdStableIds.has(id)),
|
|
243
|
+
);
|
|
230
244
|
let filteredChanges = changes.filter((change) => {
|
|
231
245
|
if (change.operation === "alter" && change.scope === "privilege") {
|
|
232
|
-
|
|
246
|
+
const targetStableId = getPrivilegeTargetStableId(change);
|
|
247
|
+
// Checking only privilege creates would keep replacement GRANTs but drop
|
|
248
|
+
// replacement REVOKEs, so preserve by replacement target stable id instead.
|
|
249
|
+
if (replacementStableIds.has(targetStableId)) {
|
|
250
|
+
return true;
|
|
251
|
+
}
|
|
252
|
+
return !droppedObjectStableIds.has(targetStableId);
|
|
233
253
|
}
|
|
234
254
|
return true;
|
|
235
255
|
});
|
|
@@ -238,6 +258,7 @@ export function diffCatalogs(
|
|
|
238
258
|
changes: filteredChanges,
|
|
239
259
|
mainCatalog: main,
|
|
240
260
|
branchCatalog: branch,
|
|
261
|
+
diffContext,
|
|
241
262
|
});
|
|
242
263
|
filteredChanges = normalizePostDiffChanges({
|
|
243
264
|
changes: expandedDependencies.changes,
|
|
@@ -6,6 +6,14 @@ import { DefaultPrivilegeState } from "./objects/base.default-privileges.ts";
|
|
|
6
6
|
import { CreateProcedure } from "./objects/procedure/changes/procedure.create.ts";
|
|
7
7
|
import { DropProcedure } from "./objects/procedure/changes/procedure.drop.ts";
|
|
8
8
|
import { Procedure } from "./objects/procedure/procedure.model.ts";
|
|
9
|
+
import {
|
|
10
|
+
AlterRlsPolicySetUsingExpression,
|
|
11
|
+
AlterRlsPolicySetWithCheckExpression,
|
|
12
|
+
} from "./objects/rls-policy/changes/rls-policy.alter.ts";
|
|
13
|
+
import { CreateCommentOnRlsPolicy } from "./objects/rls-policy/changes/rls-policy.comment.ts";
|
|
14
|
+
import { CreateRlsPolicy } from "./objects/rls-policy/changes/rls-policy.create.ts";
|
|
15
|
+
import { DropRlsPolicy } from "./objects/rls-policy/changes/rls-policy.drop.ts";
|
|
16
|
+
import { RlsPolicy } from "./objects/rls-policy/rls-policy.model.ts";
|
|
9
17
|
import { CreateSequence } from "./objects/sequence/changes/sequence.create.ts";
|
|
10
18
|
import { DropSequence } from "./objects/sequence/changes/sequence.drop.ts";
|
|
11
19
|
import { diffSequences } from "./objects/sequence/sequence.diff.ts";
|
|
@@ -40,6 +48,7 @@ function mockChange(overrides: {
|
|
|
40
48
|
scope: "object",
|
|
41
49
|
creates,
|
|
42
50
|
drops,
|
|
51
|
+
invalidates: [],
|
|
43
52
|
requires: [],
|
|
44
53
|
table: { schema: "public", name: "t" },
|
|
45
54
|
serialize: () => [],
|
|
@@ -49,6 +58,20 @@ function mockChange(overrides: {
|
|
|
49
58
|
} as unknown as Change;
|
|
50
59
|
}
|
|
51
60
|
|
|
61
|
+
function mockInvalidatingChange(invalidates: string[]): Change {
|
|
62
|
+
return {
|
|
63
|
+
objectType: "table",
|
|
64
|
+
operation: "alter",
|
|
65
|
+
scope: "object",
|
|
66
|
+
creates: [],
|
|
67
|
+
drops: [],
|
|
68
|
+
invalidates,
|
|
69
|
+
requires: [],
|
|
70
|
+
table: { schema: "public", name: "t" },
|
|
71
|
+
serialize: () => "",
|
|
72
|
+
} as unknown as Change;
|
|
73
|
+
}
|
|
74
|
+
|
|
52
75
|
describe("expandReplaceDependencies", () => {
|
|
53
76
|
test("returns changes unchanged when there are no replace roots", async () => {
|
|
54
77
|
const catalog = await createEmptyCatalog(160004, "u");
|
|
@@ -695,4 +718,263 @@ describe("expandReplaceDependencies", () => {
|
|
|
695
718
|
|
|
696
719
|
expect(expanded.changes.some((c) => c instanceof DropView)).toBe(true);
|
|
697
720
|
});
|
|
721
|
+
|
|
722
|
+
test("promotes dependent RLS policy when a procedure's signature changes", async () => {
|
|
723
|
+
const baseline = await createEmptyCatalog(170000, "postgres");
|
|
724
|
+
const procedureBase = {
|
|
725
|
+
schema: "public",
|
|
726
|
+
name: "check_role",
|
|
727
|
+
kind: "f" as const,
|
|
728
|
+
return_type: "boolean",
|
|
729
|
+
return_type_schema: "pg_catalog",
|
|
730
|
+
language: "plpgsql",
|
|
731
|
+
security_definer: false,
|
|
732
|
+
volatility: "v" as const,
|
|
733
|
+
parallel_safety: "u" as const,
|
|
734
|
+
execution_cost: 100,
|
|
735
|
+
result_rows: 0,
|
|
736
|
+
is_strict: false,
|
|
737
|
+
leakproof: false,
|
|
738
|
+
returns_set: false,
|
|
739
|
+
argument_names: ["id", "role"],
|
|
740
|
+
all_argument_types: null,
|
|
741
|
+
argument_modes: null,
|
|
742
|
+
source_code: "BEGIN RETURN true; END;",
|
|
743
|
+
binary_path: null,
|
|
744
|
+
sql_body: null,
|
|
745
|
+
config: null,
|
|
746
|
+
owner: "postgres",
|
|
747
|
+
comment: null,
|
|
748
|
+
privileges: [],
|
|
749
|
+
};
|
|
750
|
+
const mainProcedure = new Procedure({
|
|
751
|
+
...procedureBase,
|
|
752
|
+
argument_count: 2,
|
|
753
|
+
argument_default_count: 0,
|
|
754
|
+
argument_types: ["uuid", "text"],
|
|
755
|
+
argument_defaults: null,
|
|
756
|
+
definition:
|
|
757
|
+
"CREATE FUNCTION public.check_role(id uuid, role text) RETURNS boolean ...",
|
|
758
|
+
});
|
|
759
|
+
const branchProcedure = new Procedure({
|
|
760
|
+
...procedureBase,
|
|
761
|
+
argument_count: 3,
|
|
762
|
+
argument_default_count: 1,
|
|
763
|
+
argument_names: ["id", "role", "extra"],
|
|
764
|
+
argument_types: ["uuid", "text", "text"],
|
|
765
|
+
argument_defaults: "'default'::text",
|
|
766
|
+
definition:
|
|
767
|
+
"CREATE FUNCTION public.check_role(id uuid, role text, extra text DEFAULT 'default'::text) RETURNS boolean ...",
|
|
768
|
+
});
|
|
769
|
+
const policyBase = {
|
|
770
|
+
schema: "public",
|
|
771
|
+
table_name: "profiles",
|
|
772
|
+
name: "check_role_policy",
|
|
773
|
+
command: "r" as const,
|
|
774
|
+
permissive: true,
|
|
775
|
+
roles: ["public"],
|
|
776
|
+
using_expression: "public.check_role(id, role)",
|
|
777
|
+
with_check_expression: null,
|
|
778
|
+
owner: "postgres",
|
|
779
|
+
comment: "policy comment",
|
|
780
|
+
referenced_relations: [],
|
|
781
|
+
};
|
|
782
|
+
const mainPolicy = new RlsPolicy({
|
|
783
|
+
...policyBase,
|
|
784
|
+
referenced_procedures: [
|
|
785
|
+
{
|
|
786
|
+
schema: "public",
|
|
787
|
+
name: "check_role",
|
|
788
|
+
argument_types: ["uuid", "text"],
|
|
789
|
+
},
|
|
790
|
+
],
|
|
791
|
+
});
|
|
792
|
+
const branchPolicy = new RlsPolicy({
|
|
793
|
+
...policyBase,
|
|
794
|
+
referenced_procedures: [
|
|
795
|
+
{
|
|
796
|
+
schema: "public",
|
|
797
|
+
name: "check_role",
|
|
798
|
+
argument_types: ["uuid", "text", "text"],
|
|
799
|
+
},
|
|
800
|
+
],
|
|
801
|
+
});
|
|
802
|
+
|
|
803
|
+
const changes: Change[] = [
|
|
804
|
+
new DropProcedure({ procedure: mainProcedure }),
|
|
805
|
+
new CreateProcedure({ procedure: branchProcedure }),
|
|
806
|
+
];
|
|
807
|
+
const mainCatalog = new Catalog({
|
|
808
|
+
// oxlint-disable-next-line typescript/no-misused-spread
|
|
809
|
+
...baseline,
|
|
810
|
+
procedures: { [mainProcedure.stableId]: mainProcedure },
|
|
811
|
+
rlsPolicies: { [mainPolicy.stableId]: mainPolicy },
|
|
812
|
+
depends: [
|
|
813
|
+
{
|
|
814
|
+
dependent_stable_id: mainPolicy.stableId,
|
|
815
|
+
referenced_stable_id: mainProcedure.stableId,
|
|
816
|
+
deptype: "n",
|
|
817
|
+
},
|
|
818
|
+
],
|
|
819
|
+
});
|
|
820
|
+
const branchCatalog = new Catalog({
|
|
821
|
+
// oxlint-disable-next-line typescript/no-misused-spread
|
|
822
|
+
...baseline,
|
|
823
|
+
procedures: { [branchProcedure.stableId]: branchProcedure },
|
|
824
|
+
rlsPolicies: { [branchPolicy.stableId]: branchPolicy },
|
|
825
|
+
depends: [],
|
|
826
|
+
});
|
|
827
|
+
|
|
828
|
+
const expanded = expandReplaceDependencies({
|
|
829
|
+
changes,
|
|
830
|
+
mainCatalog,
|
|
831
|
+
branchCatalog,
|
|
832
|
+
});
|
|
833
|
+
|
|
834
|
+
expect(expanded.changes.some((c) => c instanceof DropRlsPolicy)).toBe(true);
|
|
835
|
+
expect(expanded.changes.some((c) => c instanceof CreateRlsPolicy)).toBe(
|
|
836
|
+
true,
|
|
837
|
+
);
|
|
838
|
+
expect(
|
|
839
|
+
expanded.changes.some((c) => c instanceof CreateCommentOnRlsPolicy),
|
|
840
|
+
).toBe(true);
|
|
841
|
+
});
|
|
842
|
+
|
|
843
|
+
test("promotes dependent RLS policy when a referenced column is invalidated", async () => {
|
|
844
|
+
const baseline = await createEmptyCatalog(170000, "postgres");
|
|
845
|
+
const columnTemplate = {
|
|
846
|
+
position: 1,
|
|
847
|
+
data_type: "text",
|
|
848
|
+
data_type_str: "text",
|
|
849
|
+
is_custom_type: false,
|
|
850
|
+
custom_type_type: null,
|
|
851
|
+
custom_type_category: null,
|
|
852
|
+
custom_type_schema: null,
|
|
853
|
+
custom_type_name: null,
|
|
854
|
+
not_null: true,
|
|
855
|
+
is_identity: false,
|
|
856
|
+
is_identity_always: false,
|
|
857
|
+
is_generated: false,
|
|
858
|
+
collation: null,
|
|
859
|
+
default: null,
|
|
860
|
+
comment: null,
|
|
861
|
+
};
|
|
862
|
+
const tableBase = {
|
|
863
|
+
schema: "public",
|
|
864
|
+
name: "solution_categories_with_policy",
|
|
865
|
+
persistence: "p" as const,
|
|
866
|
+
row_security: true,
|
|
867
|
+
force_row_security: false,
|
|
868
|
+
has_indexes: false,
|
|
869
|
+
has_rules: false,
|
|
870
|
+
has_triggers: false,
|
|
871
|
+
has_subclasses: false,
|
|
872
|
+
is_populated: true,
|
|
873
|
+
replica_identity: "d" as const,
|
|
874
|
+
is_partition: false,
|
|
875
|
+
options: null,
|
|
876
|
+
partition_bound: null,
|
|
877
|
+
partition_by: null,
|
|
878
|
+
owner: "postgres",
|
|
879
|
+
comment: null,
|
|
880
|
+
parent_schema: null,
|
|
881
|
+
parent_name: null,
|
|
882
|
+
constraints: [],
|
|
883
|
+
privileges: [],
|
|
884
|
+
};
|
|
885
|
+
const mainRoleColumn = {
|
|
886
|
+
...columnTemplate,
|
|
887
|
+
name: "role",
|
|
888
|
+
};
|
|
889
|
+
const branchRoleColumn = {
|
|
890
|
+
...columnTemplate,
|
|
891
|
+
name: "role",
|
|
892
|
+
data_type: "user_role_enum",
|
|
893
|
+
data_type_str: "public.user_role_enum",
|
|
894
|
+
is_custom_type: true,
|
|
895
|
+
custom_type_type: "e",
|
|
896
|
+
custom_type_category: "E",
|
|
897
|
+
custom_type_schema: "public",
|
|
898
|
+
custom_type_name: "user_role_enum",
|
|
899
|
+
};
|
|
900
|
+
const mainTable = new Table({
|
|
901
|
+
...tableBase,
|
|
902
|
+
columns: [mainRoleColumn],
|
|
903
|
+
});
|
|
904
|
+
const branchTable = new Table({
|
|
905
|
+
...tableBase,
|
|
906
|
+
columns: [branchRoleColumn],
|
|
907
|
+
});
|
|
908
|
+
const policyBase = {
|
|
909
|
+
schema: "public",
|
|
910
|
+
table_name: "solution_categories_with_policy",
|
|
911
|
+
name: "categories_admin_manage",
|
|
912
|
+
command: "*" as const,
|
|
913
|
+
permissive: true,
|
|
914
|
+
roles: ["public"],
|
|
915
|
+
owner: "postgres",
|
|
916
|
+
comment: null,
|
|
917
|
+
referenced_relations: [],
|
|
918
|
+
referenced_procedures: [],
|
|
919
|
+
};
|
|
920
|
+
const mainPolicy = new RlsPolicy({
|
|
921
|
+
...policyBase,
|
|
922
|
+
using_expression: "role = 'admin'",
|
|
923
|
+
with_check_expression: "role = 'admin'",
|
|
924
|
+
});
|
|
925
|
+
const branchPolicy = new RlsPolicy({
|
|
926
|
+
...policyBase,
|
|
927
|
+
using_expression: "role = 'admin'::public.user_role_enum",
|
|
928
|
+
with_check_expression: "role = 'admin'::public.user_role_enum",
|
|
929
|
+
});
|
|
930
|
+
const alterUsing = new AlterRlsPolicySetUsingExpression({
|
|
931
|
+
policy: mainPolicy,
|
|
932
|
+
usingExpression: branchPolicy.using_expression,
|
|
933
|
+
});
|
|
934
|
+
const alterWithCheck = new AlterRlsPolicySetWithCheckExpression({
|
|
935
|
+
policy: mainPolicy,
|
|
936
|
+
withCheckExpression: branchPolicy.with_check_expression,
|
|
937
|
+
});
|
|
938
|
+
const changes: Change[] = [
|
|
939
|
+
mockInvalidatingChange([
|
|
940
|
+
"column:public.solution_categories_with_policy.role",
|
|
941
|
+
]),
|
|
942
|
+
alterUsing,
|
|
943
|
+
alterWithCheck,
|
|
944
|
+
];
|
|
945
|
+
const mainCatalog = new Catalog({
|
|
946
|
+
// oxlint-disable-next-line typescript/no-misused-spread
|
|
947
|
+
...baseline,
|
|
948
|
+
tables: { [mainTable.stableId]: mainTable },
|
|
949
|
+
rlsPolicies: { [mainPolicy.stableId]: mainPolicy },
|
|
950
|
+
depends: [
|
|
951
|
+
{
|
|
952
|
+
dependent_stable_id: mainPolicy.stableId,
|
|
953
|
+
referenced_stable_id:
|
|
954
|
+
"column:public.solution_categories_with_policy.role",
|
|
955
|
+
deptype: "n",
|
|
956
|
+
},
|
|
957
|
+
],
|
|
958
|
+
});
|
|
959
|
+
const branchCatalog = new Catalog({
|
|
960
|
+
// oxlint-disable-next-line typescript/no-misused-spread
|
|
961
|
+
...baseline,
|
|
962
|
+
tables: { [branchTable.stableId]: branchTable },
|
|
963
|
+
rlsPolicies: { [branchPolicy.stableId]: branchPolicy },
|
|
964
|
+
depends: [],
|
|
965
|
+
});
|
|
966
|
+
|
|
967
|
+
const expanded = expandReplaceDependencies({
|
|
968
|
+
changes,
|
|
969
|
+
mainCatalog,
|
|
970
|
+
branchCatalog,
|
|
971
|
+
});
|
|
972
|
+
|
|
973
|
+
expect(expanded.changes.some((c) => c instanceof DropRlsPolicy)).toBe(true);
|
|
974
|
+
expect(expanded.changes.some((c) => c instanceof CreateRlsPolicy)).toBe(
|
|
975
|
+
true,
|
|
976
|
+
);
|
|
977
|
+
expect(expanded.changes).not.toContain(alterUsing);
|
|
978
|
+
expect(expanded.changes).not.toContain(alterWithCheck);
|
|
979
|
+
});
|
|
698
980
|
});
|