@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.
Files changed (271) hide show
  1. package/dist/core/catalog.diff.js +4 -3
  2. package/dist/core/catalog.model.d.ts +8 -1
  3. package/dist/core/catalog.model.js +10 -8
  4. package/dist/core/expand-replace-dependencies.js +23 -0
  5. package/dist/core/integrations/filter/flatten.js +13 -0
  6. package/dist/core/objects/aggregate/aggregate.diff.js +16 -0
  7. package/dist/core/objects/aggregate/aggregate.model.d.ts +10 -0
  8. package/dist/core/objects/aggregate/aggregate.model.js +19 -1
  9. package/dist/core/objects/aggregate/changes/aggregate.base.d.ts +1 -1
  10. package/dist/core/objects/aggregate/changes/aggregate.security-label.d.ts +28 -0
  11. package/dist/core/objects/aggregate/changes/aggregate.security-label.js +64 -0
  12. package/dist/core/objects/aggregate/changes/aggregate.types.d.ts +2 -1
  13. package/dist/core/objects/base.model.d.ts +8 -0
  14. package/dist/core/objects/base.model.js +2 -0
  15. package/dist/core/objects/domain/changes/domain.base.d.ts +1 -1
  16. package/dist/core/objects/domain/changes/domain.security-label.d.ts +28 -0
  17. package/dist/core/objects/domain/changes/domain.security-label.js +61 -0
  18. package/dist/core/objects/domain/changes/domain.types.d.ts +2 -1
  19. package/dist/core/objects/domain/domain.diff.js +16 -0
  20. package/dist/core/objects/domain/domain.model.d.ts +10 -0
  21. package/dist/core/objects/domain/domain.model.js +19 -1
  22. package/dist/core/objects/event-trigger/changes/event-trigger.base.d.ts +1 -1
  23. package/dist/core/objects/event-trigger/changes/event-trigger.security-label.d.ts +28 -0
  24. package/dist/core/objects/event-trigger/changes/event-trigger.security-label.js +61 -0
  25. package/dist/core/objects/event-trigger/changes/event-trigger.types.d.ts +2 -1
  26. package/dist/core/objects/event-trigger/event-trigger.diff.js +16 -0
  27. package/dist/core/objects/event-trigger/event-trigger.model.d.ts +10 -0
  28. package/dist/core/objects/event-trigger/event-trigger.model.js +19 -1
  29. package/dist/core/objects/extract-with-retry.d.ts +36 -0
  30. package/dist/core/objects/extract-with-retry.js +51 -0
  31. package/dist/core/objects/foreign-data-wrapper/foreign-table/changes/foreign-table.base.d.ts +1 -1
  32. package/dist/core/objects/foreign-data-wrapper/foreign-table/changes/foreign-table.security-label.d.ts +28 -0
  33. package/dist/core/objects/foreign-data-wrapper/foreign-table/changes/foreign-table.security-label.js +61 -0
  34. package/dist/core/objects/foreign-data-wrapper/foreign-table/changes/foreign-table.types.d.ts +2 -1
  35. package/dist/core/objects/foreign-data-wrapper/foreign-table/foreign-table.diff.js +16 -0
  36. package/dist/core/objects/foreign-data-wrapper/foreign-table/foreign-table.model.d.ts +22 -0
  37. package/dist/core/objects/foreign-data-wrapper/foreign-table/foreign-table.model.js +20 -1
  38. package/dist/core/objects/index/index.diff.js +0 -1
  39. package/dist/core/objects/index/index.model.d.ts +2 -3
  40. package/dist/core/objects/index/index.model.js +17 -6
  41. package/dist/core/objects/materialized-view/changes/materialized-view.base.d.ts +1 -1
  42. package/dist/core/objects/materialized-view/changes/materialized-view.security-label.d.ts +28 -0
  43. package/dist/core/objects/materialized-view/changes/materialized-view.security-label.js +61 -0
  44. package/dist/core/objects/materialized-view/changes/materialized-view.types.d.ts +2 -1
  45. package/dist/core/objects/materialized-view/materialized-view.diff.js +18 -0
  46. package/dist/core/objects/materialized-view/materialized-view.model.d.ts +24 -1
  47. package/dist/core/objects/materialized-view/materialized-view.model.js +40 -5
  48. package/dist/core/objects/procedure/changes/procedure.base.d.ts +1 -1
  49. package/dist/core/objects/procedure/changes/procedure.security-label.d.ts +28 -0
  50. package/dist/core/objects/procedure/changes/procedure.security-label.js +69 -0
  51. package/dist/core/objects/procedure/changes/procedure.types.d.ts +2 -1
  52. package/dist/core/objects/procedure/procedure.diff.js +16 -0
  53. package/dist/core/objects/procedure/procedure.model.d.ts +12 -1
  54. package/dist/core/objects/procedure/procedure.model.js +39 -5
  55. package/dist/core/objects/publication/changes/publication.base.d.ts +1 -1
  56. package/dist/core/objects/publication/changes/publication.security-label.d.ts +28 -0
  57. package/dist/core/objects/publication/changes/publication.security-label.js +61 -0
  58. package/dist/core/objects/publication/changes/publication.types.d.ts +2 -1
  59. package/dist/core/objects/publication/publication.diff.js +16 -0
  60. package/dist/core/objects/publication/publication.model.d.ts +14 -0
  61. package/dist/core/objects/publication/publication.model.js +20 -1
  62. package/dist/core/objects/rls-policy/rls-policy.diff.js +13 -1
  63. package/dist/core/objects/role/changes/role.base.d.ts +1 -1
  64. package/dist/core/objects/role/changes/role.security-label.d.ts +28 -0
  65. package/dist/core/objects/role/changes/role.security-label.js +61 -0
  66. package/dist/core/objects/role/changes/role.types.d.ts +2 -1
  67. package/dist/core/objects/role/role.diff.js +16 -0
  68. package/dist/core/objects/role/role.model.d.ts +10 -0
  69. package/dist/core/objects/role/role.model.js +29 -0
  70. package/dist/core/objects/rule/rule.model.d.ts +2 -1
  71. package/dist/core/objects/rule/rule.model.js +20 -3
  72. package/dist/core/objects/schema/changes/schema.base.d.ts +1 -1
  73. package/dist/core/objects/schema/changes/schema.security-label.d.ts +28 -0
  74. package/dist/core/objects/schema/changes/schema.security-label.js +61 -0
  75. package/dist/core/objects/schema/changes/schema.types.d.ts +2 -1
  76. package/dist/core/objects/schema/schema.diff.js +24 -1
  77. package/dist/core/objects/schema/schema.model.d.ts +10 -0
  78. package/dist/core/objects/schema/schema.model.js +18 -1
  79. package/dist/core/objects/security-label.types.d.ts +20 -0
  80. package/dist/core/objects/security-label.types.js +46 -0
  81. package/dist/core/objects/sequence/changes/sequence.base.d.ts +1 -1
  82. package/dist/core/objects/sequence/changes/sequence.security-label.d.ts +28 -0
  83. package/dist/core/objects/sequence/changes/sequence.security-label.js +61 -0
  84. package/dist/core/objects/sequence/changes/sequence.types.d.ts +2 -1
  85. package/dist/core/objects/sequence/sequence.diff.d.ts +2 -1
  86. package/dist/core/objects/sequence/sequence.diff.js +44 -4
  87. package/dist/core/objects/sequence/sequence.model.d.ts +10 -0
  88. package/dist/core/objects/sequence/sequence.model.js +19 -1
  89. package/dist/core/objects/subscription/changes/subscription.base.d.ts +1 -1
  90. package/dist/core/objects/subscription/changes/subscription.security-label.d.ts +28 -0
  91. package/dist/core/objects/subscription/changes/subscription.security-label.js +61 -0
  92. package/dist/core/objects/subscription/changes/subscription.types.d.ts +2 -1
  93. package/dist/core/objects/subscription/subscription.diff.js +16 -0
  94. package/dist/core/objects/subscription/subscription.model.d.ts +10 -0
  95. package/dist/core/objects/subscription/subscription.model.js +19 -1
  96. package/dist/core/objects/table/changes/table.alter.d.ts +12 -1
  97. package/dist/core/objects/table/changes/table.alter.js +20 -2
  98. package/dist/core/objects/table/changes/table.base.d.ts +1 -1
  99. package/dist/core/objects/table/changes/table.security-label.d.ts +63 -0
  100. package/dist/core/objects/table/changes/table.security-label.js +134 -0
  101. package/dist/core/objects/table/changes/table.types.d.ts +2 -1
  102. package/dist/core/objects/table/table.diff.js +68 -15
  103. package/dist/core/objects/table/table.model.d.ts +36 -1
  104. package/dist/core/objects/table/table.model.js +74 -7
  105. package/dist/core/objects/trigger/trigger.model.d.ts +2 -1
  106. package/dist/core/objects/trigger/trigger.model.js +20 -4
  107. package/dist/core/objects/type/composite-type/changes/composite-type.base.d.ts +1 -1
  108. package/dist/core/objects/type/composite-type/changes/composite-type.security-label.d.ts +28 -0
  109. package/dist/core/objects/type/composite-type/changes/composite-type.security-label.js +61 -0
  110. package/dist/core/objects/type/composite-type/changes/composite-type.types.d.ts +2 -1
  111. package/dist/core/objects/type/composite-type/composite-type.diff.js +16 -0
  112. package/dist/core/objects/type/composite-type/composite-type.model.d.ts +22 -0
  113. package/dist/core/objects/type/composite-type/composite-type.model.js +22 -2
  114. package/dist/core/objects/type/enum/changes/enum.base.d.ts +1 -1
  115. package/dist/core/objects/type/enum/changes/enum.security-label.d.ts +28 -0
  116. package/dist/core/objects/type/enum/changes/enum.security-label.js +61 -0
  117. package/dist/core/objects/type/enum/changes/enum.types.d.ts +2 -1
  118. package/dist/core/objects/type/enum/enum.diff.js +16 -0
  119. package/dist/core/objects/type/enum/enum.model.d.ts +10 -0
  120. package/dist/core/objects/type/enum/enum.model.js +20 -1
  121. package/dist/core/objects/type/range/changes/range.base.d.ts +1 -1
  122. package/dist/core/objects/type/range/changes/range.security-label.d.ts +28 -0
  123. package/dist/core/objects/type/range/changes/range.security-label.js +61 -0
  124. package/dist/core/objects/type/range/changes/range.types.d.ts +2 -1
  125. package/dist/core/objects/type/range/range.diff.js +16 -0
  126. package/dist/core/objects/type/range/range.model.d.ts +10 -0
  127. package/dist/core/objects/type/range/range.model.js +19 -1
  128. package/dist/core/objects/utils.d.ts +2 -0
  129. package/dist/core/objects/utils.js +6 -0
  130. package/dist/core/objects/view/changes/view.base.d.ts +1 -1
  131. package/dist/core/objects/view/changes/view.security-label.d.ts +28 -0
  132. package/dist/core/objects/view/changes/view.security-label.js +61 -0
  133. package/dist/core/objects/view/changes/view.types.d.ts +2 -1
  134. package/dist/core/objects/view/view.diff.js +13 -0
  135. package/dist/core/objects/view/view.model.d.ts +28 -1
  136. package/dist/core/objects/view/view.model.js +40 -5
  137. package/dist/core/plan/create.js +3 -1
  138. package/dist/core/plan/sql-format/fixtures.js +1 -0
  139. package/dist/core/plan/types.d.ts +8 -0
  140. package/dist/core/{post-diff-cycle-breaking.d.ts → post-diff-normalization.d.ts} +8 -1
  141. package/dist/core/post-diff-normalization.js +202 -0
  142. package/dist/core/sort/cycle-breakers.js +1 -1
  143. package/dist/core/sort/utils.d.ts +10 -0
  144. package/dist/core/sort/utils.js +28 -0
  145. package/package.json +1 -1
  146. package/src/core/catalog.diff.ts +4 -2
  147. package/src/core/catalog.model.ts +21 -8
  148. package/src/core/expand-replace-dependencies.test.ts +131 -0
  149. package/src/core/expand-replace-dependencies.ts +24 -0
  150. package/src/core/integrations/filter/dsl.test.ts +27 -0
  151. package/src/core/integrations/filter/flatten.ts +16 -0
  152. package/src/core/objects/aggregate/aggregate.diff.ts +33 -0
  153. package/src/core/objects/aggregate/aggregate.model.ts +22 -1
  154. package/src/core/objects/aggregate/changes/aggregate.base.ts +5 -1
  155. package/src/core/objects/aggregate/changes/aggregate.security-label.ts +99 -0
  156. package/src/core/objects/aggregate/changes/aggregate.types.ts +3 -1
  157. package/src/core/objects/base.model.ts +2 -0
  158. package/src/core/objects/domain/changes/domain.base.ts +5 -1
  159. package/src/core/objects/domain/changes/domain.security-label.test.ts +56 -0
  160. package/src/core/objects/domain/changes/domain.security-label.ts +77 -0
  161. package/src/core/objects/domain/changes/domain.types.ts +3 -1
  162. package/src/core/objects/domain/domain.diff.ts +33 -0
  163. package/src/core/objects/domain/domain.model.ts +22 -1
  164. package/src/core/objects/event-trigger/changes/event-trigger.base.ts +1 -1
  165. package/src/core/objects/event-trigger/changes/event-trigger.security-label.ts +95 -0
  166. package/src/core/objects/event-trigger/changes/event-trigger.types.ts +3 -1
  167. package/src/core/objects/event-trigger/event-trigger.diff.ts +33 -0
  168. package/src/core/objects/event-trigger/event-trigger.model.ts +22 -1
  169. package/src/core/objects/extract-with-retry.test.ts +143 -0
  170. package/src/core/objects/extract-with-retry.ts +87 -0
  171. package/src/core/objects/foreign-data-wrapper/foreign-table/changes/foreign-table.base.ts +5 -1
  172. package/src/core/objects/foreign-data-wrapper/foreign-table/changes/foreign-table.security-label.ts +95 -0
  173. package/src/core/objects/foreign-data-wrapper/foreign-table/changes/foreign-table.types.ts +3 -1
  174. package/src/core/objects/foreign-data-wrapper/foreign-table/foreign-table.diff.ts +33 -0
  175. package/src/core/objects/foreign-data-wrapper/foreign-table/foreign-table.model.ts +24 -1
  176. package/src/core/objects/index/index.diff.ts +0 -1
  177. package/src/core/objects/index/index.model.test.ts +37 -1
  178. package/src/core/objects/index/index.model.ts +25 -6
  179. package/src/core/objects/materialized-view/changes/materialized-view.base.ts +5 -1
  180. package/src/core/objects/materialized-view/changes/materialized-view.security-label.test.ts +63 -0
  181. package/src/core/objects/materialized-view/changes/materialized-view.security-label.ts +95 -0
  182. package/src/core/objects/materialized-view/changes/materialized-view.types.ts +3 -1
  183. package/src/core/objects/materialized-view/materialized-view.diff.ts +37 -0
  184. package/src/core/objects/materialized-view/materialized-view.model.test.ts +93 -0
  185. package/src/core/objects/materialized-view/materialized-view.model.ts +52 -8
  186. package/src/core/objects/procedure/changes/procedure.base.ts +5 -1
  187. package/src/core/objects/procedure/changes/procedure.security-label.ts +105 -0
  188. package/src/core/objects/procedure/changes/procedure.types.ts +3 -1
  189. package/src/core/objects/procedure/procedure.diff.ts +33 -0
  190. package/src/core/objects/procedure/procedure.model.test.ts +117 -0
  191. package/src/core/objects/procedure/procedure.model.ts +51 -7
  192. package/src/core/objects/publication/changes/publication.base.ts +1 -1
  193. package/src/core/objects/publication/changes/publication.security-label.ts +95 -0
  194. package/src/core/objects/publication/changes/publication.types.ts +3 -1
  195. package/src/core/objects/publication/publication.diff.ts +33 -0
  196. package/src/core/objects/publication/publication.model.ts +24 -1
  197. package/src/core/objects/rls-policy/rls-policy.diff.ts +19 -1
  198. package/src/core/objects/role/changes/role.base.ts +2 -1
  199. package/src/core/objects/role/changes/role.security-label.ts +77 -0
  200. package/src/core/objects/role/changes/role.types.ts +3 -1
  201. package/src/core/objects/role/role.diff.ts +33 -0
  202. package/src/core/objects/role/role.model.ts +32 -0
  203. package/src/core/objects/rule/rule.model.test.ts +99 -0
  204. package/src/core/objects/rule/rule.model.ts +28 -4
  205. package/src/core/objects/schema/changes/schema.alter.test.ts +1 -0
  206. package/src/core/objects/schema/changes/schema.base.ts +5 -1
  207. package/src/core/objects/schema/changes/schema.create.test.ts +1 -0
  208. package/src/core/objects/schema/changes/schema.drop.test.ts +1 -0
  209. package/src/core/objects/schema/changes/schema.security-label.test.ts +76 -0
  210. package/src/core/objects/schema/changes/schema.security-label.ts +77 -0
  211. package/src/core/objects/schema/changes/schema.types.ts +3 -1
  212. package/src/core/objects/schema/schema.diff.test.ts +1 -0
  213. package/src/core/objects/schema/schema.diff.ts +43 -1
  214. package/src/core/objects/schema/schema.model.ts +21 -1
  215. package/src/core/objects/security-label.types.test.ts +106 -0
  216. package/src/core/objects/security-label.types.ts +61 -0
  217. package/src/core/objects/sequence/changes/sequence.base.ts +5 -1
  218. package/src/core/objects/sequence/changes/sequence.security-label.test.ts +58 -0
  219. package/src/core/objects/sequence/changes/sequence.security-label.ts +92 -0
  220. package/src/core/objects/sequence/changes/sequence.types.ts +3 -1
  221. package/src/core/objects/sequence/sequence.diff.test.ts +87 -0
  222. package/src/core/objects/sequence/sequence.diff.ts +64 -6
  223. package/src/core/objects/sequence/sequence.model.ts +22 -1
  224. package/src/core/objects/subscription/changes/subscription.base.ts +1 -1
  225. package/src/core/objects/subscription/changes/subscription.security-label.ts +95 -0
  226. package/src/core/objects/subscription/changes/subscription.types.ts +3 -1
  227. package/src/core/objects/subscription/subscription.diff.ts +33 -0
  228. package/src/core/objects/subscription/subscription.model.ts +22 -1
  229. package/src/core/objects/table/changes/table.alter.test.ts +13 -21
  230. package/src/core/objects/table/changes/table.alter.ts +30 -3
  231. package/src/core/objects/table/changes/table.base.ts +5 -1
  232. package/src/core/objects/table/changes/table.security-label.test.ts +140 -0
  233. package/src/core/objects/table/changes/table.security-label.ts +183 -0
  234. package/src/core/objects/table/changes/table.types.ts +3 -1
  235. package/src/core/objects/table/table.diff.ts +111 -19
  236. package/src/core/objects/table/table.model.test.ts +209 -0
  237. package/src/core/objects/table/table.model.ts +94 -9
  238. package/src/core/objects/trigger/trigger.model.test.ts +113 -0
  239. package/src/core/objects/trigger/trigger.model.ts +28 -5
  240. package/src/core/objects/type/composite-type/changes/composite-type.base.ts +5 -1
  241. package/src/core/objects/type/composite-type/changes/composite-type.security-label.ts +95 -0
  242. package/src/core/objects/type/composite-type/changes/composite-type.types.ts +3 -1
  243. package/src/core/objects/type/composite-type/composite-type.diff.ts +33 -0
  244. package/src/core/objects/type/composite-type/composite-type.model.ts +26 -2
  245. package/src/core/objects/type/enum/changes/enum.base.ts +5 -1
  246. package/src/core/objects/type/enum/changes/enum.security-label.ts +77 -0
  247. package/src/core/objects/type/enum/changes/enum.types.ts +3 -1
  248. package/src/core/objects/type/enum/enum.diff.ts +33 -0
  249. package/src/core/objects/type/enum/enum.model.ts +25 -1
  250. package/src/core/objects/type/range/changes/range.base.ts +5 -1
  251. package/src/core/objects/type/range/changes/range.security-label.ts +77 -0
  252. package/src/core/objects/type/range/changes/range.types.ts +3 -1
  253. package/src/core/objects/type/range/range.diff.ts +33 -0
  254. package/src/core/objects/type/range/range.model.ts +22 -1
  255. package/src/core/objects/utils.ts +6 -0
  256. package/src/core/objects/view/changes/view.base.ts +5 -1
  257. package/src/core/objects/view/changes/view.security-label.test.ts +64 -0
  258. package/src/core/objects/view/changes/view.security-label.ts +77 -0
  259. package/src/core/objects/view/changes/view.types.ts +3 -1
  260. package/src/core/objects/view/view.diff.ts +31 -0
  261. package/src/core/objects/view/view.model.test.ts +90 -0
  262. package/src/core/objects/view/view.model.ts +53 -7
  263. package/src/core/plan/create.ts +3 -1
  264. package/src/core/plan/sql-format/fixtures.ts +1 -0
  265. package/src/core/plan/types.ts +8 -0
  266. package/src/core/{post-diff-cycle-breaking.test.ts → post-diff-normalization.test.ts} +168 -4
  267. package/src/core/post-diff-normalization.ts +260 -0
  268. package/src/core/sort/cycle-breakers.ts +1 -1
  269. package/src/core/sort/utils.ts +38 -0
  270. package/dist/core/post-diff-cycle-breaking.js +0 -100
  271. package/src/core/post-diff-cycle-breaking.ts +0 -138
