@supabase/pg-delta 1.0.0-alpha.22 → 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.
Files changed (220) hide show
  1. package/dist/core/catalog.model.js +1 -0
  2. package/dist/core/integrations/filter/flatten.js +13 -0
  3. package/dist/core/objects/aggregate/aggregate.diff.js +16 -0
  4. package/dist/core/objects/aggregate/aggregate.model.d.ts +10 -0
  5. package/dist/core/objects/aggregate/aggregate.model.js +19 -1
  6. package/dist/core/objects/aggregate/changes/aggregate.base.d.ts +1 -1
  7. package/dist/core/objects/aggregate/changes/aggregate.security-label.d.ts +28 -0
  8. package/dist/core/objects/aggregate/changes/aggregate.security-label.js +64 -0
  9. package/dist/core/objects/aggregate/changes/aggregate.types.d.ts +2 -1
  10. package/dist/core/objects/base.model.d.ts +8 -0
  11. package/dist/core/objects/base.model.js +2 -0
  12. package/dist/core/objects/domain/changes/domain.base.d.ts +1 -1
  13. package/dist/core/objects/domain/changes/domain.security-label.d.ts +28 -0
  14. package/dist/core/objects/domain/changes/domain.security-label.js +61 -0
  15. package/dist/core/objects/domain/changes/domain.types.d.ts +2 -1
  16. package/dist/core/objects/domain/domain.diff.js +16 -0
  17. package/dist/core/objects/domain/domain.model.d.ts +10 -0
  18. package/dist/core/objects/domain/domain.model.js +19 -1
  19. package/dist/core/objects/event-trigger/changes/event-trigger.base.d.ts +1 -1
  20. package/dist/core/objects/event-trigger/changes/event-trigger.security-label.d.ts +28 -0
  21. package/dist/core/objects/event-trigger/changes/event-trigger.security-label.js +61 -0
  22. package/dist/core/objects/event-trigger/changes/event-trigger.types.d.ts +2 -1
  23. package/dist/core/objects/event-trigger/event-trigger.diff.js +16 -0
  24. package/dist/core/objects/event-trigger/event-trigger.model.d.ts +10 -0
  25. package/dist/core/objects/event-trigger/event-trigger.model.js +19 -1
  26. package/dist/core/objects/foreign-data-wrapper/foreign-table/changes/foreign-table.base.d.ts +1 -1
  27. package/dist/core/objects/foreign-data-wrapper/foreign-table/changes/foreign-table.security-label.d.ts +28 -0
  28. package/dist/core/objects/foreign-data-wrapper/foreign-table/changes/foreign-table.security-label.js +61 -0
  29. package/dist/core/objects/foreign-data-wrapper/foreign-table/changes/foreign-table.types.d.ts +2 -1
  30. package/dist/core/objects/foreign-data-wrapper/foreign-table/foreign-table.diff.js +16 -0
  31. package/dist/core/objects/foreign-data-wrapper/foreign-table/foreign-table.model.d.ts +22 -0
  32. package/dist/core/objects/foreign-data-wrapper/foreign-table/foreign-table.model.js +20 -1
  33. package/dist/core/objects/materialized-view/changes/materialized-view.base.d.ts +1 -1
  34. package/dist/core/objects/materialized-view/changes/materialized-view.security-label.d.ts +28 -0
  35. package/dist/core/objects/materialized-view/changes/materialized-view.security-label.js +61 -0
  36. package/dist/core/objects/materialized-view/changes/materialized-view.types.d.ts +2 -1
  37. package/dist/core/objects/materialized-view/materialized-view.diff.js +18 -0
  38. package/dist/core/objects/materialized-view/materialized-view.model.d.ts +22 -0
  39. package/dist/core/objects/materialized-view/materialized-view.model.js +20 -1
  40. package/dist/core/objects/procedure/changes/procedure.base.d.ts +1 -1
  41. package/dist/core/objects/procedure/changes/procedure.security-label.d.ts +28 -0
  42. package/dist/core/objects/procedure/changes/procedure.security-label.js +69 -0
  43. package/dist/core/objects/procedure/changes/procedure.types.d.ts +2 -1
  44. package/dist/core/objects/procedure/procedure.diff.js +16 -0
  45. package/dist/core/objects/procedure/procedure.model.d.ts +10 -0
  46. package/dist/core/objects/procedure/procedure.model.js +19 -1
  47. package/dist/core/objects/publication/changes/publication.base.d.ts +1 -1
  48. package/dist/core/objects/publication/changes/publication.security-label.d.ts +28 -0
  49. package/dist/core/objects/publication/changes/publication.security-label.js +61 -0
  50. package/dist/core/objects/publication/changes/publication.types.d.ts +2 -1
  51. package/dist/core/objects/publication/publication.diff.js +16 -0
  52. package/dist/core/objects/publication/publication.model.d.ts +14 -0
  53. package/dist/core/objects/publication/publication.model.js +20 -1
  54. package/dist/core/objects/role/changes/role.base.d.ts +1 -1
  55. package/dist/core/objects/role/changes/role.security-label.d.ts +28 -0
  56. package/dist/core/objects/role/changes/role.security-label.js +61 -0
  57. package/dist/core/objects/role/changes/role.types.d.ts +2 -1
  58. package/dist/core/objects/role/role.diff.js +16 -0
  59. package/dist/core/objects/role/role.model.d.ts +10 -0
  60. package/dist/core/objects/role/role.model.js +29 -0
  61. package/dist/core/objects/schema/changes/schema.base.d.ts +1 -1
  62. package/dist/core/objects/schema/changes/schema.security-label.d.ts +28 -0
  63. package/dist/core/objects/schema/changes/schema.security-label.js +61 -0
  64. package/dist/core/objects/schema/changes/schema.types.d.ts +2 -1
  65. package/dist/core/objects/schema/schema.diff.js +24 -1
  66. package/dist/core/objects/schema/schema.model.d.ts +10 -0
  67. package/dist/core/objects/schema/schema.model.js +18 -1
  68. package/dist/core/objects/security-label.types.d.ts +20 -0
  69. package/dist/core/objects/security-label.types.js +46 -0
  70. package/dist/core/objects/sequence/changes/sequence.base.d.ts +1 -1
  71. package/dist/core/objects/sequence/changes/sequence.security-label.d.ts +28 -0
  72. package/dist/core/objects/sequence/changes/sequence.security-label.js +61 -0
  73. package/dist/core/objects/sequence/changes/sequence.types.d.ts +2 -1
  74. package/dist/core/objects/sequence/sequence.diff.js +16 -0
  75. package/dist/core/objects/sequence/sequence.model.d.ts +10 -0
  76. package/dist/core/objects/sequence/sequence.model.js +19 -1
  77. package/dist/core/objects/subscription/changes/subscription.base.d.ts +1 -1
  78. package/dist/core/objects/subscription/changes/subscription.security-label.d.ts +28 -0
  79. package/dist/core/objects/subscription/changes/subscription.security-label.js +61 -0
  80. package/dist/core/objects/subscription/changes/subscription.types.d.ts +2 -1
  81. package/dist/core/objects/subscription/subscription.diff.js +16 -0
  82. package/dist/core/objects/subscription/subscription.model.d.ts +10 -0
  83. package/dist/core/objects/subscription/subscription.model.js +19 -1
  84. package/dist/core/objects/table/changes/table.base.d.ts +1 -1
  85. package/dist/core/objects/table/changes/table.security-label.d.ts +63 -0
  86. package/dist/core/objects/table/changes/table.security-label.js +134 -0
  87. package/dist/core/objects/table/changes/table.types.d.ts +2 -1
  88. package/dist/core/objects/table/table.diff.js +49 -0
  89. package/dist/core/objects/table/table.model.d.ts +30 -0
  90. package/dist/core/objects/table/table.model.js +34 -2
  91. package/dist/core/objects/type/composite-type/changes/composite-type.base.d.ts +1 -1
  92. package/dist/core/objects/type/composite-type/changes/composite-type.security-label.d.ts +28 -0
  93. package/dist/core/objects/type/composite-type/changes/composite-type.security-label.js +61 -0
  94. package/dist/core/objects/type/composite-type/changes/composite-type.types.d.ts +2 -1
  95. package/dist/core/objects/type/composite-type/composite-type.diff.js +16 -0
  96. package/dist/core/objects/type/composite-type/composite-type.model.d.ts +22 -0
  97. package/dist/core/objects/type/composite-type/composite-type.model.js +22 -2
  98. package/dist/core/objects/type/enum/changes/enum.base.d.ts +1 -1
  99. package/dist/core/objects/type/enum/changes/enum.security-label.d.ts +28 -0
  100. package/dist/core/objects/type/enum/changes/enum.security-label.js +61 -0
  101. package/dist/core/objects/type/enum/changes/enum.types.d.ts +2 -1
  102. package/dist/core/objects/type/enum/enum.diff.js +16 -0
  103. package/dist/core/objects/type/enum/enum.model.d.ts +10 -0
  104. package/dist/core/objects/type/enum/enum.model.js +20 -1
  105. package/dist/core/objects/type/range/changes/range.base.d.ts +1 -1
  106. package/dist/core/objects/type/range/changes/range.security-label.d.ts +28 -0
  107. package/dist/core/objects/type/range/changes/range.security-label.js +61 -0
  108. package/dist/core/objects/type/range/changes/range.types.d.ts +2 -1
  109. package/dist/core/objects/type/range/range.diff.js +16 -0
  110. package/dist/core/objects/type/range/range.model.d.ts +10 -0
  111. package/dist/core/objects/type/range/range.model.js +19 -1
  112. package/dist/core/objects/utils.d.ts +1 -0
  113. package/dist/core/objects/utils.js +3 -0
  114. package/dist/core/objects/view/changes/view.base.d.ts +1 -1
  115. package/dist/core/objects/view/changes/view.security-label.d.ts +28 -0
  116. package/dist/core/objects/view/changes/view.security-label.js +61 -0
  117. package/dist/core/objects/view/changes/view.types.d.ts +2 -1
  118. package/dist/core/objects/view/view.diff.js +13 -0
  119. package/dist/core/objects/view/view.model.d.ts +26 -0
  120. package/dist/core/objects/view/view.model.js +20 -1
  121. package/dist/core/plan/sql-format/fixtures.js +1 -0
  122. package/package.json +1 -1
  123. package/src/core/catalog.model.ts +1 -0
  124. package/src/core/integrations/filter/dsl.test.ts +27 -0
  125. package/src/core/integrations/filter/flatten.ts +16 -0
  126. package/src/core/objects/aggregate/aggregate.diff.ts +33 -0
  127. package/src/core/objects/aggregate/aggregate.model.ts +22 -1
  128. package/src/core/objects/aggregate/changes/aggregate.base.ts +5 -1
  129. package/src/core/objects/aggregate/changes/aggregate.security-label.ts +99 -0
  130. package/src/core/objects/aggregate/changes/aggregate.types.ts +3 -1
  131. package/src/core/objects/base.model.ts +2 -0
  132. package/src/core/objects/domain/changes/domain.base.ts +5 -1
  133. package/src/core/objects/domain/changes/domain.security-label.test.ts +56 -0
  134. package/src/core/objects/domain/changes/domain.security-label.ts +77 -0
  135. package/src/core/objects/domain/changes/domain.types.ts +3 -1
  136. package/src/core/objects/domain/domain.diff.ts +33 -0
  137. package/src/core/objects/domain/domain.model.ts +22 -1
  138. package/src/core/objects/event-trigger/changes/event-trigger.base.ts +1 -1
  139. package/src/core/objects/event-trigger/changes/event-trigger.security-label.ts +95 -0
  140. package/src/core/objects/event-trigger/changes/event-trigger.types.ts +3 -1
  141. package/src/core/objects/event-trigger/event-trigger.diff.ts +33 -0
  142. package/src/core/objects/event-trigger/event-trigger.model.ts +22 -1
  143. package/src/core/objects/foreign-data-wrapper/foreign-table/changes/foreign-table.base.ts +5 -1
  144. package/src/core/objects/foreign-data-wrapper/foreign-table/changes/foreign-table.security-label.ts +95 -0
  145. package/src/core/objects/foreign-data-wrapper/foreign-table/changes/foreign-table.types.ts +3 -1
  146. package/src/core/objects/foreign-data-wrapper/foreign-table/foreign-table.diff.ts +33 -0
  147. package/src/core/objects/foreign-data-wrapper/foreign-table/foreign-table.model.ts +24 -1
  148. package/src/core/objects/materialized-view/changes/materialized-view.base.ts +5 -1
  149. package/src/core/objects/materialized-view/changes/materialized-view.security-label.test.ts +63 -0
  150. package/src/core/objects/materialized-view/changes/materialized-view.security-label.ts +95 -0
  151. package/src/core/objects/materialized-view/changes/materialized-view.types.ts +3 -1
  152. package/src/core/objects/materialized-view/materialized-view.diff.ts +37 -0
  153. package/src/core/objects/materialized-view/materialized-view.model.ts +25 -4
  154. package/src/core/objects/procedure/changes/procedure.base.ts +5 -1
  155. package/src/core/objects/procedure/changes/procedure.security-label.ts +105 -0
  156. package/src/core/objects/procedure/changes/procedure.types.ts +3 -1
  157. package/src/core/objects/procedure/procedure.diff.ts +33 -0
  158. package/src/core/objects/procedure/procedure.model.ts +23 -2
  159. package/src/core/objects/publication/changes/publication.base.ts +1 -1
  160. package/src/core/objects/publication/changes/publication.security-label.ts +95 -0
  161. package/src/core/objects/publication/changes/publication.types.ts +3 -1
  162. package/src/core/objects/publication/publication.diff.ts +33 -0
  163. package/src/core/objects/publication/publication.model.ts +24 -1
  164. package/src/core/objects/role/changes/role.base.ts +2 -1
  165. package/src/core/objects/role/changes/role.security-label.ts +77 -0
  166. package/src/core/objects/role/changes/role.types.ts +3 -1
  167. package/src/core/objects/role/role.diff.ts +33 -0
  168. package/src/core/objects/role/role.model.ts +32 -0
  169. package/src/core/objects/schema/changes/schema.alter.test.ts +1 -0
  170. package/src/core/objects/schema/changes/schema.base.ts +5 -1
  171. package/src/core/objects/schema/changes/schema.create.test.ts +1 -0
  172. package/src/core/objects/schema/changes/schema.drop.test.ts +1 -0
  173. package/src/core/objects/schema/changes/schema.security-label.test.ts +76 -0
  174. package/src/core/objects/schema/changes/schema.security-label.ts +77 -0
  175. package/src/core/objects/schema/changes/schema.types.ts +3 -1
  176. package/src/core/objects/schema/schema.diff.test.ts +1 -0
  177. package/src/core/objects/schema/schema.diff.ts +43 -1
  178. package/src/core/objects/schema/schema.model.ts +21 -1
  179. package/src/core/objects/security-label.types.test.ts +106 -0
  180. package/src/core/objects/security-label.types.ts +61 -0
  181. package/src/core/objects/sequence/changes/sequence.base.ts +5 -1
  182. package/src/core/objects/sequence/changes/sequence.security-label.test.ts +58 -0
  183. package/src/core/objects/sequence/changes/sequence.security-label.ts +92 -0
  184. package/src/core/objects/sequence/changes/sequence.types.ts +3 -1
  185. package/src/core/objects/sequence/sequence.diff.ts +33 -0
  186. package/src/core/objects/sequence/sequence.model.ts +22 -1
  187. package/src/core/objects/subscription/changes/subscription.base.ts +1 -1
  188. package/src/core/objects/subscription/changes/subscription.security-label.ts +95 -0
  189. package/src/core/objects/subscription/changes/subscription.types.ts +3 -1
  190. package/src/core/objects/subscription/subscription.diff.ts +33 -0
  191. package/src/core/objects/subscription/subscription.model.ts +22 -1
  192. package/src/core/objects/table/changes/table.base.ts +5 -1
  193. package/src/core/objects/table/changes/table.security-label.test.ts +140 -0
  194. package/src/core/objects/table/changes/table.security-label.ts +183 -0
  195. package/src/core/objects/table/changes/table.types.ts +3 -1
  196. package/src/core/objects/table/table.diff.ts +87 -0
  197. package/src/core/objects/table/table.model.ts +42 -2
  198. package/src/core/objects/type/composite-type/changes/composite-type.base.ts +5 -1
  199. package/src/core/objects/type/composite-type/changes/composite-type.security-label.ts +95 -0
  200. package/src/core/objects/type/composite-type/changes/composite-type.types.ts +3 -1
  201. package/src/core/objects/type/composite-type/composite-type.diff.ts +33 -0
  202. package/src/core/objects/type/composite-type/composite-type.model.ts +26 -2
  203. package/src/core/objects/type/enum/changes/enum.base.ts +5 -1
  204. package/src/core/objects/type/enum/changes/enum.security-label.ts +77 -0
  205. package/src/core/objects/type/enum/changes/enum.types.ts +3 -1
  206. package/src/core/objects/type/enum/enum.diff.ts +33 -0
  207. package/src/core/objects/type/enum/enum.model.ts +25 -1
  208. package/src/core/objects/type/range/changes/range.base.ts +5 -1
  209. package/src/core/objects/type/range/changes/range.security-label.ts +77 -0
  210. package/src/core/objects/type/range/changes/range.types.ts +3 -1
  211. package/src/core/objects/type/range/range.diff.ts +33 -0
  212. package/src/core/objects/type/range/range.model.ts +22 -1
  213. package/src/core/objects/utils.ts +3 -0
  214. package/src/core/objects/view/changes/view.base.ts +5 -1
  215. package/src/core/objects/view/changes/view.security-label.test.ts +64 -0
  216. package/src/core/objects/view/changes/view.security-label.ts +77 -0
  217. package/src/core/objects/view/changes/view.types.ts +3 -1
  218. package/src/core/objects/view/view.diff.ts +31 -0
  219. package/src/core/objects/view/view.model.ts +25 -2
  220. package/src/core/plan/sql-format/fixtures.ts +1 -0
