@supabase/pg-delta 1.0.0-alpha.21 → 1.0.0-alpha.23
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 +4 -3
- package/dist/core/catalog.model.d.ts +8 -1
- package/dist/core/catalog.model.js +10 -8
- package/dist/core/expand-replace-dependencies.js +23 -0
- package/dist/core/integrations/filter/flatten.js +13 -0
- package/dist/core/objects/aggregate/aggregate.diff.js +16 -0
- package/dist/core/objects/aggregate/aggregate.model.d.ts +10 -0
- package/dist/core/objects/aggregate/aggregate.model.js +19 -1
- package/dist/core/objects/aggregate/changes/aggregate.base.d.ts +1 -1
- package/dist/core/objects/aggregate/changes/aggregate.security-label.d.ts +28 -0
- package/dist/core/objects/aggregate/changes/aggregate.security-label.js +64 -0
- package/dist/core/objects/aggregate/changes/aggregate.types.d.ts +2 -1
- package/dist/core/objects/base.model.d.ts +8 -0
- package/dist/core/objects/base.model.js +2 -0
- package/dist/core/objects/domain/changes/domain.base.d.ts +1 -1
- package/dist/core/objects/domain/changes/domain.security-label.d.ts +28 -0
- package/dist/core/objects/domain/changes/domain.security-label.js +61 -0
- package/dist/core/objects/domain/changes/domain.types.d.ts +2 -1
- package/dist/core/objects/domain/domain.diff.js +16 -0
- package/dist/core/objects/domain/domain.model.d.ts +10 -0
- package/dist/core/objects/domain/domain.model.js +19 -1
- package/dist/core/objects/event-trigger/changes/event-trigger.base.d.ts +1 -1
- package/dist/core/objects/event-trigger/changes/event-trigger.security-label.d.ts +28 -0
- package/dist/core/objects/event-trigger/changes/event-trigger.security-label.js +61 -0
- package/dist/core/objects/event-trigger/changes/event-trigger.types.d.ts +2 -1
- package/dist/core/objects/event-trigger/event-trigger.diff.js +16 -0
- package/dist/core/objects/event-trigger/event-trigger.model.d.ts +10 -0
- package/dist/core/objects/event-trigger/event-trigger.model.js +19 -1
- package/dist/core/objects/extract-with-retry.d.ts +36 -0
- package/dist/core/objects/extract-with-retry.js +51 -0
- package/dist/core/objects/foreign-data-wrapper/foreign-table/changes/foreign-table.base.d.ts +1 -1
- package/dist/core/objects/foreign-data-wrapper/foreign-table/changes/foreign-table.security-label.d.ts +28 -0
- package/dist/core/objects/foreign-data-wrapper/foreign-table/changes/foreign-table.security-label.js +61 -0
- package/dist/core/objects/foreign-data-wrapper/foreign-table/changes/foreign-table.types.d.ts +2 -1
- package/dist/core/objects/foreign-data-wrapper/foreign-table/foreign-table.diff.js +16 -0
- package/dist/core/objects/foreign-data-wrapper/foreign-table/foreign-table.model.d.ts +22 -0
- package/dist/core/objects/foreign-data-wrapper/foreign-table/foreign-table.model.js +20 -1
- package/dist/core/objects/index/index.diff.js +0 -1
- package/dist/core/objects/index/index.model.d.ts +2 -3
- package/dist/core/objects/index/index.model.js +17 -6
- package/dist/core/objects/materialized-view/changes/materialized-view.base.d.ts +1 -1
- package/dist/core/objects/materialized-view/changes/materialized-view.security-label.d.ts +28 -0
- package/dist/core/objects/materialized-view/changes/materialized-view.security-label.js +61 -0
- package/dist/core/objects/materialized-view/changes/materialized-view.types.d.ts +2 -1
- package/dist/core/objects/materialized-view/materialized-view.diff.js +18 -0
- package/dist/core/objects/materialized-view/materialized-view.model.d.ts +24 -1
- package/dist/core/objects/materialized-view/materialized-view.model.js +40 -5
- package/dist/core/objects/procedure/changes/procedure.base.d.ts +1 -1
- package/dist/core/objects/procedure/changes/procedure.security-label.d.ts +28 -0
- package/dist/core/objects/procedure/changes/procedure.security-label.js +69 -0
- package/dist/core/objects/procedure/changes/procedure.types.d.ts +2 -1
- package/dist/core/objects/procedure/procedure.diff.js +16 -0
- package/dist/core/objects/procedure/procedure.model.d.ts +12 -1
- package/dist/core/objects/procedure/procedure.model.js +39 -5
- package/dist/core/objects/publication/changes/publication.base.d.ts +1 -1
- package/dist/core/objects/publication/changes/publication.security-label.d.ts +28 -0
- package/dist/core/objects/publication/changes/publication.security-label.js +61 -0
- package/dist/core/objects/publication/changes/publication.types.d.ts +2 -1
- package/dist/core/objects/publication/publication.diff.js +16 -0
- package/dist/core/objects/publication/publication.model.d.ts +14 -0
- package/dist/core/objects/publication/publication.model.js +20 -1
- package/dist/core/objects/rls-policy/rls-policy.diff.js +13 -1
- package/dist/core/objects/role/changes/role.base.d.ts +1 -1
- package/dist/core/objects/role/changes/role.security-label.d.ts +28 -0
- package/dist/core/objects/role/changes/role.security-label.js +61 -0
- package/dist/core/objects/role/changes/role.types.d.ts +2 -1
- package/dist/core/objects/role/role.diff.js +16 -0
- package/dist/core/objects/role/role.model.d.ts +10 -0
- package/dist/core/objects/role/role.model.js +29 -0
- package/dist/core/objects/rule/rule.model.d.ts +2 -1
- package/dist/core/objects/rule/rule.model.js +20 -3
- package/dist/core/objects/schema/changes/schema.base.d.ts +1 -1
- package/dist/core/objects/schema/changes/schema.security-label.d.ts +28 -0
- package/dist/core/objects/schema/changes/schema.security-label.js +61 -0
- package/dist/core/objects/schema/changes/schema.types.d.ts +2 -1
- package/dist/core/objects/schema/schema.diff.js +24 -1
- package/dist/core/objects/schema/schema.model.d.ts +10 -0
- package/dist/core/objects/schema/schema.model.js +18 -1
- package/dist/core/objects/security-label.types.d.ts +20 -0
- package/dist/core/objects/security-label.types.js +46 -0
- package/dist/core/objects/sequence/changes/sequence.base.d.ts +1 -1
- package/dist/core/objects/sequence/changes/sequence.security-label.d.ts +28 -0
- package/dist/core/objects/sequence/changes/sequence.security-label.js +61 -0
- package/dist/core/objects/sequence/changes/sequence.types.d.ts +2 -1
- package/dist/core/objects/sequence/sequence.diff.d.ts +2 -1
- package/dist/core/objects/sequence/sequence.diff.js +44 -4
- package/dist/core/objects/sequence/sequence.model.d.ts +10 -0
- package/dist/core/objects/sequence/sequence.model.js +19 -1
- package/dist/core/objects/subscription/changes/subscription.base.d.ts +1 -1
- package/dist/core/objects/subscription/changes/subscription.security-label.d.ts +28 -0
- package/dist/core/objects/subscription/changes/subscription.security-label.js +61 -0
- package/dist/core/objects/subscription/changes/subscription.types.d.ts +2 -1
- package/dist/core/objects/subscription/subscription.diff.js +16 -0
- package/dist/core/objects/subscription/subscription.model.d.ts +10 -0
- package/dist/core/objects/subscription/subscription.model.js +19 -1
- package/dist/core/objects/table/changes/table.alter.d.ts +12 -1
- package/dist/core/objects/table/changes/table.alter.js +20 -2
- package/dist/core/objects/table/changes/table.base.d.ts +1 -1
- package/dist/core/objects/table/changes/table.security-label.d.ts +63 -0
- package/dist/core/objects/table/changes/table.security-label.js +134 -0
- package/dist/core/objects/table/changes/table.types.d.ts +2 -1
- package/dist/core/objects/table/table.diff.js +68 -15
- package/dist/core/objects/table/table.model.d.ts +36 -1
- package/dist/core/objects/table/table.model.js +74 -7
- package/dist/core/objects/trigger/trigger.model.d.ts +2 -1
- package/dist/core/objects/trigger/trigger.model.js +20 -4
- package/dist/core/objects/type/composite-type/changes/composite-type.base.d.ts +1 -1
- package/dist/core/objects/type/composite-type/changes/composite-type.security-label.d.ts +28 -0
- package/dist/core/objects/type/composite-type/changes/composite-type.security-label.js +61 -0
- package/dist/core/objects/type/composite-type/changes/composite-type.types.d.ts +2 -1
- package/dist/core/objects/type/composite-type/composite-type.diff.js +16 -0
- package/dist/core/objects/type/composite-type/composite-type.model.d.ts +22 -0
- package/dist/core/objects/type/composite-type/composite-type.model.js +22 -2
- package/dist/core/objects/type/enum/changes/enum.base.d.ts +1 -1
- package/dist/core/objects/type/enum/changes/enum.security-label.d.ts +28 -0
- package/dist/core/objects/type/enum/changes/enum.security-label.js +61 -0
- package/dist/core/objects/type/enum/changes/enum.types.d.ts +2 -1
- package/dist/core/objects/type/enum/enum.diff.js +16 -0
- package/dist/core/objects/type/enum/enum.model.d.ts +10 -0
- package/dist/core/objects/type/enum/enum.model.js +20 -1
- package/dist/core/objects/type/range/changes/range.base.d.ts +1 -1
- package/dist/core/objects/type/range/changes/range.security-label.d.ts +28 -0
- package/dist/core/objects/type/range/changes/range.security-label.js +61 -0
- package/dist/core/objects/type/range/changes/range.types.d.ts +2 -1
- package/dist/core/objects/type/range/range.diff.js +16 -0
- package/dist/core/objects/type/range/range.model.d.ts +10 -0
- package/dist/core/objects/type/range/range.model.js +19 -1
- package/dist/core/objects/utils.d.ts +2 -0
- package/dist/core/objects/utils.js +6 -0
- package/dist/core/objects/view/changes/view.base.d.ts +1 -1
- package/dist/core/objects/view/changes/view.security-label.d.ts +28 -0
- package/dist/core/objects/view/changes/view.security-label.js +61 -0
- package/dist/core/objects/view/changes/view.types.d.ts +2 -1
- package/dist/core/objects/view/view.diff.js +13 -0
- package/dist/core/objects/view/view.model.d.ts +28 -1
- package/dist/core/objects/view/view.model.js +40 -5
- package/dist/core/plan/create.js +3 -1
- package/dist/core/plan/sql-format/fixtures.js +1 -0
- package/dist/core/plan/types.d.ts +8 -0
- package/dist/core/{post-diff-cycle-breaking.d.ts → post-diff-normalization.d.ts} +8 -1
- package/dist/core/post-diff-normalization.js +202 -0
- package/dist/core/sort/cycle-breakers.js +1 -1
- package/dist/core/sort/utils.d.ts +10 -0
- package/dist/core/sort/utils.js +28 -0
- package/package.json +1 -1
- package/src/core/catalog.diff.ts +4 -2
- package/src/core/catalog.model.ts +21 -8
- package/src/core/expand-replace-dependencies.test.ts +131 -0
- package/src/core/expand-replace-dependencies.ts +24 -0
- package/src/core/integrations/filter/dsl.test.ts +27 -0
- package/src/core/integrations/filter/flatten.ts +16 -0
- package/src/core/objects/aggregate/aggregate.diff.ts +33 -0
- package/src/core/objects/aggregate/aggregate.model.ts +22 -1
- package/src/core/objects/aggregate/changes/aggregate.base.ts +5 -1
- package/src/core/objects/aggregate/changes/aggregate.security-label.ts +99 -0
- package/src/core/objects/aggregate/changes/aggregate.types.ts +3 -1
- package/src/core/objects/base.model.ts +2 -0
- package/src/core/objects/domain/changes/domain.base.ts +5 -1
- package/src/core/objects/domain/changes/domain.security-label.test.ts +56 -0
- package/src/core/objects/domain/changes/domain.security-label.ts +77 -0
- package/src/core/objects/domain/changes/domain.types.ts +3 -1
- package/src/core/objects/domain/domain.diff.ts +33 -0
- package/src/core/objects/domain/domain.model.ts +22 -1
- package/src/core/objects/event-trigger/changes/event-trigger.base.ts +1 -1
- package/src/core/objects/event-trigger/changes/event-trigger.security-label.ts +95 -0
- package/src/core/objects/event-trigger/changes/event-trigger.types.ts +3 -1
- package/src/core/objects/event-trigger/event-trigger.diff.ts +33 -0
- package/src/core/objects/event-trigger/event-trigger.model.ts +22 -1
- package/src/core/objects/extract-with-retry.test.ts +143 -0
- package/src/core/objects/extract-with-retry.ts +87 -0
- package/src/core/objects/foreign-data-wrapper/foreign-table/changes/foreign-table.base.ts +5 -1
- package/src/core/objects/foreign-data-wrapper/foreign-table/changes/foreign-table.security-label.ts +95 -0
- package/src/core/objects/foreign-data-wrapper/foreign-table/changes/foreign-table.types.ts +3 -1
- package/src/core/objects/foreign-data-wrapper/foreign-table/foreign-table.diff.ts +33 -0
- package/src/core/objects/foreign-data-wrapper/foreign-table/foreign-table.model.ts +24 -1
- package/src/core/objects/index/index.diff.ts +0 -1
- package/src/core/objects/index/index.model.test.ts +37 -1
- package/src/core/objects/index/index.model.ts +25 -6
- package/src/core/objects/materialized-view/changes/materialized-view.base.ts +5 -1
- package/src/core/objects/materialized-view/changes/materialized-view.security-label.test.ts +63 -0
- package/src/core/objects/materialized-view/changes/materialized-view.security-label.ts +95 -0
- package/src/core/objects/materialized-view/changes/materialized-view.types.ts +3 -1
- package/src/core/objects/materialized-view/materialized-view.diff.ts +37 -0
- package/src/core/objects/materialized-view/materialized-view.model.test.ts +93 -0
- package/src/core/objects/materialized-view/materialized-view.model.ts +52 -8
- package/src/core/objects/procedure/changes/procedure.base.ts +5 -1
- package/src/core/objects/procedure/changes/procedure.security-label.ts +105 -0
- package/src/core/objects/procedure/changes/procedure.types.ts +3 -1
- package/src/core/objects/procedure/procedure.diff.ts +33 -0
- package/src/core/objects/procedure/procedure.model.test.ts +117 -0
- package/src/core/objects/procedure/procedure.model.ts +51 -7
- package/src/core/objects/publication/changes/publication.base.ts +1 -1
- package/src/core/objects/publication/changes/publication.security-label.ts +95 -0
- package/src/core/objects/publication/changes/publication.types.ts +3 -1
- package/src/core/objects/publication/publication.diff.ts +33 -0
- package/src/core/objects/publication/publication.model.ts +24 -1
- package/src/core/objects/rls-policy/rls-policy.diff.ts +19 -1
- package/src/core/objects/role/changes/role.base.ts +2 -1
- package/src/core/objects/role/changes/role.security-label.ts +77 -0
- package/src/core/objects/role/changes/role.types.ts +3 -1
- package/src/core/objects/role/role.diff.ts +33 -0
- package/src/core/objects/role/role.model.ts +32 -0
- package/src/core/objects/rule/rule.model.test.ts +99 -0
- package/src/core/objects/rule/rule.model.ts +28 -4
- package/src/core/objects/schema/changes/schema.alter.test.ts +1 -0
- package/src/core/objects/schema/changes/schema.base.ts +5 -1
- package/src/core/objects/schema/changes/schema.create.test.ts +1 -0
- package/src/core/objects/schema/changes/schema.drop.test.ts +1 -0
- package/src/core/objects/schema/changes/schema.security-label.test.ts +76 -0
- package/src/core/objects/schema/changes/schema.security-label.ts +77 -0
- package/src/core/objects/schema/changes/schema.types.ts +3 -1
- package/src/core/objects/schema/schema.diff.test.ts +1 -0
- package/src/core/objects/schema/schema.diff.ts +43 -1
- package/src/core/objects/schema/schema.model.ts +21 -1
- package/src/core/objects/security-label.types.test.ts +106 -0
- package/src/core/objects/security-label.types.ts +61 -0
- package/src/core/objects/sequence/changes/sequence.base.ts +5 -1
- package/src/core/objects/sequence/changes/sequence.security-label.test.ts +58 -0
- package/src/core/objects/sequence/changes/sequence.security-label.ts +92 -0
- package/src/core/objects/sequence/changes/sequence.types.ts +3 -1
- package/src/core/objects/sequence/sequence.diff.test.ts +87 -0
- package/src/core/objects/sequence/sequence.diff.ts +64 -6
- package/src/core/objects/sequence/sequence.model.ts +22 -1
- package/src/core/objects/subscription/changes/subscription.base.ts +1 -1
- package/src/core/objects/subscription/changes/subscription.security-label.ts +95 -0
- package/src/core/objects/subscription/changes/subscription.types.ts +3 -1
- package/src/core/objects/subscription/subscription.diff.ts +33 -0
- package/src/core/objects/subscription/subscription.model.ts +22 -1
- package/src/core/objects/table/changes/table.alter.test.ts +13 -21
- package/src/core/objects/table/changes/table.alter.ts +30 -3
- package/src/core/objects/table/changes/table.base.ts +5 -1
- package/src/core/objects/table/changes/table.security-label.test.ts +140 -0
- package/src/core/objects/table/changes/table.security-label.ts +183 -0
- package/src/core/objects/table/changes/table.types.ts +3 -1
- package/src/core/objects/table/table.diff.ts +111 -19
- package/src/core/objects/table/table.model.test.ts +209 -0
- package/src/core/objects/table/table.model.ts +94 -9
- package/src/core/objects/trigger/trigger.model.test.ts +113 -0
- package/src/core/objects/trigger/trigger.model.ts +28 -5
- package/src/core/objects/type/composite-type/changes/composite-type.base.ts +5 -1
- package/src/core/objects/type/composite-type/changes/composite-type.security-label.ts +95 -0
- package/src/core/objects/type/composite-type/changes/composite-type.types.ts +3 -1
- package/src/core/objects/type/composite-type/composite-type.diff.ts +33 -0
- package/src/core/objects/type/composite-type/composite-type.model.ts +26 -2
- package/src/core/objects/type/enum/changes/enum.base.ts +5 -1
- package/src/core/objects/type/enum/changes/enum.security-label.ts +77 -0
- package/src/core/objects/type/enum/changes/enum.types.ts +3 -1
- package/src/core/objects/type/enum/enum.diff.ts +33 -0
- package/src/core/objects/type/enum/enum.model.ts +25 -1
- package/src/core/objects/type/range/changes/range.base.ts +5 -1
- package/src/core/objects/type/range/changes/range.security-label.ts +77 -0
- package/src/core/objects/type/range/changes/range.types.ts +3 -1
- package/src/core/objects/type/range/range.diff.ts +33 -0
- package/src/core/objects/type/range/range.model.ts +22 -1
- package/src/core/objects/utils.ts +6 -0
- package/src/core/objects/view/changes/view.base.ts +5 -1
- package/src/core/objects/view/changes/view.security-label.test.ts +64 -0
- package/src/core/objects/view/changes/view.security-label.ts +77 -0
- package/src/core/objects/view/changes/view.types.ts +3 -1
- package/src/core/objects/view/view.diff.ts +31 -0
- package/src/core/objects/view/view.model.test.ts +90 -0
- package/src/core/objects/view/view.model.ts +53 -7
- package/src/core/plan/create.ts +3 -1
- package/src/core/plan/sql-format/fixtures.ts +1 -0
- package/src/core/plan/types.ts +8 -0
- package/src/core/{post-diff-cycle-breaking.test.ts → post-diff-normalization.test.ts} +168 -4
- package/src/core/post-diff-normalization.ts +260 -0
- package/src/core/sort/cycle-breakers.ts +1 -1
- package/src/core/sort/utils.ts +38 -0
- package/dist/core/post-diff-cycle-breaking.js +0 -100
- package/src/core/post-diff-cycle-breaking.ts +0 -138
|
@@ -3,7 +3,11 @@ import type { Range } from "../range.model.ts";
|
|
|
3
3
|
|
|
4
4
|
abstract class BaseRangeChange extends BaseChange {
|
|
5
5
|
abstract readonly range: Range;
|
|
6
|
-
abstract readonly scope:
|
|
6
|
+
abstract readonly scope:
|
|
7
|
+
| "object"
|
|
8
|
+
| "comment"
|
|
9
|
+
| "privilege"
|
|
10
|
+
| "security_label";
|
|
7
11
|
readonly objectType: "range" = "range";
|
|
8
12
|
}
|
|
9
13
|
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { quoteLiteral } from "../../../base.change.ts";
|
|
2
|
+
import type { SecurityLabelProps } from "../../../security-label.types.ts";
|
|
3
|
+
import { stableId } from "../../../utils.ts";
|
|
4
|
+
import type { Range } from "../range.model.ts";
|
|
5
|
+
import { CreateRangeChange, DropRangeChange } from "./range.base.ts";
|
|
6
|
+
|
|
7
|
+
export type SecurityLabelRange =
|
|
8
|
+
| CreateSecurityLabelOnRange
|
|
9
|
+
| DropSecurityLabelOnRange;
|
|
10
|
+
|
|
11
|
+
export class CreateSecurityLabelOnRange extends CreateRangeChange {
|
|
12
|
+
public readonly range: Range;
|
|
13
|
+
public readonly securityLabel: SecurityLabelProps;
|
|
14
|
+
public readonly scope = "security_label" as const;
|
|
15
|
+
|
|
16
|
+
constructor(props: { range: Range; securityLabel: SecurityLabelProps }) {
|
|
17
|
+
super();
|
|
18
|
+
this.range = props.range;
|
|
19
|
+
this.securityLabel = props.securityLabel;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
get creates() {
|
|
23
|
+
return [
|
|
24
|
+
stableId.securityLabel(this.range.stableId, this.securityLabel.provider),
|
|
25
|
+
];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
get requires() {
|
|
29
|
+
return [this.range.stableId];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
serialize(): string {
|
|
33
|
+
return [
|
|
34
|
+
"SECURITY LABEL FOR",
|
|
35
|
+
this.securityLabel.provider,
|
|
36
|
+
"ON TYPE",
|
|
37
|
+
`${this.range.schema}.${this.range.name}`,
|
|
38
|
+
"IS",
|
|
39
|
+
quoteLiteral(this.securityLabel.label),
|
|
40
|
+
].join(" ");
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export class DropSecurityLabelOnRange extends DropRangeChange {
|
|
45
|
+
public readonly range: Range;
|
|
46
|
+
public readonly securityLabel: SecurityLabelProps;
|
|
47
|
+
public readonly scope = "security_label" as const;
|
|
48
|
+
|
|
49
|
+
constructor(props: { range: Range; securityLabel: SecurityLabelProps }) {
|
|
50
|
+
super();
|
|
51
|
+
this.range = props.range;
|
|
52
|
+
this.securityLabel = props.securityLabel;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
get drops() {
|
|
56
|
+
return [
|
|
57
|
+
stableId.securityLabel(this.range.stableId, this.securityLabel.provider),
|
|
58
|
+
];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
get requires() {
|
|
62
|
+
return [
|
|
63
|
+
stableId.securityLabel(this.range.stableId, this.securityLabel.provider),
|
|
64
|
+
this.range.stableId,
|
|
65
|
+
];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
serialize(): string {
|
|
69
|
+
return [
|
|
70
|
+
"SECURITY LABEL FOR",
|
|
71
|
+
this.securityLabel.provider,
|
|
72
|
+
"ON TYPE",
|
|
73
|
+
`${this.range.schema}.${this.range.name}`,
|
|
74
|
+
"IS NULL",
|
|
75
|
+
].join(" ");
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -3,6 +3,7 @@ import type { CommentRange } from "./range.comment.ts";
|
|
|
3
3
|
import type { CreateRange } from "./range.create.ts";
|
|
4
4
|
import type { DropRange } from "./range.drop.ts";
|
|
5
5
|
import type { RangePrivilege } from "./range.privilege.ts";
|
|
6
|
+
import type { SecurityLabelRange } from "./range.security-label.ts";
|
|
6
7
|
|
|
7
8
|
/** Union of all range-related change variants (`objectType: "range"`). @category Change Types */
|
|
8
9
|
export type RangeChange =
|
|
@@ -10,4 +11,5 @@ export type RangeChange =
|
|
|
10
11
|
| CommentRange
|
|
11
12
|
| CreateRange
|
|
12
13
|
| DropRange
|
|
13
|
-
| RangePrivilege
|
|
14
|
+
| RangePrivilege
|
|
15
|
+
| SecurityLabelRange;
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
filterPublicBuiltInDefaults,
|
|
6
6
|
} from "../../base.privilege-diff.ts";
|
|
7
7
|
import type { ObjectDiffContext } from "../../diff-context.ts";
|
|
8
|
+
import { diffSecurityLabels } from "../../security-label.types.ts";
|
|
8
9
|
import { hasNonAlterableChanges } from "../../utils.ts";
|
|
9
10
|
import { AlterRangeChangeOwner } from "./changes/range.alter.ts";
|
|
10
11
|
import {
|
|
@@ -18,6 +19,10 @@ import {
|
|
|
18
19
|
RevokeGrantOptionRangePrivileges,
|
|
19
20
|
RevokeRangePrivileges,
|
|
20
21
|
} from "./changes/range.privilege.ts";
|
|
22
|
+
import {
|
|
23
|
+
CreateSecurityLabelOnRange,
|
|
24
|
+
DropSecurityLabelOnRange,
|
|
25
|
+
} from "./changes/range.security-label.ts";
|
|
21
26
|
import type { RangeChange } from "./changes/range.types.ts";
|
|
22
27
|
import type { Range } from "./range.model.ts";
|
|
23
28
|
|
|
@@ -59,6 +64,14 @@ export function diffRanges(
|
|
|
59
64
|
if (createdRange.comment !== null) {
|
|
60
65
|
changes.push(new CreateCommentOnRange({ range: createdRange }));
|
|
61
66
|
}
|
|
67
|
+
for (const label of createdRange.security_labels) {
|
|
68
|
+
changes.push(
|
|
69
|
+
new CreateSecurityLabelOnRange({
|
|
70
|
+
range: createdRange,
|
|
71
|
+
securityLabel: label,
|
|
72
|
+
}),
|
|
73
|
+
);
|
|
74
|
+
}
|
|
62
75
|
|
|
63
76
|
// PRIVILEGES: For created objects, compare against default privileges state
|
|
64
77
|
// The migration script will run ALTER DEFAULT PRIVILEGES before CREATE (via constraint spec),
|
|
@@ -156,6 +169,26 @@ export function diffRanges(
|
|
|
156
169
|
}
|
|
157
170
|
}
|
|
158
171
|
|
|
172
|
+
// SECURITY LABELS
|
|
173
|
+
changes.push(
|
|
174
|
+
...diffSecurityLabels<
|
|
175
|
+
CreateSecurityLabelOnRange | DropSecurityLabelOnRange
|
|
176
|
+
>(
|
|
177
|
+
mainRange.security_labels,
|
|
178
|
+
branchRange.security_labels,
|
|
179
|
+
(securityLabel) =>
|
|
180
|
+
new CreateSecurityLabelOnRange({
|
|
181
|
+
range: branchRange,
|
|
182
|
+
securityLabel,
|
|
183
|
+
}),
|
|
184
|
+
(securityLabel) =>
|
|
185
|
+
new DropSecurityLabelOnRange({
|
|
186
|
+
range: mainRange,
|
|
187
|
+
securityLabel,
|
|
188
|
+
}),
|
|
189
|
+
),
|
|
190
|
+
);
|
|
191
|
+
|
|
159
192
|
// PRIVILEGES
|
|
160
193
|
// Filter out PUBLIC's built-in default USAGE privilege from main catalog
|
|
161
194
|
// (PostgreSQL grants it automatically, so we shouldn't compare it)
|
|
@@ -6,6 +6,10 @@ import {
|
|
|
6
6
|
type PrivilegeProps,
|
|
7
7
|
privilegePropsSchema,
|
|
8
8
|
} from "../../base.privilege-diff.ts";
|
|
9
|
+
import {
|
|
10
|
+
type SecurityLabelProps,
|
|
11
|
+
securityLabelPropsSchema,
|
|
12
|
+
} from "../../security-label.types.ts";
|
|
9
13
|
|
|
10
14
|
const rangePropsSchema = z.object({
|
|
11
15
|
schema: z.string(),
|
|
@@ -30,6 +34,7 @@ const rangePropsSchema = z.object({
|
|
|
30
34
|
subtype_opclass_schema: z.string().nullable(),
|
|
31
35
|
subtype_opclass_name: z.string().nullable(),
|
|
32
36
|
privileges: z.array(privilegePropsSchema),
|
|
37
|
+
security_labels: z.array(securityLabelPropsSchema).default([]).optional(),
|
|
33
38
|
});
|
|
34
39
|
|
|
35
40
|
type RangePrivilegeProps = PrivilegeProps;
|
|
@@ -54,6 +59,7 @@ export class Range extends BasePgModel {
|
|
|
54
59
|
public readonly subtype_opclass_schema: RangeProps["subtype_opclass_schema"];
|
|
55
60
|
public readonly subtype_opclass_name: RangeProps["subtype_opclass_name"];
|
|
56
61
|
public readonly privileges: RangePrivilegeProps[];
|
|
62
|
+
public readonly security_labels: SecurityLabelProps[];
|
|
57
63
|
|
|
58
64
|
constructor(props: RangeProps) {
|
|
59
65
|
super();
|
|
@@ -75,6 +81,7 @@ export class Range extends BasePgModel {
|
|
|
75
81
|
this.subtype_opclass_schema = props.subtype_opclass_schema;
|
|
76
82
|
this.subtype_opclass_name = props.subtype_opclass_name;
|
|
77
83
|
this.privileges = props.privileges;
|
|
84
|
+
this.security_labels = props.security_labels ?? [];
|
|
78
85
|
}
|
|
79
86
|
|
|
80
87
|
get stableId(): `type:${string}` {
|
|
@@ -102,6 +109,7 @@ export class Range extends BasePgModel {
|
|
|
102
109
|
subtype_opclass_name: this.subtype_opclass_name,
|
|
103
110
|
comment: this.comment,
|
|
104
111
|
privileges: this.privileges,
|
|
112
|
+
security_labels: this.security_labels,
|
|
105
113
|
};
|
|
106
114
|
}
|
|
107
115
|
}
|
|
@@ -166,7 +174,20 @@ select
|
|
|
166
174
|
)
|
|
167
175
|
from lateral aclexplode(COALESCE(t.typacl, acldefault('T', t.typowner))) as x(grantor, grantee, privilege_type, is_grantable)
|
|
168
176
|
), '[]'
|
|
169
|
-
) as privileges
|
|
177
|
+
) as privileges,
|
|
178
|
+
coalesce(
|
|
179
|
+
(
|
|
180
|
+
select json_agg(
|
|
181
|
+
json_build_object('provider', sl.provider, 'label', sl.label)
|
|
182
|
+
order by sl.provider
|
|
183
|
+
)
|
|
184
|
+
from pg_catalog.pg_seclabel sl
|
|
185
|
+
where sl.objoid = t.oid
|
|
186
|
+
and sl.classoid = 'pg_type'::regclass
|
|
187
|
+
and sl.objsubid = 0
|
|
188
|
+
),
|
|
189
|
+
'[]'::json
|
|
190
|
+
) as security_labels
|
|
170
191
|
from pg_catalog.pg_range r
|
|
171
192
|
join pg_catalog.pg_type t on t.oid = r.rngtypid
|
|
172
193
|
join pg_catalog.pg_type subt on subt.oid = r.rngsubtype
|
|
@@ -71,9 +71,15 @@ export const stableId = {
|
|
|
71
71
|
constraint(schema: string, table: string, constraint: string) {
|
|
72
72
|
return `constraint:${schema}.${table}.${constraint}` as const;
|
|
73
73
|
},
|
|
74
|
+
index(schema: string, table: string, indexName: string) {
|
|
75
|
+
return `index:${schema}.${table}.${indexName}` as const;
|
|
76
|
+
},
|
|
74
77
|
comment(objectStableId: string) {
|
|
75
78
|
return `comment:${objectStableId}` as const;
|
|
76
79
|
},
|
|
80
|
+
securityLabel(objectStableId: string, provider: string) {
|
|
81
|
+
return `securityLabel:${objectStableId}::provider:${provider}` as const;
|
|
82
|
+
},
|
|
77
83
|
role(role: string) {
|
|
78
84
|
return `role:${role}` as const;
|
|
79
85
|
},
|
|
@@ -3,7 +3,11 @@ import type { View } from "../view.model.ts";
|
|
|
3
3
|
|
|
4
4
|
abstract class BaseViewChange extends BaseChange {
|
|
5
5
|
abstract readonly view: View;
|
|
6
|
-
abstract readonly scope:
|
|
6
|
+
abstract readonly scope:
|
|
7
|
+
| "object"
|
|
8
|
+
| "comment"
|
|
9
|
+
| "privilege"
|
|
10
|
+
| "security_label";
|
|
7
11
|
readonly objectType: "view" = "view";
|
|
8
12
|
}
|
|
9
13
|
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { assertValidSql } from "../../../test-utils/assert-valid-sql.ts";
|
|
3
|
+
import { stableId } from "../../utils.ts";
|
|
4
|
+
import { View, type ViewProps } from "../view.model.ts";
|
|
5
|
+
import {
|
|
6
|
+
CreateSecurityLabelOnView,
|
|
7
|
+
DropSecurityLabelOnView,
|
|
8
|
+
} from "./view.security-label.ts";
|
|
9
|
+
|
|
10
|
+
const makeView = (): View =>
|
|
11
|
+
new View({
|
|
12
|
+
schema: "public",
|
|
13
|
+
name: "v",
|
|
14
|
+
definition: "SELECT 1",
|
|
15
|
+
row_security: false,
|
|
16
|
+
force_row_security: false,
|
|
17
|
+
has_indexes: false,
|
|
18
|
+
has_rules: false,
|
|
19
|
+
has_triggers: false,
|
|
20
|
+
has_subclasses: false,
|
|
21
|
+
is_populated: true,
|
|
22
|
+
replica_identity: "d",
|
|
23
|
+
is_partition: false,
|
|
24
|
+
options: null,
|
|
25
|
+
partition_bound: null,
|
|
26
|
+
owner: "postgres",
|
|
27
|
+
comment: null,
|
|
28
|
+
columns: [],
|
|
29
|
+
privileges: [],
|
|
30
|
+
} as ViewProps);
|
|
31
|
+
|
|
32
|
+
describe("view.security-label", () => {
|
|
33
|
+
test("create serializes and tracks dependencies", async () => {
|
|
34
|
+
const view = makeView();
|
|
35
|
+
const change = new CreateSecurityLabelOnView({
|
|
36
|
+
view,
|
|
37
|
+
securityLabel: { provider: "dummy", label: "classified" },
|
|
38
|
+
});
|
|
39
|
+
expect(change.scope).toBe("security_label");
|
|
40
|
+
expect(change.creates).toEqual([
|
|
41
|
+
stableId.securityLabel(view.stableId, "dummy"),
|
|
42
|
+
]);
|
|
43
|
+
expect(change.requires).toEqual([view.stableId]);
|
|
44
|
+
await assertValidSql(change.serialize());
|
|
45
|
+
expect(change.serialize()).toBe(
|
|
46
|
+
"SECURITY LABEL FOR dummy ON VIEW public.v IS 'classified'",
|
|
47
|
+
);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test("drop serializes to IS NULL", async () => {
|
|
51
|
+
const view = makeView();
|
|
52
|
+
const change = new DropSecurityLabelOnView({
|
|
53
|
+
view,
|
|
54
|
+
securityLabel: { provider: "dummy", label: "classified" },
|
|
55
|
+
});
|
|
56
|
+
expect(change.drops).toEqual([
|
|
57
|
+
stableId.securityLabel(view.stableId, "dummy"),
|
|
58
|
+
]);
|
|
59
|
+
await assertValidSql(change.serialize());
|
|
60
|
+
expect(change.serialize()).toBe(
|
|
61
|
+
"SECURITY LABEL FOR dummy ON VIEW public.v IS NULL",
|
|
62
|
+
);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { quoteLiteral } from "../../base.change.ts";
|
|
2
|
+
import type { SecurityLabelProps } from "../../security-label.types.ts";
|
|
3
|
+
import { stableId } from "../../utils.ts";
|
|
4
|
+
import type { View } from "../view.model.ts";
|
|
5
|
+
import { CreateViewChange, DropViewChange } from "./view.base.ts";
|
|
6
|
+
|
|
7
|
+
export type SecurityLabelView =
|
|
8
|
+
| CreateSecurityLabelOnView
|
|
9
|
+
| DropSecurityLabelOnView;
|
|
10
|
+
|
|
11
|
+
export class CreateSecurityLabelOnView extends CreateViewChange {
|
|
12
|
+
public readonly view: View;
|
|
13
|
+
public readonly securityLabel: SecurityLabelProps;
|
|
14
|
+
public readonly scope = "security_label" as const;
|
|
15
|
+
|
|
16
|
+
constructor(props: { view: View; securityLabel: SecurityLabelProps }) {
|
|
17
|
+
super();
|
|
18
|
+
this.view = props.view;
|
|
19
|
+
this.securityLabel = props.securityLabel;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
get creates() {
|
|
23
|
+
return [
|
|
24
|
+
stableId.securityLabel(this.view.stableId, this.securityLabel.provider),
|
|
25
|
+
];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
get requires() {
|
|
29
|
+
return [this.view.stableId];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
serialize(): string {
|
|
33
|
+
return [
|
|
34
|
+
"SECURITY LABEL FOR",
|
|
35
|
+
this.securityLabel.provider,
|
|
36
|
+
"ON VIEW",
|
|
37
|
+
`${this.view.schema}.${this.view.name}`,
|
|
38
|
+
"IS",
|
|
39
|
+
quoteLiteral(this.securityLabel.label),
|
|
40
|
+
].join(" ");
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export class DropSecurityLabelOnView extends DropViewChange {
|
|
45
|
+
public readonly view: View;
|
|
46
|
+
public readonly securityLabel: SecurityLabelProps;
|
|
47
|
+
public readonly scope = "security_label" as const;
|
|
48
|
+
|
|
49
|
+
constructor(props: { view: View; securityLabel: SecurityLabelProps }) {
|
|
50
|
+
super();
|
|
51
|
+
this.view = props.view;
|
|
52
|
+
this.securityLabel = props.securityLabel;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
get drops() {
|
|
56
|
+
return [
|
|
57
|
+
stableId.securityLabel(this.view.stableId, this.securityLabel.provider),
|
|
58
|
+
];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
get requires() {
|
|
62
|
+
return [
|
|
63
|
+
stableId.securityLabel(this.view.stableId, this.securityLabel.provider),
|
|
64
|
+
this.view.stableId,
|
|
65
|
+
];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
serialize(): string {
|
|
69
|
+
return [
|
|
70
|
+
"SECURITY LABEL FOR",
|
|
71
|
+
this.securityLabel.provider,
|
|
72
|
+
"ON VIEW",
|
|
73
|
+
`${this.view.schema}.${this.view.name}`,
|
|
74
|
+
"IS NULL",
|
|
75
|
+
].join(" ");
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -3,6 +3,7 @@ import type { CommentView } from "./view.comment.ts";
|
|
|
3
3
|
import type { CreateView } from "./view.create.ts";
|
|
4
4
|
import type { DropView } from "./view.drop.ts";
|
|
5
5
|
import type { ViewPrivilege } from "./view.privilege.ts";
|
|
6
|
+
import type { SecurityLabelView } from "./view.security-label.ts";
|
|
6
7
|
|
|
7
8
|
/** Union of all view-related change variants (`objectType: "view"`). @category Change Types */
|
|
8
9
|
export type ViewChange =
|
|
@@ -10,4 +11,5 @@ export type ViewChange =
|
|
|
10
11
|
| CommentView
|
|
11
12
|
| CreateView
|
|
12
13
|
| DropView
|
|
13
|
-
| ViewPrivilege
|
|
14
|
+
| ViewPrivilege
|
|
15
|
+
| SecurityLabelView;
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
emitColumnPrivilegeChanges,
|
|
6
6
|
} from "../base.privilege-diff.ts";
|
|
7
7
|
import type { ObjectDiffContext } from "../diff-context.ts";
|
|
8
|
+
import { diffSecurityLabels } from "../security-label.types.ts";
|
|
8
9
|
import { deepEqual, hasNonAlterableChanges } from "../utils.ts";
|
|
9
10
|
import {
|
|
10
11
|
AlterViewChangeOwner,
|
|
@@ -22,6 +23,10 @@ import {
|
|
|
22
23
|
RevokeGrantOptionViewPrivileges,
|
|
23
24
|
RevokeViewPrivileges,
|
|
24
25
|
} from "./changes/view.privilege.ts";
|
|
26
|
+
import {
|
|
27
|
+
CreateSecurityLabelOnView,
|
|
28
|
+
DropSecurityLabelOnView,
|
|
29
|
+
} from "./changes/view.security-label.ts";
|
|
25
30
|
import type { ViewChange } from "./changes/view.types.ts";
|
|
26
31
|
import type { View } from "./view.model.ts";
|
|
27
32
|
|
|
@@ -57,6 +62,12 @@ export function diffViews(
|
|
|
57
62
|
changes.push(new CreateCommentOnView({ view }));
|
|
58
63
|
}
|
|
59
64
|
|
|
65
|
+
for (const label of view.security_labels) {
|
|
66
|
+
changes.push(
|
|
67
|
+
new CreateSecurityLabelOnView({ view, securityLabel: label }),
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
60
71
|
// PRIVILEGES: For created objects, compare against default privileges state
|
|
61
72
|
// The migration script will run ALTER DEFAULT PRIVILEGES before CREATE (via constraint spec),
|
|
62
73
|
// so objects are created with the default privileges state in effect.
|
|
@@ -195,6 +206,26 @@ export function diffViews(
|
|
|
195
206
|
}
|
|
196
207
|
}
|
|
197
208
|
|
|
209
|
+
// SECURITY LABELS
|
|
210
|
+
changes.push(
|
|
211
|
+
...diffSecurityLabels<
|
|
212
|
+
CreateSecurityLabelOnView | DropSecurityLabelOnView
|
|
213
|
+
>(
|
|
214
|
+
mainView.security_labels,
|
|
215
|
+
branchView.security_labels,
|
|
216
|
+
(securityLabel) =>
|
|
217
|
+
new CreateSecurityLabelOnView({
|
|
218
|
+
view: branchView,
|
|
219
|
+
securityLabel,
|
|
220
|
+
}),
|
|
221
|
+
(securityLabel) =>
|
|
222
|
+
new DropSecurityLabelOnView({
|
|
223
|
+
view: mainView,
|
|
224
|
+
securityLabel,
|
|
225
|
+
}),
|
|
226
|
+
),
|
|
227
|
+
);
|
|
228
|
+
|
|
198
229
|
// Note: View renaming would also use ALTER VIEW ... RENAME TO ...
|
|
199
230
|
// But since our View model uses 'name' as the identity field,
|
|
200
231
|
// a name change would be handled as drop + create by diffObjects()
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import type { Pool } from "pg";
|
|
3
|
+
import { extractViews, View } from "./view.model.ts";
|
|
4
|
+
|
|
5
|
+
const baseRow = {
|
|
6
|
+
schema: "public",
|
|
7
|
+
row_security: false,
|
|
8
|
+
force_row_security: false,
|
|
9
|
+
has_indexes: false,
|
|
10
|
+
has_rules: false,
|
|
11
|
+
has_triggers: false,
|
|
12
|
+
has_subclasses: false,
|
|
13
|
+
is_populated: true,
|
|
14
|
+
replica_identity: "d" as const,
|
|
15
|
+
is_partition: false,
|
|
16
|
+
options: null,
|
|
17
|
+
partition_bound: null,
|
|
18
|
+
owner: "postgres",
|
|
19
|
+
comment: null,
|
|
20
|
+
columns: [],
|
|
21
|
+
privileges: [],
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const mockPool = (rows: unknown[]): Pool =>
|
|
25
|
+
({ query: async () => ({ rows }) }) as unknown as Pool;
|
|
26
|
+
|
|
27
|
+
const mockPoolSequence = (...attempts: unknown[][]): Pool => {
|
|
28
|
+
let i = 0;
|
|
29
|
+
return {
|
|
30
|
+
query: async () => ({
|
|
31
|
+
rows: attempts[Math.min(i++, attempts.length - 1)],
|
|
32
|
+
}),
|
|
33
|
+
} as unknown as Pool;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const NO_BACKOFF = { backoffMs: 0 } as const;
|
|
37
|
+
|
|
38
|
+
describe("extractViews", () => {
|
|
39
|
+
test("skips rows where pg_get_viewdef returned NULL after exhausting retries", async () => {
|
|
40
|
+
const views = await extractViews(
|
|
41
|
+
mockPool([
|
|
42
|
+
{
|
|
43
|
+
...baseRow,
|
|
44
|
+
name: '"good_view"',
|
|
45
|
+
definition: "SELECT 1",
|
|
46
|
+
},
|
|
47
|
+
{ ...baseRow, name: '"orphan_view"', definition: null },
|
|
48
|
+
]),
|
|
49
|
+
NO_BACKOFF,
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
expect(views).toHaveLength(1);
|
|
53
|
+
expect(views[0]).toBeInstanceOf(View);
|
|
54
|
+
expect(views[0]?.name).toBe('"good_view"');
|
|
55
|
+
expect(views[0]?.definition).toBe("SELECT 1");
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("does not throw ZodError when the only row has a null definition", async () => {
|
|
59
|
+
await expect(
|
|
60
|
+
extractViews(
|
|
61
|
+
mockPool([{ ...baseRow, name: '"orphan"', definition: null }]),
|
|
62
|
+
NO_BACKOFF,
|
|
63
|
+
),
|
|
64
|
+
).resolves.toEqual([]);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test("returns all views when every row has a valid definition", async () => {
|
|
68
|
+
const views = await extractViews(
|
|
69
|
+
mockPool([
|
|
70
|
+
{ ...baseRow, name: '"a"', definition: "SELECT 1" },
|
|
71
|
+
{ ...baseRow, name: '"b"', definition: "SELECT 2" },
|
|
72
|
+
]),
|
|
73
|
+
NO_BACKOFF,
|
|
74
|
+
);
|
|
75
|
+
expect(views.map((v) => v.name)).toEqual(['"a"', '"b"']);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test("recovers when pg_get_viewdef is NULL on first attempt but resolved on retry", async () => {
|
|
79
|
+
const views = await extractViews(
|
|
80
|
+
mockPoolSequence(
|
|
81
|
+
[{ ...baseRow, name: '"racy_view"', definition: null }],
|
|
82
|
+
[{ ...baseRow, name: '"racy_view"', definition: "SELECT 42" }],
|
|
83
|
+
),
|
|
84
|
+
{ retries: 2, backoffMs: 0 },
|
|
85
|
+
);
|
|
86
|
+
expect(views).toHaveLength(1);
|
|
87
|
+
expect(views[0]?.name).toBe('"racy_view"');
|
|
88
|
+
expect(views[0]?.definition).toBe("SELECT 42");
|
|
89
|
+
});
|
|
90
|
+
});
|
|
@@ -11,6 +11,15 @@ import {
|
|
|
11
11
|
type PrivilegeProps,
|
|
12
12
|
privilegePropsSchema,
|
|
13
13
|
} from "../base.privilege-diff.ts";
|
|
14
|
+
import {
|
|
15
|
+
type ExtractRetryOptions,
|
|
16
|
+
extractWithDefinitionRetry,
|
|
17
|
+
} from "../extract-with-retry.ts";
|
|
18
|
+
import {
|
|
19
|
+
normalizeSecurityLabels,
|
|
20
|
+
type SecurityLabelProps,
|
|
21
|
+
securityLabelPropsSchema,
|
|
22
|
+
} from "../security-label.types.ts";
|
|
14
23
|
import { ReplicaIdentitySchema } from "../table/table.model.ts";
|
|
15
24
|
|
|
16
25
|
const viewPropsSchema = z.object({
|
|
@@ -32,6 +41,16 @@ const viewPropsSchema = z.object({
|
|
|
32
41
|
comment: z.string().nullable(),
|
|
33
42
|
columns: z.array(columnPropsSchema),
|
|
34
43
|
privileges: z.array(privilegePropsSchema),
|
|
44
|
+
security_labels: z.array(securityLabelPropsSchema).default([]).optional(),
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// pg_get_viewdef(oid) can return NULL when the underlying view (or its
|
|
48
|
+
// pg_rewrite row) is dropped between catalog scan and resolution, or under
|
|
49
|
+
// transient catalog state during recovery. An unreadable view cannot be
|
|
50
|
+
// diffed, so we accept NULL here and filter the row out at extraction time
|
|
51
|
+
// rather than crashing the whole catalog parse with a ZodError.
|
|
52
|
+
const viewRowSchema = viewPropsSchema.extend({
|
|
53
|
+
definition: z.string().nullable(),
|
|
35
54
|
});
|
|
36
55
|
|
|
37
56
|
type ViewPrivilegeProps = PrivilegeProps;
|
|
@@ -56,6 +75,7 @@ export class View extends BasePgModel implements TableLikeObject {
|
|
|
56
75
|
public readonly comment: ViewProps["comment"];
|
|
57
76
|
public readonly columns: ViewProps["columns"];
|
|
58
77
|
public readonly privileges: ViewPrivilegeProps[];
|
|
78
|
+
public readonly security_labels: SecurityLabelProps[];
|
|
59
79
|
|
|
60
80
|
constructor(props: ViewProps) {
|
|
61
81
|
super();
|
|
@@ -81,6 +101,7 @@ export class View extends BasePgModel implements TableLikeObject {
|
|
|
81
101
|
this.comment = props.comment;
|
|
82
102
|
this.columns = props.columns;
|
|
83
103
|
this.privileges = props.privileges;
|
|
104
|
+
this.security_labels = props.security_labels ?? [];
|
|
84
105
|
}
|
|
85
106
|
|
|
86
107
|
get stableId(): `view:${string}` {
|
|
@@ -112,6 +133,7 @@ export class View extends BasePgModel implements TableLikeObject {
|
|
|
112
133
|
comment: this.comment,
|
|
113
134
|
columns: this.columns,
|
|
114
135
|
privileges: this.privileges,
|
|
136
|
+
security_labels: this.security_labels,
|
|
115
137
|
};
|
|
116
138
|
}
|
|
117
139
|
|
|
@@ -121,13 +143,22 @@ export class View extends BasePgModel implements TableLikeObject {
|
|
|
121
143
|
data: {
|
|
122
144
|
...this.dataFields,
|
|
123
145
|
columns: normalizeColumns(this.columns),
|
|
146
|
+
security_labels: normalizeSecurityLabels(this.security_labels),
|
|
124
147
|
},
|
|
125
148
|
};
|
|
126
149
|
}
|
|
127
150
|
}
|
|
128
151
|
|
|
129
|
-
export async function extractViews(
|
|
130
|
-
|
|
152
|
+
export async function extractViews(
|
|
153
|
+
pool: Pool,
|
|
154
|
+
options?: ExtractRetryOptions,
|
|
155
|
+
): Promise<View[]> {
|
|
156
|
+
const viewRows = await extractWithDefinitionRetry({
|
|
157
|
+
label: "views",
|
|
158
|
+
options,
|
|
159
|
+
hasNullDefinition: (row) => row.definition === null,
|
|
160
|
+
query: async () => {
|
|
161
|
+
const result = await pool.query<ViewProps>(sql`
|
|
131
162
|
with extension_oids as (
|
|
132
163
|
select
|
|
133
164
|
objid
|
|
@@ -243,7 +274,20 @@ select
|
|
|
243
274
|
join lateral aclexplode(src.acl) as x(grantor, grantee, privilege_type, is_grantable) on true
|
|
244
275
|
group by x.grantee, x.privilege_type
|
|
245
276
|
) as grp
|
|
246
|
-
), '[]') as privileges
|
|
277
|
+
), '[]') as privileges,
|
|
278
|
+
coalesce(
|
|
279
|
+
(
|
|
280
|
+
select json_agg(
|
|
281
|
+
json_build_object('provider', sl.provider, 'label', sl.label)
|
|
282
|
+
order by sl.provider
|
|
283
|
+
)
|
|
284
|
+
from pg_catalog.pg_seclabel sl
|
|
285
|
+
where sl.objoid = v.oid
|
|
286
|
+
and sl.classoid = 'pg_class'::regclass
|
|
287
|
+
and sl.objsubid = 0
|
|
288
|
+
),
|
|
289
|
+
'[]'::json
|
|
290
|
+
) as security_labels
|
|
247
291
|
from
|
|
248
292
|
views v
|
|
249
293
|
left join pg_attribute a on a.attrelid = v.oid and a.attnum > 0 and not a.attisdropped
|
|
@@ -254,9 +298,11 @@ group by
|
|
|
254
298
|
order by
|
|
255
299
|
v.schema, v.name
|
|
256
300
|
`);
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
301
|
+
return result.rows.map((row: unknown) => viewRowSchema.parse(row));
|
|
302
|
+
},
|
|
303
|
+
});
|
|
304
|
+
const validatedRows = viewRows.filter(
|
|
305
|
+
(row): row is ViewProps => row.definition !== null,
|
|
260
306
|
);
|
|
261
|
-
return validatedRows.map((row
|
|
307
|
+
return validatedRows.map((row) => new View(row));
|
|
262
308
|
}
|