@@ -3,7 +3,11 @@ import type { MaterializedView } from "../materialized-view.model.ts";
3
3
 
4
4
  abstract class BaseMaterializedViewChange extends BaseChange {
5
5
  abstract readonly materializedView: MaterializedView;
6
- abstract readonly scope: "object" | "comment" | "privilege";
6
+ abstract readonly scope:
7
+ | "object"
8
+ | "comment"
9
+ | "privilege"
10
+ | "security_label";
7
11
  readonly objectType: "materialized_view" = "materialized_view";
8
12
  }
9
13
 
@@ -0,0 +1,63 @@
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 {
5
+ MaterializedView,
6
+ type MaterializedViewProps,
7
+ } from "../materialized-view.model.ts";
8
+ import {
9
+ CreateSecurityLabelOnMaterializedView,
10
+ DropSecurityLabelOnMaterializedView,
11
+ } from "./materialized-view.security-label.ts";
12
+
13
+ const makeMV = (): MaterializedView =>
14
+ new MaterializedView({
15
+ schema: "public",
16
+ name: "mv",
17
+ definition: "SELECT 1",
18
+ row_security: false,
19
+ force_row_security: false,
20
+ has_indexes: false,
21
+ has_rules: false,
22
+ has_triggers: false,
23
+ has_subclasses: false,
24
+ is_populated: true,
25
+ replica_identity: "d",
26
+ is_partition: false,
27
+ options: null,
28
+ partition_bound: null,
29
+ owner: "postgres",
30
+ comment: null,
31
+ columns: [],
32
+ privileges: [],
33
+ } as MaterializedViewProps);
34
+
35
+ describe("materialized-view.security-label", () => {
36
+ test("create serializes", async () => {
37
+ const mv = makeMV();
38
+ const change = new CreateSecurityLabelOnMaterializedView({
39
+ materializedView: mv,
40
+ securityLabel: { provider: "dummy", label: "classified" },
41
+ });
42
+ expect(change.scope).toBe("security_label");
43
+ expect(change.creates).toEqual([
44
+ stableId.securityLabel(mv.stableId, "dummy"),
45
+ ]);
46
+ await assertValidSql(change.serialize());
47
+ expect(change.serialize()).toBe(
48
+ "SECURITY LABEL FOR dummy ON MATERIALIZED VIEW public.mv IS 'classified'",
49
+ );
50
+ });
51
+
52
+ test("drop serializes to IS NULL", async () => {
53
+ const mv = makeMV();
54
+ const change = new DropSecurityLabelOnMaterializedView({
55
+ materializedView: mv,
56
+ securityLabel: { provider: "dummy", label: "x" },
57
+ });
58
+ await assertValidSql(change.serialize());
59
+ expect(change.serialize()).toBe(
60
+ "SECURITY LABEL FOR dummy ON MATERIALIZED VIEW public.mv IS NULL",
61
+ );
62
+ });
63
+ });
@@ -0,0 +1,95 @@
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 { MaterializedView } from "../materialized-view.model.ts";
5
+ import {
6
+ CreateMaterializedViewChange,
7
+ DropMaterializedViewChange,
8
+ } from "./materialized-view.base.ts";
9
+
10
+ export type SecurityLabelMaterializedView =
11
+ | CreateSecurityLabelOnMaterializedView
12
+ | DropSecurityLabelOnMaterializedView;
13
+
14
+ export class CreateSecurityLabelOnMaterializedView extends CreateMaterializedViewChange {
15
+ public readonly materializedView: MaterializedView;
16
+ public readonly securityLabel: SecurityLabelProps;
17
+ public readonly scope = "security_label" as const;
18
+
19
+ constructor(props: {
20
+ materializedView: MaterializedView;
21
+ securityLabel: SecurityLabelProps;
22
+ }) {
23
+ super();
24
+ this.materializedView = props.materializedView;
25
+ this.securityLabel = props.securityLabel;
26
+ }
27
+
28
+ get creates() {
29
+ return [
30
+ stableId.securityLabel(
31
+ this.materializedView.stableId,
32
+ this.securityLabel.provider,
33
+ ),
34
+ ];
35
+ }
36
+
37
+ get requires() {
38
+ return [this.materializedView.stableId];
39
+ }
40
+
41
+ serialize(): string {
42
+ return [
43
+ "SECURITY LABEL FOR",
44
+ this.securityLabel.provider,
45
+ "ON MATERIALIZED VIEW",
46
+ `${this.materializedView.schema}.${this.materializedView.name}`,
47
+ "IS",
48
+ quoteLiteral(this.securityLabel.label),
49
+ ].join(" ");
50
+ }
51
+ }
52
+
53
+ export class DropSecurityLabelOnMaterializedView extends DropMaterializedViewChange {
54
+ public readonly materializedView: MaterializedView;
55
+ public readonly securityLabel: SecurityLabelProps;
56
+ public readonly scope = "security_label" as const;
57
+
58
+ constructor(props: {
59
+ materializedView: MaterializedView;
60
+ securityLabel: SecurityLabelProps;
61
+ }) {
62
+ super();
63
+ this.materializedView = props.materializedView;
64
+ this.securityLabel = props.securityLabel;
65
+ }
66
+
67
+ get drops() {
68
+ return [
69
+ stableId.securityLabel(
70
+ this.materializedView.stableId,
71
+ this.securityLabel.provider,
72
+ ),
73
+ ];
74
+ }
75
+
76
+ get requires() {
77
+ return [
78
+ stableId.securityLabel(
79
+ this.materializedView.stableId,
80
+ this.securityLabel.provider,
81
+ ),
82
+ this.materializedView.stableId,
83
+ ];
84
+ }
85
+
86
+ serialize(): string {
87
+ return [
88
+ "SECURITY LABEL FOR",
89
+ this.securityLabel.provider,
90
+ "ON MATERIALIZED VIEW",
91
+ `${this.materializedView.schema}.${this.materializedView.name}`,
92
+ "IS NULL",
93
+ ].join(" ");
94
+ }
95
+ }
@@ -3,6 +3,7 @@ import type { CommentMaterializedView } from "./materialized-view.comment.ts";
3
3
  import type { CreateMaterializedView } from "./materialized-view.create.ts";