@@ -1,5 +1,6 @@
1
1
  import { diffObjects } from "../base.diff.ts";
2
2
  import type { ObjectDiffContext } from "../diff-context.ts";
3
+ import { diffSecurityLabels } from "../security-label.types.ts";
3
4
  import { deepEqual } from "../utils.ts";
4
5
  import {
5
6
  AlterPublicationAddSchemas,
@@ -15,6 +16,10 @@ import {
15
16
  } from "./changes/publication.comment.ts";
16
17
  import { CreatePublication } from "./changes/publication.create.ts";
17
18
  import { DropPublication } from "./changes/publication.drop.ts";
19
+ import {
20
+ CreateSecurityLabelOnPublication,
21
+ DropSecurityLabelOnPublication,
22
+ } from "./changes/publication.security-label.ts";
18
23
  import type { PublicationChange } from "./changes/publication.types.ts";
19
24
  import type {
20
25
  Publication,
@@ -47,6 +52,14 @@ export function diffPublications(
47
52
  if (publication.comment !== null) {
48
53
  changes.push(new CreateCommentOnPublication({ publication }));
49
54
  }
55
+ for (const label of publication.security_labels) {
56
+ changes.push(
57
+ new CreateSecurityLabelOnPublication({
58
+ publication,
59
+ securityLabel: label,
60
+ }),
61
+ );
62
+ }
50
63
  }
51
64
 
52
65
  for (const id of dropped) {
@@ -172,6 +185,26 @@ export function diffPublications(
172
185
  );
173
186
  }
174
187
  }
188
+
189
+ // SECURITY LABELS
190
+ changes.push(
191
+ ...diffSecurityLabels<
192
+ CreateSecurityLabelOnPublication | DropSecurityLabelOnPublication
193
+ >(
194
+ mainPublication.security_labels,
195
+ branchPublication.security_labels,
196
+ (securityLabel) =>
197
+ new CreateSecurityLabelOnPublication({
198
+ publication: branchPublication,
199
+ securityLabel,
200
+ }),
201
+ (securityLabel) =>
202
+ new DropSecurityLabelOnPublication({
203
+ publication: mainPublication,
204
+ securityLabel,
205
+ }),
206
+ ),
207
+ );
175
208
  }