4
4
  import type { DropMaterializedView } from "./materialized-view.drop.ts";
5
5
  import type { MaterializedViewPrivilege } from "./materialized-view.privilege.ts";
6
+ import type { SecurityLabelMaterializedView } from "./materialized-view.security-label.ts";
6
7
 
7
8
  /** Union of all materialized-view-related change variants (`objectType: "materialized_view"`). @category Change Types */
8
9
  export type MaterializedViewChange =
@@ -10,4 +11,5 @@ export type MaterializedViewChange =
10
11
  | CommentMaterializedView
11
12
  | CreateMaterializedView
12
13
  | DropMaterializedView
13
- | MaterializedViewPrivilege;
14
+ | MaterializedViewPrivilege
15
+ | SecurityLabelMaterializedView;
@@ -4,6 +4,7 @@ import {
4
4
  emitColumnPrivilegeChanges,
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 { deepEqual, hasNonAlterableChanges } from "../utils.ts";
8
9
  import {
9
10
  AlterMaterializedViewChangeOwner,
@@ -22,6 +23,10 @@ import {
22
23
  RevokeGrantOptionMaterializedViewPrivileges,
23
24
  RevokeMaterializedViewPrivileges,
24
25
  } from "./changes/materialized-view.privilege.ts";
26
+ import {
27
+ CreateSecurityLabelOnMaterializedView,
28
+ DropSecurityLabelOnMaterializedView,
29
+ } from "./changes/materialized-view.security-label.ts";
25
30
  import type { MaterializedViewChange } from "./changes/materialized-view.types.ts";
26
31
  import type { MaterializedView } from "./materialized-view.model.ts";
27
32
 
@@ -88,6 +93,17 @@ export function diffMaterializedViews(
88
93
  }
89
94
  }
90
95
 
96
+ // Security labels on the matview itself (columns of matviews are not
97
+ // supported targets of SECURITY LABEL, so we only label the relation).
98
+ for (const label of mv.security_labels) {
99
+ changes.push(
100
+ new CreateSecurityLabelOnMaterializedView({
101
+ materializedView: mv,
102
+ securityLabel: label,
103
+ }),
104
+ );
105
+ }
106
+
91
107
  // PRIVILEGES: For created objects, compare against default privileges state
92
108
  // The migration script will run ALTER DEFAULT PRIVILEGES before CREATE (via constraint spec),
93
109
  // so objects are created with the default privileges state in effect.
@@ -241,6 +257,27 @@ export function diffMaterializedViews(
241
257
  );
242
258
  }
243
259
  }
260
+
261
+ // SECURITY LABELS
262
+ changes.push(
263
+ ...diffSecurityLabels<
264
+ | CreateSecurityLabelOnMaterializedView
265
+ | DropSecurityLabelOnMaterializedView
266
+ >(
267
+ mainMaterializedView.security_labels,
268
+ branchMaterializedView.security_labels,
269
+ (securityLabel) =>
270
+ new CreateSecurityLabelOnMaterializedView({
271
+ materializedView: branchMaterializedView,
272
+ securityLabel,
273
+ }),
274
+ (securityLabel) =>
275
+ new DropSecurityLabelOnMaterializedView({
276
+ materializedView: mainMaterializedView,
277
+ securityLabel,
278
+ }),
279
+ ),
280
+ );
244
281
  // COMMENT changes on columns
245
282
  const mainCols = new Map(
246
283
  mainMaterializedView.columns.map((c) => [c.name, c]),
@@ -0,0 +1,93 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import type { Pool } from "pg";
3
+ import {
4
+ extractMaterializedViews,
5
+ MaterializedView,
6
+ } from "./materialized-view.model.ts";
7
+
8
+ const baseRow = {
9
+ schema: "public",
10
+ row_security: false,
11
+ force_row_security: false,
12
+ has_indexes: false,
13
+ has_rules: false,
14
+ has_triggers: false,
15
+ has_subclasses: false,
16
+ is_populated: true,
17
+ replica_identity: "d" as const,
18
+ is_partition: false,
19
+ options: null,
20
+ partition_bound: null,
21
+ owner: "postgres",
22
+ comment: null,
23
+ columns: [],
24
+ privileges: [],
25
+ };
26
+
27
+ const mockPool = (rows: unknown[]): Pool =>
28
+ ({ query: async () => ({ rows }) }) as unknown as Pool;
29
+
30
+ const mockPoolSequence = (...attempts: unknown[][]): Pool => {
31
+ let i = 0;
32
+ return {
33
+ query: async () => ({
34
+ rows: attempts[Math.min(i++, attempts.length - 1)],
35
+ }),
36
+ } as unknown as Pool;
37
+ };
38
+
39
+ const NO_BACKOFF = { backoffMs: 0 } as const;
40
+
41
+ describe("extractMaterializedViews", () => {
42
+ test("skips rows where pg_get_viewdef returned NULL after exhausting retries", async () => {
43
+ const mvs = await extractMaterializedViews(
44
+ mockPool([
45
+ {
46
+ ...baseRow,
47
+ name: '"good_mv"',
48
+ definition: "SELECT 1",
49
+ },
50
+ { ...baseRow, name: '"orphan_mv"', definition: null },
51
+ ]),
52
+ NO_BACKOFF,
53
+ );
54
+
55
+ expect(mvs).toHaveLength(1);
56
+ expect(mvs[0]).toBeInstanceOf(MaterializedView);
57
+ expect(mvs[0]?.name).toBe('"good_mv"');
58
+ expect(mvs[0]?.definition).toBe("SELECT 1");
59
+ });
60
+
61
+ test("does not throw ZodError when the only row has a null definition", async () => {
62
+ await expect(
63
+ extractMaterializedViews(
64
+ mockPool([{ ...baseRow, name: '"orphan"', definition: null }]),
65
+ NO_BACKOFF,
66
+ ),
67
+ ).resolves.toEqual([]);
68
+ });
69
+
70
+ test("returns all materialized views when every row has a valid definition", async () => {
71
+ const mvs = await extractMaterializedViews(
72
+ mockPool([
73
+ { ...baseRow, name: '"a"', definition: "SELECT 1" },
74
+ { ...baseRow, name: '"b"', definition: "SELECT 2" },
75
+ ]),
76
+ NO_BACKOFF,
77
+ );
78
+ expect(mvs.map((m) => m.name)).toEqual(['"a"', '"b"']);
79
+ });
80
+
81
+ test("recovers when pg_get_viewdef is NULL on first attempt but resolved on retry", async () => {
82
+ const mvs = await extractMaterializedViews(
83
+ mockPoolSequence(
84
+ [{ ...baseRow, name: '"racy_mv"', definition: null }],
85
+ [{ ...baseRow, name: '"racy_mv"', definition: "SELECT 42" }],
86
+ ),
87
+ { retries: 2, backoffMs: 0 },
88
+ );
89
+ expect(mvs).toHaveLength(1);
90
+ expect(mvs[0]?.name).toBe('"racy_mv"');
91
+ expect(mvs[0]?.definition).toBe("SELECT 42");
92
+ });
93
+ });
@@ -10,6 +10,15 @@ import {
10
10
  type PrivilegeProps,
11
11
  privilegePropsSchema,
12
12
  } from "../base.privilege-diff.ts";
13
+ import {
14
+ type ExtractRetryOptions,
15
+ extractWithDefinitionRetry,
16
+ } from "../extract-with-retry.ts";
17
+ import {
18
+ normalizeSecurityLabels,
19
+ type SecurityLabelProps,
20
+ securityLabelPropsSchema,
21
+ } from "../security-label.types.ts";
13
22
  import { ReplicaIdentitySchema } from "../table/table.model.ts";
14
23
 
15
24
  const materializedViewPropsSchema = z.object({
@@ -31,6 +40,16 @@ const materializedViewPropsSchema = z.object({
31
40
  comment: z.string().nullable(),
32
41
  columns: z.array(columnPropsSchema),
33
42
  privileges: z.array(privilegePropsSchema),
43
+ security_labels: z.array(securityLabelPropsSchema).default([]).optional(),
44
+ });
45
+
46
+ // pg_get_viewdef(oid) can return NULL when the underlying matview (or its
47
+ // pg_rewrite row) is dropped between catalog scan and resolution, or under
48
+ // transient catalog state during recovery. An unreadable matview cannot be
49
+ // diffed, so we accept NULL here and filter the row out at extraction time
50
+ // rather than crashing the whole catalog parse with a ZodError.
51
+ const materializedViewRowSchema = materializedViewPropsSchema.extend({
52
+ definition: z.string().nullable(),
34
53
  });
35
54
 
36
55
  type MaterializedViewPrivilegeProps = PrivilegeProps;
@@ -55,6 +74,7 @@ export class MaterializedView extends BasePgModel implements TableLikeObject {
55
74
  public readonly comment: MaterializedViewProps["comment"];
56
75
  public readonly columns: MaterializedViewProps["columns"];
57
76
  public readonly privileges: MaterializedViewPrivilegeProps[];
77
+ public readonly security_labels: SecurityLabelProps[];
58
78
 
59
79
  constructor(props: MaterializedViewProps) {
60
80
  super();
@@ -80,6 +100,7 @@ export class MaterializedView extends BasePgModel implements TableLikeObject {
80
100
  this.comment = props.comment;
81
101
  this.columns = props.columns;
82
102
  this.privileges = props.privileges;
103
+ this.security_labels = props.security_labels ?? [];
83
104
  }
84
105
 
85
106
  get stableId(): `materializedView:${string}` {
@@ -111,6 +132,7 @@ export class MaterializedView extends BasePgModel implements TableLikeObject {
111
132
  comment: this.comment,
112
133
  columns: this.columns,
113
134
  privileges: this.privileges,
135
+ security_labels: this.security_labels,
114
136
  };
115
137
  }
116
138
 
@@ -135,6 +157,7 @@ export class MaterializedView extends BasePgModel implements TableLikeObject {
135
157
  data: {
136
158
  ...this.dataFields,
137
159
  columns: normalizeColumns(),
160
+ security_labels: normalizeSecurityLabels(this.security_labels),
138
161
  },
139
162
  };
140
163
  }
@@ -142,8 +165,14 @@ export class MaterializedView extends BasePgModel implements TableLikeObject {
142
165
 
143
166
  export async function extractMaterializedViews(
144
167
  pool: Pool,
168
+ options?: ExtractRetryOptions,
145
169
  ): Promise<MaterializedView[]> {
146
- const { rows: mvRows } = await pool.query<MaterializedViewProps>(sql`
170
+ const mvRows = await extractWithDefinitionRetry({
171
+ label: "materialized views",
172
+ options,
173
+ hasNullDefinition: (row) => row.definition === null,
174
+ query: async () => {
175
+ const result = await pool.query<MaterializedViewProps>(sql`
147
176
  with extension_oids as (
148
177
  select
149
178
  objid
@@ -233,7 +262,20 @@ select
233
262
  join lateral aclexplode(src.acl) as x(grantor, grantee, privilege_type, is_grantable) on true
234
263
  group by x.grantee, x.privilege_type
235
264
  ) as grp
236
- ), '[]') as privileges
265
+ ), '[]') as privileges,
266
+ coalesce(
267
+ (
268
+ select json_agg(
269
+ json_build_object('provider', sl.provider, 'label', sl.label)
270
+ order by sl.provider
271
+ )
272
+ from pg_catalog.pg_seclabel sl
273
+ where sl.objoid = c.oid
274
+ and sl.classoid = 'pg_class'::regclass
275
+ and sl.objsubid = 0
276
+ ),
277
+ '[]'::json
278
+ ) as security_labels
237
279
  from
238
280
  pg_catalog.pg_class c
239
281
  left outer join extension_oids e on c.oid = e.objid
@@ -248,11 +290,13 @@ group by
248
290
  order by
249
291
  c.relnamespace::regnamespace, c.relname
250
292
  `);
251
- // Validate and parse each row using the Zod schema
252
- const validatedRows = mvRows.map((row: unknown) =>
253
- materializedViewPropsSchema.parse(row),
254
- );
255
- return validatedRows.map(
256
- (row: MaterializedViewProps) => new MaterializedView(row),
293
+ return result.rows.map((row: unknown) =>
294
+ materializedViewRowSchema.parse(row),
295
+ );
296
+ },
297
+ });
298
+ const validatedRows = mvRows.filter(
299
+ (row): row is MaterializedViewProps => row.definition !== null,
257
300
  );
301
+ return validatedRows.map((row) => new MaterializedView(row));
258
302
  }
@@ -3,7 +3,11 @@ import type { Procedure } from "../procedure.model.ts";
3
3
 
4
4
  abstract class BaseProcedureChange extends BaseChange {
5
5
  abstract readonly procedure: Procedure;
6
- abstract readonly scope: "object" | "comment" | "privilege";
6
+ abstract readonly scope:
7
+ | "object"
8
+ | "comment"
9
+ | "privilege"
10
+ | "security_label";
7
11
  readonly objectType: "procedure" = "procedure";
8
12
  }
9
13
 
@@ -0,0 +1,105 @@
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 { Procedure } from "../procedure.model.ts";
5
+ import {
6
+ CreateProcedureChange,
7
+ DropProcedureChange,
8
+ } from "./procedure.base.ts";
9
+
10
+ export type SecurityLabelProcedure =
11
+ | CreateSecurityLabelOnProcedure
12
+ | DropSecurityLabelOnProcedure;
13
+
14
+ function targetKeyword(p: Procedure): "FUNCTION" | "PROCEDURE" {
15
+ return p.kind === "p" ? "PROCEDURE" : "FUNCTION";
16
+ }
17
+
18
+ function procedureIdentity(p: Procedure): string {
19
+ return `${p.schema}.${p.name}(${(p.argument_types ?? []).join(",")})`;
20
+ }
21
+
22
+ export class CreateSecurityLabelOnProcedure extends CreateProcedureChange {
23
+ public readonly procedure: Procedure;
24
+ public readonly securityLabel: SecurityLabelProps;
25
+ public readonly scope = "security_label" as const;
26
+
27
+ constructor(props: {
28
+ procedure: Procedure;
29
+ securityLabel: SecurityLabelProps;
30
+ }) {
31
+ super();
32
+ this.procedure = props.procedure;
33
+ this.securityLabel = props.securityLabel;
34
+ }
35
+
36
+ get creates() {
37
+ return [
38
+ stableId.securityLabel(
39
+ this.procedure.stableId,
40
+ this.securityLabel.provider,
41
+ ),
42
+ ];
43
+ }
44
+
45
+ get requires() {
46
+ return [this.procedure.stableId];
47
+ }
48
+
49
+ serialize(): string {
50
+ return [
51
+ "SECURITY LABEL FOR",
52
+ this.securityLabel.provider,
53
+ "ON",
54
+ targetKeyword(this.procedure),
55
+ procedureIdentity(this.procedure),
56
+ "IS",
57
+ quoteLiteral(this.securityLabel.label),
58
+ ].join(" ");
59
+ }
60
+ }
61
+
62
+ export class DropSecurityLabelOnProcedure extends DropProcedureChange {
63
+ public readonly procedure: Procedure;
64
+ public readonly securityLabel: SecurityLabelProps;
65
+ public readonly scope = "security_label" as const;
66
+
67
+ constructor(props: {
68
+ procedure: Procedure;
69
+ securityLabel: SecurityLabelProps;
70
+ }) {
71
+ super();
72
+ this.procedure = props.procedure;
73
+ this.securityLabel = props.securityLabel;
74
+ }
75
+
76
+ get drops() {
77
+ return [
78
+ stableId.securityLabel(
79
+ this.procedure.stableId,
80
+ this.securityLabel.provider,
81
+ ),
82
+ ];
83
+ }
84
+
85
+ get requires() {
86
+ return [
87
+ stableId.securityLabel(
88
+ this.procedure.stableId,
89
+ this.securityLabel.provider,
90
+ ),
91
+ this.procedure.stableId,
92
+ ];
93
+ }
94
+
95
+ serialize(): string {
96
+ return [
97
+ "SECURITY LABEL FOR",
98
+ this.securityLabel.provider,
99
+ "ON",
100
+ targetKeyword(this.procedure),
101
+ procedureIdentity(this.procedure),
102
+ "IS NULL",
103
+ ].join(" ");
104
+ }
105
+ }
@@ -3,6 +3,7 @@ import type { CommentProcedure } from "./procedure.comment.ts";
3
3
  import type { CreateProcedure } from "./procedure.create.ts";
4
4
  import type { DropProcedure } from "./procedure.drop.ts";
5
5
  import type { ProcedurePrivilege } from "./procedure.privilege.ts";
6
+ import type { SecurityLabelProcedure } from "./procedure.security-label.ts";
6
7
 
7
8
  /** Union of all procedure-related change variants (`objectType: "procedure"`). @category Change Types */
8
9
  export type ProcedureChange =
@@ -10,4 +11,5 @@ export type ProcedureChange =
10
11
  | CommentProcedure
11
12
  | CreateProcedure
12
13
  | DropProcedure
13
- | ProcedurePrivilege;
14
+ | ProcedurePrivilege
15
+ | SecurityLabelProcedure;
@@ -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 { deepEqual, hasNonAlterableChanges } from "../utils.ts";
9
10
  import {
10
11
  AlterProcedureChangeOwner,
@@ -26,6 +27,10 @@ import {
26
27
  RevokeGrantOptionProcedurePrivileges,
27
28
  RevokeProcedurePrivileges,
28
29
  } from "./changes/procedure.privilege.ts";
30
+ import {
31
+ CreateSecurityLabelOnProcedure,
32
+ DropSecurityLabelOnProcedure,
33
+ } from "./changes/procedure.security-label.ts";
29
34
  import type { ProcedureChange } from "./changes/procedure.types.ts";
30
35
  import type { Procedure } from "./procedure.model.ts";
31
36
 
@@ -66,6 +71,14 @@ export function diffProcedures(
66
71
  if (proc.comment !== null) {
67
72
  changes.push(new CreateCommentOnProcedure({ procedure: proc }));
68
73
  }
74
+ for (const label of proc.security_labels) {
75
+ changes.push(
76
+ new CreateSecurityLabelOnProcedure({
77
+ procedure: proc,
78
+ securityLabel: label,
79
+ }),
80
+ );
81
+ }
69
82
 
70
83
  // PRIVILEGES: For created objects, compare against default privileges state
71
84
  // The migration script will run ALTER DEFAULT PRIVILEGES before CREATE (via constraint spec),
@@ -225,6 +238,26 @@ export function diffProcedures(
225
238
  }
226
239
  }
227
240
 
241
+ // SECURITY LABELS
242
+ changes.push(
243
+ ...diffSecurityLabels<
244
+ CreateSecurityLabelOnProcedure | DropSecurityLabelOnProcedure
245
+ >(
246
+ mainProcedure.security_labels,
247
+ branchProcedure.security_labels,
248
+ (securityLabel) =>
249
+ new CreateSecurityLabelOnProcedure({
250
+ procedure: branchProcedure,
251
+ securityLabel,
252
+ }),
253
+ (securityLabel) =>
254
+ new DropSecurityLabelOnProcedure({
255
+ procedure: mainProcedure,
256
+ securityLabel,
257
+ }),
258
+ ),
259
+ );
260
+
228
261
  // SECURITY DEFINER/INVOKER
229
262
  if (mainProcedure.security_definer !== branchProcedure.security_definer) {
230
263
  changes.push(