176
209
 
177
210
  return changes;
@@ -2,6 +2,11 @@ import { sql } from "@ts-safeql/sql-tag";
2
2
  import type { Pool } from "pg";
3
3
  import z from "zod";
4
4
  import { BasePgModel } from "../base.model.ts";
5
+ import {
6
+ normalizeSecurityLabels,
7
+ type SecurityLabelProps,
8
+ securityLabelPropsSchema,
9
+ } from "../security-label.types.ts";
5
10
 
6
11
  const publicationTablePropsSchema = z.object({
7
12
  schema: z.string(),
@@ -22,6 +27,7 @@ const publicationPropsSchema = z.object({
22
27
  publish_via_partition_root: z.boolean(),
23
28
  tables: z.array(publicationTablePropsSchema),
24
29
  schemas: z.array(z.string()),
30
+ security_labels: z.array(securityLabelPropsSchema).default([]).optional(),
25
31
  });
26
32
 
27
33
  export type PublicationTableProps = z.infer<typeof publicationTablePropsSchema>;
@@ -44,6 +50,7 @@ export class Publication extends BasePgModel {
44
50
  public readonly publish_via_partition_root: PublicationProps["publish_via_partition_root"];
45
51
  public readonly tables: PublicationTableProps[];
46
52
  public readonly schemas: PublicationProps["schemas"];
53
+ public readonly security_labels: SecurityLabelProps[];
47
54
 
48
55
  constructor(props: PublicationProps) {
49
56
  super();
@@ -72,6 +79,7 @@ export class Publication extends BasePgModel {
72
79
  });
73
80
 
74
81
  this.schemas = [...props.schemas].sort((a, b) => a.localeCompare(b));
82
+ this.security_labels = props.security_labels ?? [];
75
83
  }
76
84
 
77
85
  get stableId(): `publication:${string}` {
@@ -96,6 +104,7 @@ export class Publication extends BasePgModel {
96
104
  publish_via_partition_root: this.publish_via_partition_root,
97
105
  tables: this.tables,
98
106
  schemas: this.schemas,
107
+ security_labels: this.security_labels,
99
108
  };
100
109
  }
101
110
 
@@ -118,6 +127,7 @@ export class Publication extends BasePgModel {
118
127
  a.schema.localeCompare(b.schema) || a.name.localeCompare(b.name),
119
128
  ),
120
129
  schemas: [...this.schemas].sort((a, b) => a.localeCompare(b)),
130
+ security_labels: normalizeSecurityLabels(this.security_labels),
121
131
  },
122
132
  };
123
133
  }
@@ -194,7 +204,20 @@ export async function extractPublications(pool: Pool): Promise<Publication[]> {
194
204
  where s.pnpubid = p.oid
195
205
  ),
196
206
  '[]'::json
197
- ) as schemas
207
+ ) as schemas,
208
+ coalesce(
209
+ (
210
+ select json_agg(
211
+ json_build_object('provider', sl.provider, 'label', sl.label)
212
+ order by sl.provider
213
+ )
214
+ from pg_catalog.pg_seclabel sl
215
+ where sl.objoid = p.oid
216
+ and sl.classoid = 'pg_publication'::regclass
217
+ and sl.objsubid = 0
218
+ ),
219
+ '[]'::json
220
+ ) as security_labels
198
221
  from pg_publication p
199
222
  left join extension_oids e on e.objid = p.oid
200
223
  where e.objid is null
@@ -7,7 +7,8 @@ abstract class BaseRoleChange extends BaseChange {
7
7
  | "object"
8
8
  | "comment"
9
9
  | "membership"
10
- | "default_privilege";
10
+ | "default_privilege"
11
+ | "security_label";
11
12
  readonly objectType: "role" = "role";
12
13
  }
13
14
 
@@ -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 { Role } from "../role.model.ts";
5
+ import { CreateRoleChange, DropRoleChange } from "./role.base.ts";
6
+
7
+ export type SecurityLabelRole =
8
+ | CreateSecurityLabelOnRole
9
+ | DropSecurityLabelOnRole;
10
+
11
+ export class CreateSecurityLabelOnRole extends CreateRoleChange {
12
+ public readonly role: Role;
13
+ public readonly securityLabel: SecurityLabelProps;
14
+ public readonly scope = "security_label" as const;
15
+
16
+ constructor(props: { role: Role; securityLabel: SecurityLabelProps }) {
17
+ super();
18
+ this.role = props.role;
19
+ this.securityLabel = props.securityLabel;
20
+ }
21
+
22
+ get creates() {
23
+ return [
24
+ stableId.securityLabel(this.role.stableId, this.securityLabel.provider),
25
+ ];
26
+ }
27
+
28
+ get requires() {
29
+ return [this.role.stableId];
30
+ }
31
+
32
+ serialize(): string {
33
+ return [
34
+ "SECURITY LABEL FOR",
35
+ this.securityLabel.provider,
36
+ "ON ROLE",
37
+ this.role.name,
38
+ "IS",
39
+ quoteLiteral(this.securityLabel.label),
40
+ ].join(" ");
41
+ }
42
+ }
43
+
44
+ export class DropSecurityLabelOnRole extends DropRoleChange {
45
+ public readonly role: Role;
46
+ public readonly securityLabel: SecurityLabelProps;
47
+ public readonly scope = "security_label" as const;
48
+
49
+ constructor(props: { role: Role; securityLabel: SecurityLabelProps }) {
50
+ super();
51
+ this.role = props.role;
52
+ this.securityLabel = props.securityLabel;
53
+ }
54
+
55
+ get drops() {
56
+ return [
57
+ stableId.securityLabel(this.role.stableId, this.securityLabel.provider),
58
+ ];
59
+ }
60
+
61
+ get requires() {
62
+ return [
63
+ stableId.securityLabel(this.role.stableId, this.securityLabel.provider),
64
+ this.role.stableId,
65
+ ];
66
+ }
67
+
68
+ serialize(): string {
69
+ return [
70
+ "SECURITY LABEL FOR",
71
+ this.securityLabel.provider,
72
+ "ON ROLE",
73
+ this.role.name,
74
+ "IS NULL",
75
+ ].join(" ");
76
+ }
77
+ }
@@ -3,6 +3,7 @@ import type { CommentRole } from "./role.comment.ts";
3
3
  import type { CreateRole } from "./role.create.ts";
4
4
  import type { DropRole } from "./role.drop.ts";
5
5
  import type { RolePrivilege } from "./role.privilege.ts";
6
+ import type { SecurityLabelRole } from "./role.security-label.ts";
6
7
 
7
8
  /** Union of all role-related change variants (`objectType: "role"`). @category Change Types */
8
9
  export type RoleChange =
@@ -10,4 +11,5 @@ export type RoleChange =
10
11
  | CommentRole
11
12
  | CreateRole
12
13
  | DropRole
13
- | RolePrivilege;
14
+ | RolePrivilege
15
+ | SecurityLabelRole;
@@ -1,4 +1,5 @@
1
1
  import { diffObjects } from "../base.diff.ts";
2
+ import { diffSecurityLabels } from "../security-label.types.ts";
2
3
  import {
3
4
  AlterRoleSetConfig,
4
5
  AlterRoleSetOptions,
@@ -16,6 +17,10 @@ import {
16
17
  RevokeRoleMembership,
17
18
  RevokeRoleMembershipOptions,
18
19
  } from "./changes/role.privilege.ts";
20
+ import {
21
+ CreateSecurityLabelOnRole,
22
+ DropSecurityLabelOnRole,
23
+ } from "./changes/role.security-label.ts";
19
24
  import type { RoleChange } from "./changes/role.types.ts";
20
25
  import type { Role } from "./role.model.ts";
21
26
 
@@ -51,6 +56,14 @@ export function diffRoles(
51
56
  if (role.comment !== null) {
52
57
  changes.push(new CreateCommentOnRole({ role }));
53
58
  }
59
+ for (const label of role.security_labels) {
60
+ changes.push(
61
+ new CreateSecurityLabelOnRole({
62
+ role,
63
+ securityLabel: label,
64
+ }),
65
+ );
66
+ }
54
67
  // MEMBERSHIPS: Grant memberships immediately after role creation.
55
68
  // Members are already deduplicated by the Role model constructor.
56
69
  for (const membership of role.members) {
@@ -215,6 +228,26 @@ export function diffRoles(
215
228
  }
216
229
  }
217
230
 
231
+ // SECURITY LABELS
232
+ changes.push(
233
+ ...diffSecurityLabels<
234
+ CreateSecurityLabelOnRole | DropSecurityLabelOnRole
235
+ >(
236
+ mainRole.security_labels,
237
+ branchRole.security_labels,
238
+ (securityLabel) =>
239
+ new CreateSecurityLabelOnRole({
240
+ role: branchRole,
241
+ securityLabel,
242
+ }),
243
+ (securityLabel) =>
244
+ new DropSecurityLabelOnRole({
245
+ role: mainRole,
246
+ securityLabel,
247
+ }),
248
+ ),
249
+ );
250
+
218
251
  // MEMBERSHIPS
219
252
  // Members are already deduplicated by the Role model constructor.
220
253
  const mainMembers = new Map(mainRole.members.map((m) => [m.member, m]));
@@ -2,6 +2,10 @@ import { sql } from "@ts-safeql/sql-tag";
2
2
  import type { Pool } from "pg";
3
3
  import z from "zod";
4
4
  import { BasePgModel } from "../base.model.ts";
5
+ import {
6
+ type SecurityLabelProps,
7
+ securityLabelPropsSchema,
8
+ } from "../security-label.types.ts";
5
9
 
6
10
  const membershipInfoSchema = z.object({
7
11
  member: z.string(),
@@ -35,6 +39,7 @@ const rolePropsSchema = z.object({
35
39
  comment: z.string().nullable(),
36
40
  members: z.array(membershipInfoSchema),
37
41
  default_privileges: z.array(defaultPrivilegeSchema),
42
+ security_labels: z.array(securityLabelPropsSchema).default([]).optional(),
38
43
  });
39
44
 
40
45
  export type RoleProps = z.infer<typeof rolePropsSchema>;
@@ -53,6 +58,7 @@ export class Role extends BasePgModel {
53
58
  public readonly comment: RoleProps["comment"];
54
59
  public readonly members: RoleProps["members"];
55
60
  public readonly default_privileges: RoleProps["default_privileges"];
61
+ public readonly security_labels: SecurityLabelProps[];
56
62
 
57
63
  constructor(props: RoleProps) {
58
64
  super();
@@ -73,6 +79,7 @@ export class Role extends BasePgModel {
73
79
  this.comment = props.comment;
74
80
  this.members = deduplicateMembers(props.members);
75
81
  this.default_privileges = props.default_privileges;
82
+ this.security_labels = props.security_labels ?? [];
76
83
  }
77
84
 
78
85
  get stableId(): `role:${string}` {
@@ -129,6 +136,7 @@ export class Role extends BasePgModel {
129
136
  comment: this.comment,
130
137
  members: sortedMembers,
131
138
  default_privileges: sortedDefaultPrivs,
139
+ security_labels: this.security_labels,
132
140
  };
133
141
  }
134
142
  }
@@ -232,6 +240,18 @@ export async function extractRoles(pool: Pool): Promise<Role[]> {
232
240
  r.rolbypassrls AS can_bypass_rls,
233
241
  r.rolconfig AS config,
234
242
  obj_description(r.oid, 'pg_authid') AS comment,
243
+ COALESCE(
244
+ (
245
+ SELECT json_agg(
246
+ json_build_object('provider', sl.provider, 'label', sl.label)
247
+ ORDER BY sl.provider
248
+ )
249
+ FROM pg_catalog.pg_shseclabel sl
250
+ WHERE sl.objoid = r.oid
251
+ AND sl.classoid = 'pg_authid'::regclass
252
+ ),
253
+ '[]'::json
254
+ ) AS security_labels,
235
255
  COALESCE(rm.members, '[]') AS members,
236
256
  COALESCE(
237
257
  (
@@ -353,6 +373,18 @@ export async function extractRoles(pool: Pool): Promise<Role[]> {
353
373
  r.rolbypassrls AS can_bypass_rls,
354
374
  r.rolconfig AS config,
355
375
  obj_description(r.oid, 'pg_authid') AS comment,
376
+ COALESCE(
377
+ (
378
+ SELECT json_agg(
379
+ json_build_object('provider', sl.provider, 'label', sl.label)
380
+ ORDER BY sl.provider
381
+ )
382
+ FROM pg_catalog.pg_shseclabel sl
383
+ WHERE sl.objoid = r.oid
384
+ AND sl.classoid = 'pg_authid'::regclass
385
+ ),
386
+ '[]'::json
387
+ ) AS security_labels,
356
388
  COALESCE(rm.members, '[]') AS members,
357
389
  COALESCE(
358
390
  (
@@ -10,6 +10,7 @@ describe.concurrent("schema", () => {
10
10
  name: "test_schema",
11
11
  comment: null,
12
12
  privileges: [],
13
+ security_labels: [],
13
14
  };
14
15
  const schemaObj = new Schema({
15
16
  ...props,
@@ -3,7 +3,11 @@ import type { Schema } from "../schema.model.ts";
3
3
 
4
4
  abstract class BaseSchemaChange extends BaseChange {
5
5
  abstract readonly schema: Schema;
6
- abstract readonly scope: "object" | "comment" | "privilege";
6
+ abstract readonly scope:
7
+ | "object"
8
+ | "comment"
9
+ | "privilege"
10
+ | "security_label";
7
11
  readonly objectType: "schema" = "schema";
8
12
  }
9
13
 
@@ -10,6 +10,7 @@ describe("schema", () => {
10
10
  owner: "test",
11
11
  comment: null,
12
12
  privileges: [],
13
+ security_labels: [],
13
14
  });
14
15
 
15
16
  const change = new CreateSchema({
@@ -10,6 +10,7 @@ describe("schema", () => {
10
10
  owner: "test",
11
11
  comment: null,
12
12
  privileges: [],
13
+ security_labels: [],
13
14
  });
14
15
 
15
16
  const change = new DropSchema({
@@ -0,0 +1,76 @@
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 { Schema } from "../schema.model.ts";
5
+ import {
6
+ CreateSecurityLabelOnSchema,
7
+ DropSecurityLabelOnSchema,
8
+ } from "./schema.security-label.ts";
9
+
10
+ const makeSchema = () =>
11
+ new Schema({
12
+ name: "app",
13
+ owner: "postgres",
14
+ comment: null,
15
+ privileges: [],
16
+ security_labels: [],
17
+ });
18
+
19
+ describe("schema.security-label", () => {
20
+ test("create serializes and tracks dependencies", async () => {
21
+ const schema = makeSchema();
22
+ const change = new CreateSecurityLabelOnSchema({
23
+ schema,
24
+ securityLabel: {
25
+ provider: "pg_graphql",
26
+ label: '{"inflect_names":true}',
27
+ },
28
+ });
29
+
30
+ expect(change.scope).toBe("security_label");
31
+ expect(change.operation).toBe("create");
32
+ expect(change.objectType).toBe("schema");
33
+ expect(change.creates).toEqual([
34
+ stableId.securityLabel(schema.stableId, "pg_graphql"),
35
+ ]);
36
+ expect(change.requires).toEqual([schema.stableId]);
37
+ await assertValidSql(change.serialize());
38
+ expect(change.serialize()).toBe(
39
+ `SECURITY LABEL FOR pg_graphql ON SCHEMA app IS '{"inflect_names":true}'`,
40
+ );
41
+ });
42
+
43
+ test("drop serializes to IS NULL and tracks dependencies", async () => {
44
+ const schema = makeSchema();
45
+ const change = new DropSecurityLabelOnSchema({
46
+ schema,
47
+ securityLabel: { provider: "pg_graphql", label: "old" },
48
+ });
49
+
50
+ expect(change.scope).toBe("security_label");
51
+ expect(change.operation).toBe("drop");
52
+ expect(change.drops).toEqual([
53
+ stableId.securityLabel(schema.stableId, "pg_graphql"),
54
+ ]);
55
+ expect(change.requires).toEqual([
56
+ stableId.securityLabel(schema.stableId, "pg_graphql"),
57
+ schema.stableId,
58
+ ]);
59
+ await assertValidSql(change.serialize());
60
+ expect(change.serialize()).toBe(
61
+ "SECURITY LABEL FOR pg_graphql ON SCHEMA app IS NULL",
62
+ );
63
+ });
64
+
65
+ test("create escapes single quotes in label", async () => {
66
+ const schema = makeSchema();
67
+ const change = new CreateSecurityLabelOnSchema({
68
+ schema,
69
+ securityLabel: { provider: "p", label: "it's a test" },
70
+ });
71
+ await assertValidSql(change.serialize());
72
+ expect(change.serialize()).toBe(
73
+ "SECURITY LABEL FOR p ON SCHEMA app IS 'it''s a test'",
74
+ );
75
+ });
76
+ });
@@ -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 { Schema } from "../schema.model.ts";
5
+ import { CreateSchemaChange, DropSchemaChange } from "./schema.base.ts";
6
+
7
+ export type SecurityLabelSchema =
8
+ | CreateSecurityLabelOnSchema
9
+ | DropSecurityLabelOnSchema;
10
+
11
+ export class CreateSecurityLabelOnSchema extends CreateSchemaChange {
12
+ public readonly schema: Schema;
13
+ public readonly securityLabel: SecurityLabelProps;
14
+ public readonly scope = "security_label" as const;
15
+
16
+ constructor(props: { schema: Schema; securityLabel: SecurityLabelProps }) {
17
+ super();
18
+ this.schema = props.schema;
19
+ this.securityLabel = props.securityLabel;
20
+ }
21
+
22
+ get creates() {
23
+ return [
24
+ stableId.securityLabel(this.schema.stableId, this.securityLabel.provider),
25
+ ];
26
+ }
27
+
28
+ get requires() {
29
+ return [this.schema.stableId];
30
+ }
31
+
32
+ serialize(): string {
33
+ return [
34
+ "SECURITY LABEL FOR",
35
+ this.securityLabel.provider,
36
+ "ON SCHEMA",
37
+ this.schema.name,
38
+ "IS",
39
+ quoteLiteral(this.securityLabel.label),
40
+ ].join(" ");
41
+ }
42
+ }
43
+
44
+ export class DropSecurityLabelOnSchema extends DropSchemaChange {
45
+ public readonly schema: Schema;
46
+ public readonly securityLabel: SecurityLabelProps;
47
+ public readonly scope = "security_label" as const;
48
+
49
+ constructor(props: { schema: Schema; securityLabel: SecurityLabelProps }) {
50
+ super();
51
+ this.schema = props.schema;
52
+ this.securityLabel = props.securityLabel;
53
+ }
54
+
55
+ get drops() {
56
+ return [
57
+ stableId.securityLabel(this.schema.stableId, this.securityLabel.provider),
58
+ ];
59
+ }
60
+
61
+ get requires() {
62
+ return [
63
+ stableId.securityLabel(this.schema.stableId, this.securityLabel.provider),
64
+ this.schema.stableId,
65
+ ];
66
+ }
67
+
68
+ serialize(): string {
69
+ return [
70
+ "SECURITY LABEL FOR",
71
+ this.securityLabel.provider,
72
+ "ON SCHEMA",
73
+ this.schema.name,
74
+ "IS NULL",
75
+ ].join(" ");
76
+ }
77
+ }
@@ -3,6 +3,7 @@ import type { CommentSchema } from "./schema.comment.ts";
3
3
  import type { CreateSchema } from "./schema.create.ts";
4
4
  import type { DropSchema } from "./schema.drop.ts";
5
5
  import type { SchemaPrivilege } from "./schema.privilege.ts";
6
+ import type { SecurityLabelSchema } from "./schema.security-label.ts";
6
7
 
7
8
  /** Union of all schema-related change variants (`objectType: "schema"`). @category Change Types */
8
9
  export type SchemaChange =
@@ -10,4 +11,5 @@ export type SchemaChange =
10
11
  | CommentSchema
11
12
  | CreateSchema
12
13
  | DropSchema
13
- | SchemaPrivilege;
14
+ | SchemaPrivilege
15
+ | SecurityLabelSchema;
@@ -11,6 +11,7 @@ const base: SchemaProps = {
11
11
  owner: "o1",
12
12
  comment: null,
13
13
  privileges: [],
14
+ security_labels: [],
14
15
  };
15
16
 
16
17
  const testContext = {
@@ -4,6 +4,7 @@ import {
4
4
  emitObjectPrivilegeChanges,
5
5
  } from "../base.privilege-diff.ts";
6
6
  import type { ObjectDiffContext } from "../diff-context.ts";
7
+ import { diffSecurityLabels } from "../security-label.types.ts";
7
8
  import { AlterSchemaChangeOwner } from "./changes/schema.alter.ts";
8
9
  import {
9
10
  CreateCommentOnSchema,
@@ -16,6 +17,10 @@ import {
16
17
  RevokeGrantOptionSchemaPrivileges,
17
18
  RevokeSchemaPrivileges,
18
19
  } from "./changes/schema.privilege.ts";
20
+ import {
21
+ CreateSecurityLabelOnSchema,
22
+ DropSecurityLabelOnSchema,
23
+ } from "./changes/schema.security-label.ts";
19
24
  import type { SchemaChange } from "./changes/schema.types.ts";
20
25
  import type { Schema } from "./schema.model.ts";
21
26
 
@@ -45,6 +50,14 @@ export function diffSchemas(
45
50
  if (sc.comment !== null) {
46
51
  changes.push(new CreateCommentOnSchema({ schema: sc }));
47
52
  }
53
+ for (const label of sc.security_labels) {
54
+ changes.push(
55
+ new CreateSecurityLabelOnSchema({
56
+ schema: sc,
57
+ securityLabel: label,
58
+ }),
59
+ );
60
+ }
48
61
 
49
62
  // PRIVILEGES: For created objects, compare against default privileges state
50
63
  // The migration script will run ALTER DEFAULT PRIVILEGES before CREATE (via constraint spec),
@@ -87,7 +100,16 @@ export function diffSchemas(
87
100
  }
88
101
 
89
102
  for (const schemaId of dropped) {
90
- changes.push(new DropSchema({ schema: main[schemaId] }));
103
+ const mainSchema = main[schemaId];
104
+ for (const label of mainSchema.security_labels) {
105
+ changes.push(
106
+ new DropSecurityLabelOnSchema({
107
+ schema: mainSchema,
108
+ securityLabel: label,
109
+ }),
110
+ );
111
+ }
112
+ changes.push(new DropSchema({ schema: mainSchema }));
91
113
  }
92
114
 
93
115
  for (const schemaId of altered) {
@@ -113,6 +135,26 @@ export function diffSchemas(
113
135
  }
114
136
  }
115
137
 
138
+ // SECURITY LABELS
139
+ changes.push(
140
+ ...diffSecurityLabels<
141
+ CreateSecurityLabelOnSchema | DropSecurityLabelOnSchema
142
+ >(
143
+ mainSchema.security_labels,
144
+ branchSchema.security_labels,
145
+ (securityLabel) =>
146
+ new CreateSecurityLabelOnSchema({
147
+ schema: branchSchema,
148
+ securityLabel,
149
+ }),
150
+ (securityLabel) =>
151
+ new DropSecurityLabelOnSchema({
152
+ schema: mainSchema,
153
+ securityLabel,
154
+ }),
155
+ ),
156
+ );
157
+
116
158
  // PRIVILEGES
117
159
  // Filter out owner privileges - owner always has ALL privileges implicitly
118
160
  // and shouldn't be compared. Use branch owner as the reference.